diff --git a/.gitignore b/.gitignore index 5f4c0e2121..8f5d5a1cb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Stainless -/bin/stainless-* .stainless-cache/ .stainless.conf stainless.conf diff --git a/.larabot.conf b/.larabot.conf index 5a49cc690b..d6935700c1 100644 --- a/.larabot.conf +++ b/.larabot.conf @@ -1,15 +1,14 @@ commands = [ - "sbt -batch -Dtest-parallelism=5 test" - "sbt -batch -Dtest-parallelism=5 \"it:testOnly stainless.GhostRewriteSuite stainless.GenCSuite stainless.ScalacExtractionSuite stainless.LibrarySuite stainless.verification.SMTZ3VerificationSuite stainless.verification.SMTZ3UncheckedSuite stainless.verification.TerminationVerificationSuite stainless.verification.ImperativeSuite stainless.verification.FullImperativeSuite stainless.verification.StrictArithmeticSuite stainless.verification.CodeGenVerificationSuite stainless.verification.SMTCVC4VerificationSuite stainless.verification.SMTCVC4UncheckedSuite stainless.verification.PrincessVerificationSuite stainless.termination.TerminationSuite stainless.evaluators.EvaluatorComponentTest\"" + "sbt -batch -Dtestsuite-parallelism=5 test" + "sbt -batch -Dtestsuite-parallelism=3 -Dtestcase-parallelism=5 \"stainless-dotty / IntegrationTest / testOnly stainless.DottyExtractionSuite stainless.GhostRewriteSuite stainless.GenCSuite stainless.LibrarySuite stainless.verification.VerificationSuite stainless.verification.UncheckedSuite stainless.verification.TerminationVerificationSuite stainless.verification.ImperativeSuite stainless.verification.FullImperativeSuite stainless.verification.StrictArithmeticSuite stainless.termination.TerminationSuite stainless.evaluators.EvaluatorComponentTest\"" ] nightly { commands = [ "sbt universal:stage" - "sbt -batch -Dtest-parallelism=5 test" - "sbt -batch -Dtest-parallelism=5 it:test" - "bash bin/external-tests.sh --only-scalac" - "bash bin/external-tests.sh --only-dotty" + "sbt -batch -Dtestsuite-parallelism=5 test" + "sbt -batch -Dtestsuite-parallelism=3 -Dtestcase-parallelism=5 it:test" + "bash bin/external-tests.sh" "sbt -batch scripted" "bash bin/build-slc-lib.sh" ] diff --git a/README.md b/README.md index f592361360..e76b73ad3f 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # Stainless [![Release][release-img]][latest-release] [![Nightly Build Status][nightly-larabot-img]][nightly-larabot-ref] [![Build Status][larabot-img]][larabot-ref] [![Gitter chat][gitter-img]][gitter-ref] [![Apache 2.0 License][license-img]][license-ref] +Hosted at https://github.com/epfl-lara/stainless ; mirrored at https://gitlab.epfl.ch/lara/stainless + Verification framework for a subset of the [Scala](http://scala-lang.org) programming language. See the [tutorial](https://epfl-lara.github.io/asplos2022tutorial/). +Please note that Stainless does not support Scala 2 frontend anymore but only Scala 3.3. The latest release that does support Scala 2.13 frontend is the [v0.9.8.7](https://github.com/epfl-lara/stainless/releases/tag/v0.9.8.7). + ## Quick start We test mostly on [Ubuntu](https://ubuntu.com/download); on [Windows](https://www.microsoft.com/eb-gb/software-download/windows10), you can get sufficient text-based Ubuntu environment by installing [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install) (e.g. `wsl --install`, then `wsl --install -d ubuntu`). Ensure you have a [Java](https://openjdk.org/projects/jdk/17/) version ready (it can be headless); on Ubuntu `sudo apt install openjdk-17-jdk-headless` suffices. -Once ready, [Download the latest `stainless-dotty-standalone` release](https://github.com/epfl-lara/stainless/releases) for your platform. Unzip the archive, and run Stainless through the `stainless.sh` script. Stainless expects a list of space-separated Scala files to verify but also has other [Command-line Options](https://epfl-lara.github.io/stainless/options.html). +Once ready, [Download the latest `stainless-dotty-standalone` release](https://github.com/epfl-lara/stainless/releases) for your platform. Unzip the archive, and run Stainless through the `stainless` script. Stainless expects a list of space-separated Scala files to verify but also has other [Command-line Options](https://epfl-lara.github.io/stainless/options.html). -To check if everything works, you may create a file named `HelloStainless.scala` next to the `stainless.sh` script with the following content: +To check if everything works, you may create a file named `HelloStainless.scala` next to the `stainless` script with the following content: ```scala import stainless.collection._ object HelloStainless { @@ -21,7 +25,7 @@ object HelloStainless { } } ``` -and run `stainless.sh HelloStainless.scala`. +and run `stainless HelloStainless.scala`. If all goes well, Stainless should report something along the lines: ``` [ Info ] ┌───────────────────┐ @@ -36,11 +40,11 @@ If all goes well, Stainless should report something along the lines: If you see funny symbols instead of the beautiful ASCII art, run Stainless with `--no-colors` option for clean ASCII output with standardized error message format. The release archive of Stainless only requires JDK17. In particular, it needs neither a Scala compiler nor SBT. -It is shipped with Z3 4.8.14 and Princess. If z3 API is not found, use option `--solvers=smt-z3` to rely on the executable. +It is shipped with Z3 4.12.2, cvc5 1.0.8 and Princess. If Z3 API is not found, use option `--solvers=smt-z3` to rely on the executable. ## SBT Stainless plugin -Alternatively, one may integrate Stainless with SBT. The supported Scala versions are `3.2.0` and `2.13.6` +Alternatively, one may integrate Stainless with SBT. The supported Scala versions is `3.3.3`. To do so, download [sbt-stainless](https://github.com/epfl-lara/stainless/releases), and move it to the directory of the project. Assuming the project's structure is: ``` @@ -70,10 +74,11 @@ these should be moved according to the above structure. Finally, the plugin must be explicitly enabled for projects in `build.sbt` desiring Stainless with `.enablePlugins(StainlessPlugin)`. For instance: + ```scala ThisBuild / version := "0.1.0" -ThisBuild / scalaVersion := "3.2.0" +ThisBuild / scalaVersion := "3.3.3" lazy val myTestProject = (project in file(".")) .enablePlugins(StainlessPlugin) // <-------- @@ -84,16 +89,15 @@ lazy val myTestProject = (project in file(".")) Verification occurs with the usual `compile` command. -Note that this method only ships the Princess SMT solver. Z3 and CVC4 can still be used if their executable can be found in the `$PATH`. +Note that this method only ships the Princess SMT solver. Z3 and cvc5 can still be used if their executable can be found in the `$PATH`. ## Build and Use -To start quickly, install a JVM and use a [recent release](https://github.com/epfl-lara/stainless/releases). To build the project, run `sbt universal:stage`. If all goes well, scripts are generated for Scala 3 and Scala 2 versions of the front end: - * `frontends/scalac/target/universal/stage/bin/stainless-scalac` +To start quickly, install a JVM and use a [recent release](https://github.com/epfl-lara/stainless/releases). To build the project, run `sbt universal:stage`. If all goes well, scripts are generated for the front end: * `frontends/dotty/target/universal/stage/bin/stainless-dotty` Use one of these scripts as you would use `scalac` to compile Scala files. -The default behavior of Stainless is to formally verify files, instead of generating JVM class files. +The default behavior of Stainless is to formally verify files, instead of generating JVM class files. See [frontends/benchmarks/verification/valid/](frontends/benchmarks/verification/valid/) and related directories for some benchmarks and [bolts repository](https://github.com/epfl-lara/bolts/) for a larger collection. More information is available in the documentation links. @@ -136,7 +140,7 @@ Stainless is released under the Apache 2.0 license. See the [LICENSE]() file for Stainless relies on Inox to solve the various queries stemming from program verification. Inox supports model-complete queries in a feature-rich fragment that lets Stainless focus -on program transformations and soundness of both contract and termination checking and uses its own reasoning steps, as well as invocations to solvers (theorem provers) [z3](https://github.com/Z3Prover/z3), [CVC4](https://cvc4.github.io/), and [Princess](http://www.philipp.ruemmer.org/princess.shtml). +on program transformations and soundness of both contract and termination checking and uses its own reasoning steps, as well as invocations to solvers (theorem provers) [z3](https://github.com/Z3Prover/z3), [cvc5](https://cvc5.github.io/), and [Princess](http://www.philipp.ruemmer.org/princess.shtml). [latest-release]: https://github.com/epfl-lara/stainless/releases/latest [license-img]: https://img.shields.io/badge/license-Apache_2.0-blue.svg?color=134EA2 diff --git a/bin/bolts-tests.sh b/bin/bolts-tests.sh index 6c2c9171f0..b95da99c82 100755 --- a/bin/bolts-tests.sh +++ b/bin/bolts-tests.sh @@ -3,7 +3,6 @@ set -e TEST_DIR=$1 -FRONTEND=$2 echo "Moving to $TEST_DIR" mkdir -p "$TEST_DIR" cd "$TEST_DIR" || exit 1 @@ -16,6 +15,6 @@ else cd bolts || exit 1 fi -bash ./run-tests.sh "$FRONTEND" +bash ./run-tests.sh stainless-dotty cd ../.. || true diff --git a/bin/external-tests.sh b/bin/external-tests.sh index cdf1d39614..79e73b5351 100755 --- a/bin/external-tests.sh +++ b/bin/external-tests.sh @@ -84,14 +84,10 @@ Usage: external-tests.sh [options] -h | -help Print this message --skip-build Do not build Stainless (saves time if the build is already up-to-date). - --only-scalac Run the tests for the Scalac frontend only. - --only-dotty Run the tests for the Dotty frontend only. EOM } SKIP_BUILD=false -ONLY_SCALAC=false -ONLY_DOTTY=false while [[ $# -gt 0 ]]; do key="$1" @@ -104,14 +100,6 @@ while [[ $# -gt 0 ]]; do SKIP_BUILD=true shift # past argument ;; - --only-scalac) - ONLY_SCALAC=true - shift # past argument - ;; - --only-dotty) - ONLY_DOTTY=true - shift # past argument - ;; *) # unknown option usage exit 1 @@ -141,7 +129,7 @@ if [[ "$SKIP_BUILD" = false ]]; then echo "Publishing Stainless..." - STAINLESS_VERSION=$(sbt publishLocal | $SED -n -r 's#^.*stainless-scalac-plugin_2.12.13/([^/]+)/poms.*$#\1#p' | head -n1) + STAINLESS_VERSION=$(sbt publishLocal | $SED -n -r 's#^.*stainless-dotty-plugin_3.3.3/([^/]+)/poms.*$#\1#p' | head -n1) echo "Published Stainless version is: $STAINLESS_VERSION" else @@ -158,11 +146,5 @@ mkdir -p "$TEST_DIR" # Stainless Actors are currently disabled: https://github.com/epfl-lara/stainless/issues/970 # "$BIN_DIR/stainless-actors-tests.sh" "$TEST_DIR" "$STAINLESS_VERSION" -if [[ "$ONLY_DOTTY" = false ]]; then - echo "Running bolts test for scalac" - "$BIN_DIR/bolts-tests.sh" "$TEST_DIR" "stainless-scalac" -fi -if [[ "$ONLY_SCALAC" = false ]]; then - echo "Running bolts test for dotty" - "$BIN_DIR/bolts-tests.sh" "$TEST_DIR" "stainless-dotty" -fi \ No newline at end of file +echo "Running bolts test" +"$BIN_DIR/bolts-tests.sh" "$TEST_DIR" "stainless-dotty" \ No newline at end of file diff --git a/bin/launcher-cygwin-noscalaz3.tmpl.sh b/bin/launcher-cygwin-noscalaz3.tmpl.sh index 975a557606..abed0b13f0 100644 --- a/bin/launcher-cygwin-noscalaz3.tmpl.sh +++ b/bin/launcher-cygwin-noscalaz3.tmpl.sh @@ -78,6 +78,7 @@ _canonicalize_file_path() { BASE_DIR="$( dirname "$( realpath "${BASH_SOURCE[0]}" )" )" Z3_DIR="$BASE_DIR/z3" +CVC5_DIR="$BASE_DIR/cvc5" STAINLESS_JAR="$BASE_DIR/lib/{STAINLESS_JAR_BASENAME}" if ! [[ -r "$STAINLESS_JAR" ]]; then @@ -87,4 +88,4 @@ fi # NOTE: $JAVA_OPTS not quoted, as it may be empty! # Cygpath necessary, see https://stackoverflow.com/a/16640483 -exec env PATH="$Z3_DIR:$PATH" java -cp $(cygpath -w $STAINLESS_JAR) $JAVA_OPTS stainless.Main "$@" +exec env PATH="$CVC5_DIR:$Z3_DIR:$PATH" java -cp $(cygpath -w $STAINLESS_JAR) $JAVA_OPTS stainless.Main "$@" diff --git a/bin/launcher-noscalaz3.tmpl.bat b/bin/launcher-noscalaz3.tmpl.bat index 690584f585..6b6879f777 100644 --- a/bin/launcher-noscalaz3.tmpl.bat +++ b/bin/launcher-noscalaz3.tmpl.bat @@ -6,5 +6,6 @@ set script_dir=%CD% popd set PATH=%PATH%;%script_dir%\z3 +set PATH=%PATH%;%script_dir%\cvc5 java -jar %script_dir%\lib\{STAINLESS_JAR_BASENAME} %* \ No newline at end of file diff --git a/bin/launcher-noscalaz3.tmpl.sh b/bin/launcher-noscalaz3.tmpl.sh index 120ffdcfc0..432a7f3c0a 100644 --- a/bin/launcher-noscalaz3.tmpl.sh +++ b/bin/launcher-noscalaz3.tmpl.sh @@ -78,6 +78,7 @@ _canonicalize_file_path() { BASE_DIR="$( dirname "$( realpath "${BASH_SOURCE[0]}" )" )" Z3_DIR="$BASE_DIR/z3" +CVC5_DIR="$BASE_DIR/cvc5" STAINLESS_JAR="$BASE_DIR/lib/{STAINLESS_JAR_BASENAME}" if ! [[ -r "$STAINLESS_JAR" ]]; then @@ -86,4 +87,4 @@ if ! [[ -r "$STAINLESS_JAR" ]]; then fi # NOTE: $JAVA_OPTS not quoted, as it may be empty! -exec env PATH="$Z3_DIR:$PATH" java -cp "$STAINLESS_JAR" $JAVA_OPTS stainless.Main "$@" +exec env PATH="$CVC5_DIR:$Z3_DIR:$PATH" java -cp "$STAINLESS_JAR" $JAVA_OPTS stainless.Main "$@" diff --git a/bin/launcher.tmpl.sh b/bin/launcher.tmpl.sh index f790b65d6f..78b5b95fc3 100644 --- a/bin/launcher.tmpl.sh +++ b/bin/launcher.tmpl.sh @@ -78,6 +78,7 @@ _canonicalize_file_path() { BASE_DIR="$( dirname "$( realpath "${BASH_SOURCE[0]}" )" )" Z3_DIR="$BASE_DIR/z3" +CVC5_DIR="$BASE_DIR/cvc5" STAINLESS_JAR="$BASE_DIR/lib/{STAINLESS_JAR_BASENAME}" SCALAZ3_JAR="$BASE_DIR/lib/{SCALAZ3_JAR_BASENAME}" JARS="$STAINLESS_JAR:$SCALAZ3_JAR" @@ -90,4 +91,4 @@ for JAR in "$STAINLESS_JAR" "$SCALAZ3_JAR"; do done # NOTE: $JAVA_OPTS not quoted, as it may be empty! -exec env PATH="$Z3_DIR:$PATH" java -cp "$JARS" $JAVA_OPTS stainless.Main "$@" +exec env PATH="$CVC5_DIR:$Z3_DIR:$PATH" java -cp "$JARS" $JAVA_OPTS stainless.Main "$@" diff --git a/bin/package-sbt-plugin.sh b/bin/package-sbt-plugin.sh index d9f2c3137d..5f1beeb943 100755 --- a/bin/package-sbt-plugin.sh +++ b/bin/package-sbt-plugin.sh @@ -6,10 +6,12 @@ if [[ $(git diff --stat) != '' ]]; then STAINLESS_VERSION="$STAINLESS_VERSION-SNAPSHOT" fi +SCALA_VERSION="3.3.3" +SCALA_LIB_VERSION="3.3.3" PUBLISHED_SBT_PLUGIN_DIR="$HOME/.ivy2/local/ch.epfl.lara/sbt-stainless/scala_2.12/sbt_1.0/$STAINLESS_VERSION" -PUBLISHED_LIB_DIR="$HOME/.ivy2/local/ch.epfl.lara/stainless-library_2.13/$STAINLESS_VERSION" -PUBLISHED_DOTTY_DIR="$HOME/.ivy2/local/ch.epfl.lara/stainless-dotty-plugin_3.2.0/$STAINLESS_VERSION" -PUBLISHED_SCALAC_DIR="$HOME/.ivy2/local/ch.epfl.lara/stainless-scalac-plugin_3.2.0/$STAINLESS_VERSION" +LIB_SCALA_VERSION_JAR_NAME_PART=$(echo $SCALA_LIB_VERSION | cut -d '.' -f 1) +PUBLISHED_LIB_DIR="$HOME/.ivy2/local/ch.epfl.lara/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART/$STAINLESS_VERSION" +PUBLISHED_DOTTY_DIR="$HOME/.ivy2/local/ch.epfl.lara/stainless-dotty-plugin_$SCALA_VERSION/$STAINLESS_VERSION" OUTPUT="./.stainless-package-sbt-plugin" rm -rf "$OUTPUT" || true @@ -41,24 +43,16 @@ info "$(tput bold)[] Preparing SBT plugin jar..." OUT_SBT_JAR_DIR="$OUTPUT/project/lib" mkdir -p "$OUT_SBT_JAR_DIR" cp "$PUBLISHED_SBT_PLUGIN_DIR/jars/sbt-stainless.jar" "$OUT_SBT_JAR_DIR/sbt-stainless.jar" +OUT_DOTTY_DIR="$OUTPUT/stainless/ch/epfl/lara/stainless-dotty-plugin_$SCALA_VERSION/$STAINLESS_VERSION" +mkdir -p "$OUT_DOTTY_DIR" +cp "$PUBLISHED_DOTTY_DIR/jars/stainless-dotty-plugin_$SCALA_VERSION.jar" "$OUT_DOTTY_DIR/stainless-dotty-plugin_$SCALA_VERSION-$STAINLESS_VERSION.jar" +cp "$PUBLISHED_DOTTY_DIR/poms/stainless-dotty-plugin_$SCALA_VERSION.pom" "$OUT_DOTTY_DIR/stainless-dotty-plugin_$SCALA_VERSION-$STAINLESS_VERSION.pom" info "$(tput bold)[] Preparing Stainless library jar..." -OUT_LIB_DIR="$OUTPUT/stainless/ch/epfl/lara/stainless-library_2.13/$STAINLESS_VERSION" +OUT_LIB_DIR="$OUTPUT/stainless/ch/epfl/lara/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART/$STAINLESS_VERSION" mkdir -p "$OUT_LIB_DIR" -cp "$PUBLISHED_LIB_DIR/srcs/stainless-library_2.13-sources.jar" "$OUT_LIB_DIR/stainless-library_2.13-$STAINLESS_VERSION-sources.jar" -cp "$PUBLISHED_LIB_DIR/poms/stainless-library_2.13.pom" "$OUT_LIB_DIR/stainless-library_2.13-$STAINLESS_VERSION.pom" - -info "$(tput bold)[] Preparing Dotty plugin jar..." -OUT_DOTTY_DIR="$OUTPUT/stainless/ch/epfl/lara/stainless-dotty-plugin_3.2.0/$STAINLESS_VERSION" -mkdir -p "$OUT_DOTTY_DIR" -cp "$PUBLISHED_DOTTY_DIR/jars/stainless-dotty-plugin_3.2.0.jar" "$OUT_DOTTY_DIR/stainless-dotty-plugin_3.2.0-$STAINLESS_VERSION.jar" -cp "$PUBLISHED_DOTTY_DIR/poms/stainless-dotty-plugin_3.2.0.pom" "$OUT_DOTTY_DIR/stainless-dotty-plugin_3.2.0-$STAINLESS_VERSION.pom" - -info "$(tput bold)[] Preparing Scalac plugin jar..." -OUT_SCALAC_DIR="$OUTPUT/stainless/ch/epfl/lara/stainless-scalac-plugin_3.2.0/$STAINLESS_VERSION" -mkdir -p "$OUT_SCALAC_DIR" -cp "$PUBLISHED_SCALAC_DIR/jars/stainless-scalac-plugin_3.2.0.jar" "$OUT_SCALAC_DIR/stainless-scalac-plugin_3.2.0-$STAINLESS_VERSION.jar" -cp "$PUBLISHED_SCALAC_DIR/poms/stainless-scalac-plugin_3.2.0.pom" "$OUT_SCALAC_DIR/stainless-scalac-plugin_3.2.0-$STAINLESS_VERSION.pom" +cp "$PUBLISHED_LIB_DIR/srcs/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART-sources.jar" "$OUT_LIB_DIR/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART-$STAINLESS_VERSION-sources.jar" +cp "$PUBLISHED_LIB_DIR/poms/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART.pom" "$OUT_LIB_DIR/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART-$STAINLESS_VERSION.pom" info "$(tput bold)[] Creating archive..." ARCHIVE="sbt-stainless" diff --git a/bin/package-standalone.sh b/bin/package-standalone.sh index e28378c695..e75643d484 100755 --- a/bin/package-standalone.sh +++ b/bin/package-standalone.sh @@ -14,21 +14,34 @@ if [[ $(git diff --stat) != '' ]]; then STAINLESS_VERSION="$STAINLESS_VERSION-SNAPSHOT" fi -SCALA_VERSION="3.2.0" -Z3_VERSION="4.8.14" +SCALA_VERSION="3.3.3" +LIB_SCALA_VERSION="3.3.3" +LIB_SCALA_VERSION_JAR_NAME_PART=$(echo $LIB_SCALA_VERSION | cut -d '.' -f 1) +Z3_VERSION="4.12.2" +CVC5_VERSION="1.0.8" -SBT_PACKAGE_SCALAC="sbt stainless-scalac-standalone/assembly" SBT_PACKAGE_DOTTY="sbt stainless-dotty-standalone/assembly" -STAINLESS_SCALAC_JAR_PATH="./frontends/stainless-scalac-standalone/target/scala-$SCALA_VERSION/stainless-scalac-standalone-$STAINLESS_VERSION.jar" +SBT_PACKAGE_LIB="sbt stainless-library/package stainless-library/packageSrc" STAINLESS_DOTTY_JAR_PATH="./frontends/stainless-dotty-standalone/target/scala-$SCALA_VERSION/stainless-dotty-standalone-$STAINLESS_VERSION.jar" +STAINLESS_LIB_BIN_JAR_PATH="./frontends/library/target/scala-$LIB_SCALA_VERSION/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART-$STAINLESS_VERSION.jar" +STAINLESS_LIB_SRC_JAR_PATH="./frontends/library/target/scala-$LIB_SCALA_VERSION/stainless-library_$LIB_SCALA_VERSION_JAR_NAME_PART-$STAINLESS_VERSION-sources.jar" SCALAZ3_JAR_LINUX_PATH="./unmanaged/scalaz3-unix-64-3.jar" -SCALAZ3_JAR_MAC_PATH="./unmanaged/scalaz3-mac-64-3.jar" +SCALAZ3_JAR_MAC_X64_PATH="./unmanaged/scalaz3-mac-64-3.jar" Z3_GITHUB_URL="https://github.com/Z3Prover/z3/releases/download/z3-$Z3_VERSION" Z3_LINUX_NAME="z3-$Z3_VERSION-x64-glibc-2.31.zip" -Z3_MAC_NAME="z3-$Z3_VERSION-x64-osx-10.16.zip" +Z3_MAC_ARM64_NAME="z3-$Z3_VERSION-arm64-osx-11.0.zip" +Z3_MAC_X64_NAME="z3-$Z3_VERSION-x64-osx-10.16.zip" Z3_WIN_NAME="z3-$Z3_VERSION-x64-win.zip" +CVC5_GITHUB_URL="https://github.com/cvc5/cvc5/releases/download/cvc5-$CVC5_VERSION" +CVC5_LICENSES_URL="https://raw.githubusercontent.com/cvc5/cvc5/main/licenses/" +CVC5_LICENSES=("minisat-LICENSE" "gpl-3.0.txt" "lgpl-3.0.txt") +CVC5_LINUX_NAME="cvc5-Linux" +CVC5_MAC_ARM64_NAME="cvc5-macOS-arm64" +CVC5_MAC_X64_NAME="cvc5-macOS" +CVC5_WIN_NAME="cvc5-Win64.exe" + LOG="./package-standalone.log" # ----- @@ -52,7 +65,6 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) TMP_DIR="$SCRIPT_DIR/../.stainless-package-standalone" mkdir -p "$TMP_DIR" -STAINLESS_SCALAC_JAR_BASENAME=$(basename "$STAINLESS_SCALAC_JAR_PATH") STAINLESS_DOTTY_JAR_BASENAME=$(basename "$STAINLESS_DOTTY_JAR_PATH") function check_tools { @@ -70,6 +82,13 @@ function check_tools { okay } +function prepare_output_dir { + for PLAT in "linux" "mac-arm64" "mac-x64" "win"; do + TMPD="$TMP_DIR/$PLAT" + (rm -r "$TMPD" 2>/dev/null || true) && mkdir -p "$TMPD" || fail + done +} + function fetch_z3 { local PLAT="$1" local NAME="$2" @@ -89,7 +108,6 @@ function fetch_z3 { else wget -O "$ZIPF" "$Z3_GITHUB_URL/$NAME" 2>> $LOG || fail fi - (rm -r "$TMPD" 2>/dev/null || true) && mkdir -p "$TMPD" || fail unzip -d "$TMPD" "$ZIPF" >> $LOG || fail mkdir -p "$TMPD/z3" >> $LOG || fail @@ -97,6 +115,43 @@ function fetch_z3 { cp "$TMPD/${NAME%.*}/$COPY_FILE" "$TMPD/z3" >> $LOG || fail done + chmod +x "$TMPD/z3/$Z3_EXEC" >> $LOG || fail + + okay +} + +function fetch_cvc5 { + local PLAT="$1" + local NAME="$2" + local BINF="$TMP_DIR/$NAME" + local TMPD="$TMP_DIR/$PLAT" + local CVC5_EXEC + info " - $PLAT" + + if [[ "$PLAT" = "win" ]]; then + CVC5_EXEC="cvc5.exe" + else + CVC5_EXEC="cvc5" + fi + + if [ -f "$BINF" ]; then + info " (Binary already exists, skipping download step.)" + else + wget -O "$BINF" "$CVC5_GITHUB_URL/$NAME" 2>> $LOG || fail + for license in "${CVC5_LICENSES[@]}"; do + wget -O "$TMP_DIR/cvc5_$license" "$CVC5_LICENSES_URL/$license" 2>> $LOG || fail + done + fi + + mkdir -p "$TMPD/cvc5" >> $LOG || fail + mkdir -p "$TMPD/cvc5/licenses" >> $LOG || fail + cp "$BINF" "$TMPD/cvc5/$CVC5_EXEC" >> $LOG || fail + for license in "${CVC5_LICENSES[@]}"; do + cp "$TMP_DIR/cvc5_$license" "$TMPD/cvc5/licenses/$license" + done + + chmod +x "$TMPD/cvc5/$CVC5_EXEC" >> $LOG || fail + okay } @@ -117,8 +172,8 @@ function generate_launcher { chmod +x "$TMPD/stainless.bat" cat "bin/launcher-cygwin-noscalaz3.tmpl.sh" | \ sed "s#{STAINLESS_JAR_BASENAME}#$STAINLESS_JAR_BASENAME#g" \ - > "$TMPD/stainless.sh" - chmod +x "$TMPD/stainless.sh" + > "$TMPD/stainless" + chmod +x "$TMPD/stainless" else local SCRIPT_TMPLT_NAME if [[ "$SCALAZ3_JAR_BASENAME" = "" ]]; then @@ -130,8 +185,8 @@ function generate_launcher { cat "$SCRIPT_TMPLT_NAME" | \ sed "s#{STAINLESS_JAR_BASENAME}#$STAINLESS_JAR_BASENAME#g" | \ sed "s#{SCALAZ3_JAR_BASENAME}#$SCALAZ3_JAR_BASENAME#g" \ - > "$TMPD/stainless.sh" - chmod +x "$TMPD/stainless.sh" + > "$TMPD/stainless" + chmod +x "$TMPD/stainless" fi } @@ -169,13 +224,18 @@ function package { local STAINLESS_SCRIPTS if [[ "$PLAT" = "win" ]]; then - STAINLESS_SCRIPTS=("stainless.bat" "stainless.sh") + STAINLESS_SCRIPTS=("stainless.bat" "stainless") else - STAINLESS_SCRIPTS=("stainless.sh") + STAINLESS_SCRIPTS=("stainless") fi + cp "$STAINLESS_LIB_BIN_JAR_PATH" "$TMPD/lib/stainless-library.jar" >> $LOG || fail + cp "$STAINLESS_LIB_SRC_JAR_PATH" "$TMPD/lib/stainless-library-sources.jar" >> $LOG || fail + cp "bin/stainless-cli" "$TMPD/stainless-cli" >> $LOG || fail + chmod +x "$TMPD/stainless-cli" >> $LOG || fail + cd "$TMPD" && \ - zip "$ZIPF" lib/** z3/** "${STAINLESS_SCRIPTS[@]}" stainless.conf >> $LOG && \ + zip "$ZIPF" lib/** z3/** cvc5/** "${STAINLESS_SCRIPTS[@]}" "stainless-cli" stainless.conf >> $LOG && \ cd - >/dev/null || fail info " Created $FRONTEND archive $ZIPF" @@ -192,28 +252,35 @@ info "$(tput bold)[] Checking required tools..." check_tools info "$(tput bold)[] Assembling fat jar..." -if [[ -f "$STAINLESS_SCALAC_JAR_PATH" && -f "$STAINLESS_DOTTY_JAR_PATH" ]]; then +if [[ -f "$STAINLESS_DOTTY_JAR_PATH" && -f "$STAINLESS_LIB_BIN_JAR_PATH" && -f "$STAINLESS_LIB_SRC_JAR_PATH" ]]; then info " (JAR already exists, skipping sbt assembly step.)" && okay else - $SBT_PACKAGE_SCALAC >> $LOG || fail - $SBT_PACKAGE_DOTTY >> $LOG && okay || fail + $SBT_PACKAGE_DOTTY >> $LOG || fail + $SBT_PACKAGE_LIB >> $LOG && okay || fail fi +prepare_output_dir + info "$(tput bold)[] Downloading Z3 binaries..." fetch_z3 "linux" $Z3_LINUX_NAME -fetch_z3 "mac" $Z3_MAC_NAME +fetch_z3 "mac-arm64" $Z3_MAC_ARM64_NAME +fetch_z3 "mac-x64" $Z3_MAC_X64_NAME fetch_z3 "win" $Z3_WIN_NAME +info "$(tput bold)[] Downloading cvc5 binaries..." +fetch_cvc5 "linux" $CVC5_LINUX_NAME +fetch_cvc5 "mac-arm64" $CVC5_MAC_ARM64_NAME +fetch_cvc5 "mac-x64" $CVC5_MAC_X64_NAME +fetch_cvc5 "win" $CVC5_WIN_NAME + info "$(tput bold)[] Packaging..." -package "linux" $STAINLESS_SCALAC_JAR_PATH $SCALAZ3_JAR_LINUX_PATH "scalac" package "linux" $STAINLESS_DOTTY_JAR_PATH $SCALAZ3_JAR_LINUX_PATH "dotty" -package "mac" $STAINLESS_SCALAC_JAR_PATH $SCALAZ3_JAR_MAC_PATH "scalac" -package "mac" $STAINLESS_DOTTY_JAR_PATH $SCALAZ3_JAR_MAC_PATH "dotty" -package "win" $STAINLESS_SCALAC_JAR_PATH "" "scalac" +package "mac-arm64" $STAINLESS_DOTTY_JAR_PATH "" "dotty" +package "mac-x64" $STAINLESS_DOTTY_JAR_PATH $SCALAZ3_JAR_MAC_X64_PATH "dotty" package "win" $STAINLESS_DOTTY_JAR_PATH "" "dotty" info "$(tput bold)[] Cleaning up..." -# We only clean up the directories used for packaging; we leave the downloaded Z3 binaries. -rm -r "$TMP_DIR/linux" "$TMP_DIR/mac" "$TMP_DIR/win" && okay +# We only clean up the directories used for packaging; we leave the downloaded Z3/cvc5 binaries. +rm -r "$TMP_DIR/linux" "$TMP_DIR/mac-arm64" "$TMP_DIR/mac-x64" "$TMP_DIR/win" && okay info "$(tput bold)Packaging successful." diff --git a/bin/stainless-cli b/bin/stainless-cli new file mode 100644 index 0000000000..3d4ca9d9c2 --- /dev/null +++ b/bin/stainless-cli @@ -0,0 +1,85 @@ +#!/usr/bin/env bash + +### The following section defines `realpath` to resolve symbolic links. +### +### Copyright (c) 2014 Michael Kropat +### Licensed under the terms of the MIT License (https://opensource.org/licenses/MIT) +### Original code at https://github.com/mkropat/sh-realpath + +realpath() { + canonicalize_path "$(resolve_symlinks "$1")" +} + +resolve_symlinks() { + _resolve_symlinks "$1" +} + +_resolve_symlinks() { + _assert_no_path_cycles "$@" || return + + local dir_context path + path=$(readlink -- "$1") + if [ $? -eq 0 ]; then + dir_context=$(dirname -- "$1") + _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" + else + printf '%s\n' "$1" + fi +} + +_prepend_dir_context_if_necessary() { + if [ "$1" = . ]; then + printf '%s\n' "$2" + else + _prepend_path_if_relative "$1" "$2" + fi +} + +_prepend_path_if_relative() { + case "$2" in + /* ) printf '%s\n' "$2" ;; + * ) printf '%s\n' "$1/$2" ;; + esac +} + +_assert_no_path_cycles() { + local target path + + target=$1 + shift + + for path in "$@"; do + if [ "$path" = "$target" ]; then + return 1 + fi + done +} + +canonicalize_path() { + if [ -d "$1" ]; then + _canonicalize_dir_path "$1" + else + _canonicalize_file_path "$1" + fi +} + +_canonicalize_dir_path() { + (cd "$1" 2>/dev/null && pwd -P) +} + +_canonicalize_file_path() { + local dir file + dir=$(dirname -- "$1") + file=$(basename -- "$1") + (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") +} + +### end of realpath code + +BASE_DIR="$( dirname "$( realpath "${BASH_SOURCE[0]}" )" )" +JARS="$BASE_DIR/lib" + +# NOTE: $JAVA_OPTS not quoted, as it may be empty! +TORUN="scala-cli --jar ${JARS}/stainless-library.jar ${JARS}/stainless-library-sources.jar $@" +echo $TORUN +${TORUN} diff --git a/build.sbt b/build.sbt index 2fab0164c1..3bdd0e1a18 100644 --- a/build.sbt +++ b/build.sbt @@ -22,8 +22,11 @@ val osArch = System.getProperty("sun.arch.data.model") val circeVersion = "0.14.1" -lazy val nTestParallelism = { - val p = System.getProperty("test-parallelism") +// The number of test suites (e.g. VerificationSuite, UncheckedSuite) run in parallel +// Note that this does not dictate the parallelism per test case (e.g. verification/valid/AbstractPost.scala, +// verification/valid/AbstractRefinementMap$.scala) which is instead set with -Dtestcase-parallelism=<...> +lazy val nTestSuiteParallelism = { + val p = System.getProperty("testsuite-parallelism") if (p ne null) { try { p.toInt @@ -36,12 +39,11 @@ lazy val nTestParallelism = { } // The Scala version with which Stainless is compiled. -val stainlessScalaVersion = "3.2.0" -// Stainless supports Scala 2.13 and Scala 3.2 programs. -val frontendScalacVersion = "2.13.10" +val stainlessScalaVersion = "3.3.3" +// Stainless supports Scala 3.3 programs. val frontendDottyVersion = stainlessScalaVersion -// The Stainless libraries use Scala 2.13, but they are compatible with Scala 3.2 as well. -val stainlessLibScalaVersion = frontendScalacVersion +// The Stainless libraries use Scala 2.13 and Scala 3.3, and is compatible only with Scala 3.3. +val stainlessLibScalaVersion = stainlessScalaVersion scalaVersion := stainlessScalaVersion @@ -73,7 +75,6 @@ lazy val baseSettings: Seq[Setting[_]] = Seq( lazy val artifactSettings: Seq[Setting[_]] = baseSettings ++ Seq( scalaVersion := stainlessScalaVersion, - buildInfoPackage := "stainless", buildInfoKeys := stainlessBuildInfoKeys, buildInfoOptions := Seq(BuildInfoOption.BuildTime), @@ -256,12 +257,12 @@ def commonFrontendSettings(compilerVersion: String): Seq[Setting[_]] = Defaults. Seq(main) }) ++ inConfig(IntegrationTest)(Defaults.testTasks ++ Seq( - logBuffered := (nTestParallelism > 1), - parallelExecution := (nTestParallelism > 1), + logBuffered := (nTestSuiteParallelism > 1), + parallelExecution := (nTestSuiteParallelism > 1), )) Global / concurrentRestrictions := Seq( - Tags.limit(Tags.Test, nTestParallelism) + Tags.limit(Tags.Test, nTestSuiteParallelism) ) val scriptSettings: Seq[Setting[_]] = Seq( @@ -275,7 +276,7 @@ val scriptSettings: Seq[Setting[_]] = Seq( def ghProject(repo: String, version: String) = RootProject(uri(s"${repo}#${version}")) // lazy val inox = RootProject(file("../inox")) -lazy val inox = ghProject("https://github.com/epfl-lara/inox.git", "ec6112f8b3e5910b78518a367f1f6b55ee0e3d81") +lazy val inox = ghProject("https://github.com/epfl-lara/inox.git", "3b02dcf4308f9e8d74ea82304bd651be8e93517f") lazy val cafebabe = ghProject("https://github.com/epfl-lara/cafebabe.git", "616e639b34379e12b8ac202849de3ebbbd0848bc") // Allow integration test to use facilities from regular tests @@ -317,56 +318,6 @@ lazy val `stainless-algebra` = (project in file("frontends") / "algebra") ) .dependsOn(`stainless-library`) -lazy val `stainless-scalac` = (project in file("frontends") / "scalac") - .enablePlugins(JavaAppPackaging) - .enablePlugins(BuildInfoPlugin) - .settings(commonSettings, commonFrontendSettings(frontendScalacVersion)) - .settings(scriptSettings, assemblySettings) - .settings(noPublishSettings) - .settings( - name := "stainless-scalac", - frontendClass := "scalac.ScalaCompiler", - libraryDependencies += "org.scala-lang" % "scala-compiler" % frontendScalacVersion, - buildInfoKeys ++= Seq[BuildInfoKey]("useJavaClassPath" -> false), - // We include Scala library to be certain we also include scala-parser-combinators (which is not shipped with the Scala std library) - assemblyPackageScala / assembleArtifact := true, - assembly / assemblyExcludedJars := { - val cp = (assembly / fullClasspath).value - // Don't include scalaz3 dependency because it is OS dependent - cp filter {_.data.getName.startsWith("scalaz3")} - }, - ) - .dependsOn(`stainless-core`) - .dependsOn(inox % "test->test;it->test,it") - .configs(IntegrationTest) - -// Following https://github.com/sbt/sbt-assembly#q-despite-the-concerned-friends-i-still-want-publish-fat-jars-what-advice-do-you-have -lazy val `stainless-scalac-plugin` = (project in file("frontends") / "stainless-scalac-plugin") - .settings(artifactSettings, publishMavenSettings, assemblySettings) - .settings( - name := "stainless-scalac-plugin", - crossVersion := CrossVersion.full, // because compiler api is not binary compatible - Compile / packageBin := (`stainless-scalac` / Compile / assembly).value - ) - -lazy val `stainless-scalac-standalone` = (project in file("frontends") / "stainless-scalac-standalone") - .enablePlugins(BuildInfoPlugin) - .enablePlugins(JavaAppPackaging) - .settings(artifactSettings, assemblySettings) - .settings( - name := "stainless-scalac-standalone", - buildInfoKeys ++= Seq[BuildInfoKey]("useJavaClassPath" -> true), - assembly / mainClass := Some("stainless.Main"), - assembly / assemblyJarName := (name.value + "-" + version.value + ".jar"), - Runtime / unmanagedJars := (`stainless-scalac` / Runtime / unmanagedJars).value, - assemblyPackageScala / assembleArtifact := true, - assembly / assemblyExcludedJars := { - val cp = (assembly / fullClasspath).value - cp filter {_.data.getName.startsWith("scalaz3")} - }, - ) - .dependsOn(`stainless-scalac`) - lazy val `stainless-dotty` = (project in file("frontends/dotty")) .enablePlugins(JavaAppPackaging) .enablePlugins(BuildInfoPlugin) @@ -430,7 +381,7 @@ lazy val `sbt-stainless` = (project in file("sbt-plugin")) buildInfoPackage := "ch.epfl.lara.sbt.stainless", buildInfoKeys ++= Seq[BuildInfoKey]( BuildInfoKey.map(version) { case (_, v) => "stainlessVersion" -> v }, - "supportedScalaVersions" -> Seq(frontendScalacVersion, frontendDottyVersion), + "supportedScalaVersions" -> Seq(frontendDottyVersion), "stainlessScalaVersion" -> stainlessScalaVersion, "stainlessLibScalaVersion" -> stainlessLibScalaVersion, ), @@ -440,7 +391,6 @@ lazy val `sbt-stainless` = (project in file("sbt-plugin")) scriptedLaunchOpts ++= Seq( "-Xmx768m", "-Dplugin.version=" + version.value, - "-Dscalac.version=" + frontendScalacVersion, "-Ddotty.version=" + frontendDottyVersion ), scriptedBufferLog := false, @@ -448,7 +398,6 @@ lazy val `sbt-stainless` = (project in file("sbt-plugin")) publishLocal.value (`stainless-library` / update).value (`stainless-library` / publishLocal).value - (`stainless-scalac-plugin` / publishLocal).value (`stainless-dotty-plugin` / publishLocal).value } ) @@ -459,8 +408,8 @@ lazy val root = (project in file(".")) .settings( Compile / sourcesInBase := false, ) - .dependsOn(`stainless-scalac`, `stainless-library`, `stainless-dotty`, `sbt-stainless`) - .aggregate(`stainless-core`, `stainless-library`, `stainless-scalac`, `stainless-dotty`, `sbt-stainless`, `stainless-scalac-plugin`, `stainless-dotty-plugin`) + .dependsOn(`stainless-library`, `stainless-dotty`, `sbt-stainless`) + .aggregate(`stainless-core`, `stainless-library`, `stainless-dotty`, `sbt-stainless`, `stainless-dotty-plugin`) def commonPublishSettings = Seq( bintrayOrganization := Some("epfl-lara") @@ -476,4 +425,4 @@ def publishMavenSettings = commonPublishSettings ++ Seq( bintrayRepository := "maven" ) -// FIXME assembly should be disabled at the top level, but isn't \ No newline at end of file +// FIXME assembly should be disabled at the top level, but isn't diff --git a/core/src/main/scala/stainless/MainHelpers.scala b/core/src/main/scala/stainless/MainHelpers.scala index db21ee8066..9b4582a8ad 100644 --- a/core/src/main/scala/stainless/MainHelpers.scala +++ b/core/src/main/scala/stainless/MainHelpers.scala @@ -50,8 +50,7 @@ trait MainHelpers extends inox.MainHelpers { self => verification.optVCCache -> Description(Verification, "Enable caching of verification conditions"), verification.optCoq -> Description(Verification, "Transform the program into a Coq program, and let Coq generate subgoals automatically"), verification.optAdmitAll -> Description(Verification, "Admit all obligations when translated into a coq program"), - verification.optStrictArithmetic -> Description(Verification, - s"Check arithmetic operations for unintended behavior and overflows (default: true)"), + verification.optStrictArithmetic -> Description(Verification, "Check arithmetic operations for unintended behavior and overflows"), verification.optAdmitVCs -> Description(Verification, "Admit all verification conditions"), verification.optSimplifier -> Description(Verification, "Select which simplifier to use for VC simplification\n" + "Available:\n" + @@ -277,4 +276,3 @@ trait MainHelpers extends inox.MainHelpers { self => JsonUtils.writeFile(new File(file), json) } } - diff --git a/core/src/main/scala/stainless/Report.scala b/core/src/main/scala/stainless/Report.scala index a06bd9bf2c..3a4fd71aa1 100644 --- a/core/src/main/scala/stainless/Report.scala +++ b/core/src/main/scala/stainless/Report.scala @@ -33,7 +33,8 @@ case class RecordRow( pos: Position, level: Level.Type, extra: Seq[String], - time: Long + time: Long, + smtLibId: Option[Int] ) /** @@ -77,12 +78,24 @@ trait AbstractReport[SelfType <: AbstractReport[SelfType]] { self: SelfType => /** Filter, sort & process rows. */ private def processRows(full: Boolean)(using ctx: inox.Context): Seq[Row] = { val printUniqueName = ctx.options.findOptionOrDefault(inox.ast.optPrintUniqueIds) - for { - RecordRow(id, pos, level, extra, time) <- annotatedRows.sortBy(r => r.id -> r.pos) + val rows = for { + RecordRow(id, pos, level, extra, time, smtLibId) <- annotatedRows.sortBy(r => r.id -> r.pos) if full || level != Level.Normal name = if (printUniqueName) id.uniqueName else id.name - contents = Position.smartPos(pos) +: (name +: (extra :+ f"${time / 1000d}%3.1f")) + t = smtLibId match { + case Some(v) => f"smt-lib-$v" + case None => "" + } + contents = Position.smartPos(pos) +: (name +: ((extra :+ f"${time / 1000d}%3.1f" ) :+ t)) } yield Row(contents map { str => Cell(withColor(str, level)) }) + + // Delete the last cell if all are empty (i.e., the debug smt flag is false, and so smt-lib-id is empty) + val colorRegex = "\u001B\\[[;\\d]*m".r + if rows.forall(r => colorRegex.replaceAllIn(r.cells.last.vString, "").isBlank()) then { + rows.map(r => Row(r.cells.dropRight(1))) + } else { + rows + } } private def withColor(str: String, level: Level)(using inox.Context): String = @@ -105,7 +118,7 @@ trait AbstractReport[SelfType <: AbstractReport[SelfType]] { self: SelfType => def hasError(identifier: Option[Identifier])(using inox.Context): Boolean = identifier match { case None => false case Some(i) => annotatedRows.exists(elem => elem match { - case RecordRow(id, pos, level, extra, time) => { + case RecordRow(id, pos, level, extra, time, smtLibId) => { (level == Level.Error && id == i) } }) @@ -114,7 +127,7 @@ trait AbstractReport[SelfType <: AbstractReport[SelfType]] { self: SelfType => def hasUnknown(identifier: Option[Identifier])(using inox.Context): Boolean = identifier match { case None => false case Some(i) => annotatedRows.exists(elem => elem match { - case RecordRow(id, pos, level, extra, time) => level == Level.Warning && id == i + case RecordRow(id, pos, level, extra, time, smtLibId) => level == Level.Warning && id == i }) } diff --git a/core/src/main/scala/stainless/ast/ExprOps.scala b/core/src/main/scala/stainless/ast/ExprOps.scala index 7513d09d6c..c24fd7a14f 100644 --- a/core/src/main/scala/stainless/ast/ExprOps.scala +++ b/core/src/main/scala/stainless/ast/ExprOps.scala @@ -412,13 +412,9 @@ class ExprOps(override val trees: Trees) extends inox.ast.ExprOps(trees) { self * Freshening of local variables * ============================= */ - protected class StainlessFreshener(override val s: self.trees.type, - override val t: self.trees.type, - freshenChooses: Boolean) + protected class StainlessFreshener(freshenChooses: Boolean) extends Freshener(freshenChooses) with transformers.Transformer { - def this(freshenChooses: Boolean) = this(self.trees, self.trees, freshenChooses) - override def transformCase(cse: MatchCase, env: Env): MatchCase = { val MatchCase(pat, guard, rhs) = cse val (newPat, newEnv) = transformAndGetEnv(pat, env) diff --git a/core/src/main/scala/stainless/ast/SymbolOps.scala b/core/src/main/scala/stainless/ast/SymbolOps.scala index 7353bc88ac..f56ad62c29 100644 --- a/core/src/main/scala/stainless/ast/SymbolOps.scala +++ b/core/src/main/scala/stainless/ast/SymbolOps.scala @@ -172,7 +172,7 @@ trait SymbolOps extends inox.ast.SymbolOps with TypeOps { self => case Some(g) => patCond withCond replaceFromSymbols(map, g) case None => patCond } - val newRhs = replaceFromSymbols(map, cse.rhs).copiedFrom(cse.rhs) + val newRhs = replaceFromSymbols(map, cse.rhs) (realCond.toClause.copiedFrom(cse), newRhs, cse) } diff --git a/core/src/main/scala/stainless/equivchk/EquivalenceChecker.scala b/core/src/main/scala/stainless/equivchk/EquivalenceChecker.scala index 15d685cb34..226c5da46a 100644 --- a/core/src/main/scala/stainless/equivchk/EquivalenceChecker.scala +++ b/core/src/main/scala/stainless/equivchk/EquivalenceChecker.scala @@ -90,7 +90,7 @@ class EquivalenceChecker(override val trees: Trees, enum Classification { case Valid(directModel: Identifier) - case Invalid(ctex: Seq[Seq[(ValDef, Expr)]]) + case Invalid(ctex: Seq[Ctex]) case Unknown } @@ -111,19 +111,26 @@ class EquivalenceChecker(override val trees: Trees, unequivalent: Map[Identifier, UnequivalentData], unsafe: Map[Identifier, UnsafeData], // Candidates that will need to be manually inspected... - unknowns: Map[Identifier, UnknownData], + unknownsEquivalence: Map[Identifier, UnknownEquivalenceData], + unknownsSafety: Map[Identifier, UnknownSafetyData], // Incorrect signature wrongs: Set[Identifier], weights: Map[Identifier, Int]) - case class Ctex(mapping: Seq[(ValDef, Expr)], expected: Expr, got: Expr) + case class Eval(expected: Expr, got: Expr) + case class Ctex(mapping: Seq[(ValDef, Expr)], eval: Option[Eval]) case class ValidData(path: Seq[Identifier], solvingInfo: SolvingInfo) // The list of counter-examples can be empty; the candidate is still invalid but a ctex could not be extracted // If the solvingInfo is None, the candidate has been pruned. - case class UnequivalentData(ctexs: Seq[Seq[(ValDef, Expr)]], solvingInfo: Option[SolvingInfo]) + case class UnequivalentData(ctexs: Seq[Ctex], solvingInfo: Option[SolvingInfo]) + case class UnsafeData(self: Seq[UnsafeCtex], auxiliaries: Map[Identifier, Seq[UnsafeCtex]]) case class UnsafeCtex(kind: VCKind, pos: Position, ctex: Option[Seq[(ValDef, Expr)]], solvingInfo: SolvingInfo) - case class UnknownData(solvingInfo: SolvingInfo) + case class UnknownEquivalenceData(solvingInfo: SolvingInfo) + + case class UnknownSafetyData(self: Seq[UnknownSafetyVC], auxiliaries: Map[Identifier, Seq[UnknownSafetyVC]]) + case class UnknownSafetyVC(kind: VCKind, pos: Position, solvingInfo: SolvingInfo) + // Note: fromCache and trivial are only relevant for valid candidates case class SolvingInfo(time: Long, solverName: Option[String], fromCache: Boolean, trivial: Boolean) { def withAddedTime(extra: Long): SolvingInfo = copy(time = time + extra) @@ -131,7 +138,7 @@ class EquivalenceChecker(override val trees: Trees, def getCurrentResults(): Results = { val equiv = clusters.map { case (model, clst) => model -> clst.toSet }.toMap - Results(equiv, valid.toMap, unequivalent.toMap, unsafe.toMap, unknowns.toMap, signatureMismatch.toSet, models.toMap) + Results(equiv, valid.toMap, unequivalent.toMap, unsafe.toMap, unknownsEquivalence.toMap, unknownsSafety.toMap, signatureMismatch.toSet, models.toMap) } //endregion @@ -235,7 +242,8 @@ class EquivalenceChecker(override val trees: Trees, private val valid = mutable.Map.empty[Identifier, ValidData] private val unequivalent = mutable.Map.empty[Identifier, UnequivalentData] private val unsafe = mutable.Map.empty[Identifier, UnsafeData] - private val unknowns = mutable.LinkedHashMap.empty[Identifier, UnknownData] + private val unknownsEquivalence = mutable.LinkedHashMap.empty[Identifier, UnknownEquivalenceData] + private val unknownsSafety = mutable.LinkedHashMap.empty[Identifier, UnknownSafetyData] private val signatureMismatch = mutable.ArrayBuffer.empty[Identifier] private val clusters = mutable.Map.empty[Identifier, mutable.ArrayBuffer[Identifier]] @@ -259,32 +267,36 @@ class EquivalenceChecker(override val trees: Trees, val ordCtexs = ctexOrderedArguments(fun, pr)(counterex.vars) val fd = symbols.functions(fun) val ctexVars = ordCtexs.map(ctex => fd.params.zip(ctex)) - if (allCandidates.contains(fun)) { - remainingCandidates -= fun - ordCtexs.foreach(addCtex) - val currUnsafeData = unsafe.getOrElse(fun, UnsafeData(Seq.empty, Map.empty)) - val newUnsafeData = currUnsafeData.copy(self = currUnsafeData.self :+ UnsafeCtex(vc.kind, vc.getPos, ctexVars, extractSolvingInfo(analysis, fun, Seq.empty))) - unsafe += fun -> newUnsafeData - Some(Set(fun)) - } else candidatesCallee.get(fun) match { - case Some(cands) => - // This means this erroneous `fun` is called by all candidates in `cands`. - // Note: we do not add the ctexs with `addCtex`, because counterex corresponds to the signature of `fun` not necessarily `cand` + reportHelper(pr)(analysis, vc) { + fun => + ordCtexs.foreach(addCtex) + val currUnsafeData = unsafe.getOrElse(fun, UnsafeData(Seq.empty, Map.empty)) + val newUnsafeData = currUnsafeData.copy(self = currUnsafeData.self :+ UnsafeCtex(vc.kind, vc.getPos, ctexVars, extractSolvingInfo(analysis, fun, Seq.empty))) + unsafe += fun -> newUnsafeData + } { cand => + // Note: we do not add the ctexs with `addCtex`, because counterex corresponds to the signature of `fun` not necessarily `cand` + val currUnsafeData = unsafe.getOrElse(cand, UnsafeData(Seq.empty, Map.empty)) + val currUnsafeCtexs = currUnsafeData.auxiliaries.getOrElse(fun, Seq.empty) + val newUnsafeCtexs = currUnsafeCtexs :+ UnsafeCtex(vc.kind, vc.getPos, ctexVars, extractSolvingInfo(analysis, fun, Seq.empty)) + val newUnsafeData = currUnsafeData.copy(auxiliaries = currUnsafeData.auxiliaries + (fun -> newUnsafeCtexs)) + unsafe += cand -> newUnsafeData + } + } - // `cands` should be of size 1 because a function called by multiple candidates must be either a library fn or - // a provided function which are all assumed to be correct. - cands.foreach { cand => - remainingCandidates -= cand - val currUnsafeData = unsafe.getOrElse(cand, UnsafeData(Seq.empty, Map.empty)) - val currUnsafeCtexs = currUnsafeData.auxiliaries.getOrElse(fun, Seq.empty) - val newUnsafeCtexs = currUnsafeCtexs :+ UnsafeCtex(vc.kind, vc.getPos, ctexVars, extractSolvingInfo(analysis, fun, Seq.empty)) - val newUnsafeData = currUnsafeData.copy(auxiliaries = currUnsafeData.auxiliaries + (fun -> newUnsafeCtexs)) - unsafe += cand -> newUnsafeData - } - Some(cands) - case None => - // Nobody knows about this function - None + def reportUnknown(pr: StainlessProgram)(analysis: VerificationAnalysis, vc: VC[pr.trees.type]): Option[Set[Identifier]] = { + val fun = vc.fid + reportHelper(pr)(analysis, vc) { + fun => + remainingCandidates -= fun + val currUnknownData = unknownsSafety.getOrElse(fun, UnknownSafetyData(Seq.empty, Map.empty)) + val newUnsafeData = currUnknownData.copy(self = currUnknownData.self :+ UnknownSafetyVC(vc.kind, vc.getPos, extractSolvingInfo(analysis, fun, Seq.empty))) + unknownsSafety += fun -> newUnsafeData + } { cand => + val currUnknownData = unknownsSafety.getOrElse(cand, UnknownSafetyData(Seq.empty, Map.empty)) + val currUnknownVCs = currUnknownData.auxiliaries.getOrElse(fun, Seq.empty) + val newUnknownVCs = currUnknownVCs :+ UnknownSafetyVC(vc.kind, vc.getPos, extractSolvingInfo(analysis, fun, Seq.empty)) + val newUnknownData = currUnknownData.copy(auxiliaries = currUnknownData.auxiliaries + (fun -> newUnknownVCs)) + unknownsSafety += cand -> newUnknownData } } @@ -304,10 +316,10 @@ class EquivalenceChecker(override val trees: Trees, case EvalCheck.Ok => picked = Some(candId) case EvalCheck.FailsTest(testId, sampleIx, ctex) => - unequivalent += candId -> UnequivalentData(Seq(ctex.mapping), None) + unequivalent += candId -> UnequivalentData(Seq(ctex), None) pruned += candId -> PruningReason.ByTest(testId, sampleIx, ctex) case EvalCheck.FailsCtex(ctex) => - unequivalent += candId -> UnequivalentData(Seq(ctex.mapping), None) + unequivalent += candId -> UnequivalentData(Seq(ctex), None) pruned += candId -> PruningReason.ByPreviousCtex(ctex) } } else { @@ -327,16 +339,18 @@ class EquivalenceChecker(override val trees: Trees, examinationState = ExaminationState.Examining(candId, RoundState(topN.head, topN.tail, strat, EquivLemmas.ToGenerate, 0L)) NextExamination.NewCandidate(candId, topN.head, strat, pruned.toMap) } else { + // This candidate has been tested with all models, so put it pack into unknowns + unknownsEquivalence += candId -> UnknownEquivalenceData(SolvingInfo(0L, None, false, false)) pickNextExamination() match { case d@NextExamination.Done(_, _) => d.copy(pruned = pruned.toMap ++ d.pruned) case nc@NextExamination.NewCandidate(_, _, _, _) => nc.copy(pruned = pruned.toMap ++ nc.pruned) } } case None => - if (unknowns.nonEmpty && unknowns.size < nbExaminedCandidates) { - nbExaminedCandidates = unknowns.size - remainingCandidates ++= unknowns.keys - unknowns.clear() + if (unknownsEquivalence.nonEmpty && unknownsEquivalence.size < nbExaminedCandidates) { + nbExaminedCandidates = unknownsEquivalence.size + remainingCandidates ++= unknownsEquivalence.keys + unknownsEquivalence.clear() pickNextExamination() match { case d@NextExamination.Done(_, _) => d.copy(pruned = pruned.toMap ++ d.pruned) case nc@NextExamination.NewCandidate(_, _, _, _) => nc.copy(pruned = pruned.toMap ++ nc.pruned) @@ -444,7 +458,7 @@ class EquivalenceChecker(override val trees: Trees, } else { // oh no, manual inspection incoming examinationState = ExaminationState.PickNext - unknowns += cand -> UnknownData(solvingInfo.withAddedTime(currCumulativeSolvingTime)) + unknownsEquivalence += cand -> UnknownEquivalenceData(solvingInfo.withAddedTime(currCumulativeSolvingTime)) RoundConclusion.CandidateClassified(cand, Classification.Unknown, invalidPairs) } } @@ -453,7 +467,7 @@ class EquivalenceChecker(override val trees: Trees, val report = analysis.toReport val allCtexs = analysis.vrs.collect { - case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _)) => + case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _, _)) => ctexOrderedArguments(vc.fid, model.program)(model.vars).map(vc.fid -> _) }.flatten.groupMap(_._1)(_._2) @@ -469,7 +483,10 @@ class EquivalenceChecker(override val trees: Trees, val candFd = symbols.functions(cand) // Take all ctex for `cand`, `eqLemma` and `proof` val ctexOrderedArgs = (Seq(cand, eqLemma) ++ proof.toSeq).flatMap(id => allCtexs.getOrElse(id, Seq.empty)) - val ctexsMap = ctexOrderedArgs.map(ctex => candFd.params.zip(ctex)) + val ctexsMap = ctexOrderedArgs.map { ctex => + val eval = evalOn(symbols.functions(model), candFd, ctex) + Ctex(candFd.params.zip(ctex), eval) + } unequivalent += cand -> UnequivalentData(ctexsMap, Some(solvingInfo.withAddedTime(currCumulativeSolvingTime))) examinationState = ExaminationState.PickNext RoundConclusion.CandidateClassified(cand, Classification.Invalid(ctexsMap), Set.empty) @@ -549,6 +566,7 @@ class EquivalenceChecker(override val trees: Trees, val newParamTps = eqLemma0.tparams.map { tparam => tparam.tp } val newParamVars = eqLemma0.params.map { param => param.toVariable } + val specsModel = if conf.topLevel then symbols.functions(allModels.head) else conf.model val subst = { val nweParamVarsPermuted = conf.strat.order match { case EquivCheckOrder.ModelFirst => @@ -558,12 +576,12 @@ class EquivalenceChecker(override val trees: Trees, // f1 = candidate, f2 = model: we need to "undo" the ordering perm.m2c.map(newParamVars) } - (conf.model.params.map(_.id) zip nweParamVarsPermuted).toMap + (specsModel.params.map(_.id) zip nweParamVarsPermuted).toMap } - val tsubst = (conf.model.tparams zip newParamTps).map { case (tparam, targ) => tparam.tp.id -> targ }.toMap + val tsubst = (specsModel.tparams zip newParamTps).map { case (tparam, targ) => tparam.tp.id -> targ }.toMap val specializer = new Specializer(eqLemma0, eqLemma0.id, tsubst, subst, Map()) - val specs = BodyWithSpecs(conf.model.fullBody).specs.filter(s => s.kind == LetKind || s.kind == PreconditionKind) + val specs = BodyWithSpecs(specsModel.fullBody).specs.filter(s => s.kind == LetKind || s.kind == PreconditionKind) val pre = specs.map { case Precondition(cond) => Precondition(specializer.transform(cond)) case LetInSpec(vd, expr) => LetInSpec(vd, specializer.transform(expr)) @@ -622,8 +640,8 @@ class EquivalenceChecker(override val trees: Trees, val (samples, instParams) = tests(id) findMap(samples.zipWithIndex) { case (arg, sampleIx) => passTestSample(arg, instParams).map(_ -> sampleIx) - }.map { case ((evalArgs, expected, got), sampleIx) => - EvalCheck.FailsTest(id, sampleIx, Ctex(cand.params.zip(evalArgs), expected, got)) + }.map { case ((evalArgs, eval), sampleIx) => + EvalCheck.FailsTest(id, sampleIx, Ctex(cand.params.zip(evalArgs), Some(eval))) } } @@ -638,9 +656,10 @@ class EquivalenceChecker(override val trees: Trees, loop(tests.keys.toSeq) } - def passTestSample(arg: Expr, instTparams: Seq[Type]): Option[(Seq[Expr], Expr, Expr)] = { + def passTestSample(arg: Expr, instTparams: Seq[Type]): Option[(Seq[Expr], Eval)] = { + val evaluator = mkEvaluator() val evalArg = try { - evaluate(arg) match { + evaluator.eval(arg) match { case inox.evaluators.EvaluationResults.Successful(evalArg) => evalArg case _ => return None // If we cannot evaluate the argument, then we consider this test to be "successful" @@ -661,10 +680,10 @@ class EquivalenceChecker(override val trees: Trees, val invocationCand = FunctionInvocation(cand.id, instTparams, argsSplit) val invocationModel = FunctionInvocation(allModels.head, instTparams, argsSplit) // any model will do try { - (evaluate(invocationCand), evaluate(invocationModel)) match { + (evaluator.eval(invocationCand), evaluator.eval(invocationModel)) match { case (inox.evaluators.EvaluationResults.Successful(output), inox.evaluators.EvaluationResults.Successful(expected)) => if (output == expected) None - else Some((argsSplit, expected, output)) + else Some((argsSplit, Eval(expected, output))) case _ => None } } catch { @@ -672,29 +691,6 @@ class EquivalenceChecker(override val trees: Trees, } } - def evaluate(expr: Expr) = { - val syms: symbols.type = symbols - type ProgramType = inox.Program {val trees: self.trees.type; val symbols: syms.type} - val prog: ProgramType = inox.Program(self.trees)(syms) - val sem = new inox.Semantics { - val trees: self.trees.type = self.trees - val symbols: syms.type = syms - val program: prog.type = prog - - def createEvaluator(ctx: inox.Context) = ??? - - def createSolver(ctx: inox.Context) = ??? - } - class EvalImpl(override val program: prog.type, override val context: inox.Context) - (using override val semantics: sem.type) - extends evaluators.RecursiveEvaluator(program, context) - with inox.evaluators.HasDefaultGlobalContext - with inox.evaluators.HasDefaultRecContext - - val evaluator = new EvalImpl(prog, self.context)(using sem) - evaluator.eval(expr) - } - val permutation = ArgPermutation(model.params.indices) // No permutation for top-level model and candidate passAllTests .orElse(evalCheckCtexOnly(model, cand, permutation).map(EvalCheck.FailsCtex.apply)) @@ -706,7 +702,7 @@ class EquivalenceChecker(override val trees: Trees, assert(areSignaturesCompatibleModuloPerm(model, cand, candPerm)) val subst = TyParamSubst(IntegerType(), i => Some(IntegerLiteral(i))) - def passUnordCtex(ctex: UnordCtex): Option[(Seq[Expr], Expr, Expr)] = { + def passUnordCtex(ctex: UnordCtex): Option[(Seq[Expr], Eval)] = { // From `ctex`, generate all possible ordered permutations of args according to the types // If the type multiplicity is 1 for all params, then there is only one ordered ctex possible val ctexSeq = ctex.args.toSeq @@ -724,57 +720,69 @@ class EquivalenceChecker(override val trees: Trees, tpeIxs(vdTpeInst) = tpeIxs(vdTpeInst) + 1 arg } - passOrdCtex(ordArgs).map { case (exp, got) => (ordArgs, exp, got) } + passOrdCtex(ordArgs).map(ordArgs -> _) } } - def passOrdCtex(args: Seq[Expr]): Option[(Expr, Expr)] = { - val syms: symbols.type = symbols - type ProgramType = inox.Program {val trees: self.trees.type; val symbols: syms.type} - val prog: ProgramType = inox.Program(self.trees)(syms) - val sem = new inox.Semantics { - val trees: prog.trees.type = prog.trees - val symbols: syms.type = prog.symbols - val program: prog.type = prog - - def createEvaluator(ctx: inox.Context) = ??? - - def createSolver(ctx: inox.Context) = ??? - } - class EvalImpl(override val program: prog.type, override val context: inox.Context) - (using override val semantics: sem.type) - extends evaluators.RecursiveEvaluator(program, context) - with inox.evaluators.HasDefaultGlobalContext - with inox.evaluators.HasDefaultRecContext { - override lazy val maxSteps: Int = maxStepsEval - } - val evaluator = new EvalImpl(prog, self.context)(using sem) - - val tparams = model.tparams.map(_ => IntegerType()) - val invocationModel = evaluator.program.trees.FunctionInvocation(model.id, tparams, args) - val invocationCand = evaluator.program.trees.FunctionInvocation(cand.id, tparams, candPerm.reverse.m2c.map(args)) - try { - (evaluator.eval(invocationCand), evaluator.eval(invocationModel)) match { - case (inox.evaluators.EvaluationResults.Successful(output), inox.evaluators.EvaluationResults.Successful(expected)) => - if (output == expected) None - else Some((expected, output)) - case _ => None - } - } catch { - case NonFatal(_) => None - } - } + // Returns an Option of (expected, got) if evaluation succeeds and got is different from expected + def passOrdCtex(args: Seq[Expr]): Option[Eval] = + evalOn(model, cand, args, candPerm) + .filter { case Eval(expected, got) => expected != got } // Substitute tparams with IntegerType() val argsTpe = model.params.map(vd => substTypeParams(model.tparams, vd.tpe)(using subst)) val unordSig = UnordSig(argsTpe.groupMapReduce(identity)(_ => 1)(_ + _)) val ctexs = ctexsDb.getOrElse(unordSig, mutable.ArrayBuffer.empty) findMap(ctexs.toSeq)(passUnordCtex) - .map { case (ctex, expected, got) => + .map { case (ctex, eval) => // ctex is ordered according to the model, so we need to reorder cand according to the permutation val candReorg = candPerm.m2c.map(cand.params) - Ctex(candReorg.zip(ctex), expected, got) + Ctex(candReorg.zip(ctex), Some(eval)) + } + } + + // Evaluate `model` and `cand` with the given `args` and whose candidate argument permutation is given by `candPerm`. + // Note: this expects `args` to have generic type substituted to integers, as it is done in `ctexOrderedArguments`. + private def evalOn(model: FunDef, cand: FunDef, args: Seq[Expr], candPerm: ArgPermutation): Option[Eval] = { + val evaluator = mkEvaluator() + val tparams = model.tparams.map(_ => IntegerType()) + val invocationModel = evaluator.program.trees.FunctionInvocation(model.id, tparams, args) + val invocationCand = evaluator.program.trees.FunctionInvocation(cand.id, tparams, candPerm.reverse.m2c.map(args)) + try { + (evaluator.eval(invocationModel), evaluator.eval(invocationCand)) match { + case (inox.evaluators.EvaluationResults.Successful(expected), inox.evaluators.EvaluationResults.Successful(output)) => + Some(Eval(expected, output)) + case _ => None } + } catch { + case NonFatal(_) => None + } + } + + private def evalOn(model: FunDef, cand: FunDef, args: Seq[Expr]): Option[Eval] = + evalOn(model, cand, args, ArgPermutation(args.indices)) + + private def mkEvaluator() = { + val syms: symbols.type = symbols + type ProgramType = inox.Program {val trees: self.trees.type; val symbols: syms.type} + val prog: ProgramType = inox.Program(self.trees)(syms) + val sem = new inox.Semantics { + val trees: prog.trees.type = prog.trees + val symbols: syms.type = prog.symbols + val program: prog.type = prog + + def createEvaluator(ctx: inox.Context) = sys.error("Unsupported") + + def createSolver(ctx: inox.Context) = sys.error("Unsupported") + } + class EvalImpl(override val program: prog.type, override val context: inox.Context) + (using override val semantics: sem.type) + extends evaluators.RecursiveEvaluator(program, context) + with inox.evaluators.HasDefaultGlobalContext + with inox.evaluators.HasDefaultRecContext { + override lazy val maxSteps: Int = maxStepsEval + } + new EvalImpl(prog, self.context)(using sem) } //endregion @@ -839,6 +847,13 @@ class EquivalenceChecker(override val trees: Trees, mod.returnType == typeOps.instantiateType(cand.returnType, substMap) } + def prefixOf(id: Identifier): Option[Seq[String]] = { + id match { + case si: SymbolIdentifier if si.symbol.path.size > 1 => Some(si.symbol.path.init) + case _ => None + } + } + // Ensure that we do *not* match `choose` functions created from `choose` expressions. // If we were to match them, we would unveil `choose` expressions which we don't want to do // because these must remain hidden behind their `choose` expression counterpart. @@ -847,8 +862,23 @@ class EquivalenceChecker(override val trees: Trees, case _ => false }) - val modSubs = getTransitiveCalls(model) - val candSubs = getTransitiveCalls(cand) + val modSubs0 = getTransitiveCalls(model) + val candSubs0 = getTransitiveCalls(cand) + // Functions that are common to both candidate and model must come from the same source, + // so these are not meaningful to pairwise check. + val modSubs1 = modSubs0.filter(fd => !candSubs0.exists(_.id == fd.id)) + val candSubs1 = candSubs0.filter(fd => !modSubs0.exists(_.id == fd.id)) + // Furthermore, if the model and candidate functions reside in different objects/namespaces + // (determined by looking at their prefix), then we can exclude functions that come outside + // of their object/namespace since these are extern and do not benefit from pairwise matching. + val (modSubs, candSubs) = (prefixOf(model), prefixOf(cand)) match { + case (Some(modPrefix), Some(candPrefix)) if modPrefix != candPrefix => + val modSubs = modSubs1.filter(fd => prefixOf(fd.id).contains(modPrefix)) + val candSubs = candSubs1.filter(fd => prefixOf(fd.id).contains(candPrefix)) + (modSubs, candSubs) + case _ => (modSubs1, candSubs1) + } + // All pairs model-candidate subfns that with compatible signature modulo arg permutation val allValidPairs = for { ms <- modSubs @@ -916,6 +946,36 @@ class EquivalenceChecker(override val trees: Trees, //region Miscellaneous + private def reportHelper(pr: StainlessProgram) + (analysis: VerificationAnalysis, vc: VC[pr.trees.type]) + // Operations to perform if the function in question is the function checked + // for equivalence (i.e. one that is passed to --comparefuns) + (onMainFault: Identifier => Unit) + // Operations to perform if the function in question is a function + // called by the function checked for equivalence + (onAuxiliaryFault: Identifier => Unit): Option[Set[Identifier]] = { + val fun = vc.fid + if (allCandidates.contains(fun)) { + remainingCandidates -= fun + onMainFault(fun) + Some(Set(fun)) + } else candidatesCallee.get(fun) match { + case Some(cands) => + // This means this erroneous or safety-unknown `fun` is called by all candidates in `cands`. + // `cands` should be of size 1 because a function called by multiple candidates must be either a library fn or + // a provided function which are all assumed to be correct. + cands.foreach { cand => + remainingCandidates -= cand + onAuxiliaryFault(cand) + } + Some(cands) + case None => + // Nobody knows about this function + None + } + } + + // Note: expects the ctex to have type parameter substituted with integer literals (as it is done in ctexOrderedArguments). private def addCtex(ctex: Seq[Expr]): Unit = { val currNbCtex = ctexsDb.map(_._2.size).sum if (currNbCtex < maxCtex) { @@ -988,7 +1048,7 @@ object EquivalenceChecker { val defaultInitScore = 200 val defaultMaxMatchingPermutation = 16 val defaultMaxCtex = 1024 - val defaultMaxStepsEval = 512 + val defaultMaxStepsEval = 10000 type Path = Seq[String] @@ -1306,15 +1366,19 @@ object EquivalenceChecker { case _ => return ExtractedTest.Failure(TestExtractionFailure.ReturnTypeMismatch) } - def peel(e: Expr, acc: Seq[Expr]): Either[Expr, Seq[Expr]] = e match { + type Bdgs = Seq[(ValDef, Expr)] + def peel(e: Expr, bdgs: Bdgs, samplesAcc: Seq[Expr]): Either[Expr, Seq[Expr]] = e match { + case Let(vd, e, body) => + peel(body, bdgs :+ (vd, e), samplesAcc) case ADT(id: SymbolIdentifier, _, Seq(head, tail)) if id.symbol.path == Seq("stainless", "collection", "Cons") => - peel(tail, acc :+ head) + val sample = bdgs.foldRight(head) { case ((vd, e), body) => Let(vd, e, body).copiedFrom(e) } + peel(tail, bdgs, samplesAcc :+ sample) case ADT(id: SymbolIdentifier, _, Seq()) if id.symbol.path == Seq("stainless", "collection", "Nil") => - Right(acc) + Right(samplesAcc) case _ => Left(e) } - val samples = peel(fd.fullBody, Seq.empty) match { + val samples = peel(fd.fullBody, Seq.empty, Seq.empty) match { case Left(_) => return ExtractedTest.Failure(TestExtractionFailure.UnknownExpr) case Right(Seq()) => return ExtractedTest.Failure(TestExtractionFailure.NoData) case Right(samplesTupled) => samplesTupled diff --git a/core/src/main/scala/stainless/equivchk/EquivalenceCheckingComponent.scala b/core/src/main/scala/stainless/equivchk/EquivalenceCheckingComponent.scala index 6640afc70d..21b15b8e12 100644 --- a/core/src/main/scala/stainless/equivchk/EquivalenceCheckingComponent.scala +++ b/core/src/main/scala/stainless/equivchk/EquivalenceCheckingComponent.scala @@ -130,7 +130,9 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking invalidVCsCands = counterExamples(gen).flatMap { case (vc, ctex) => ec.reportUnsafe(gen.program)(gen, vc, ctex).getOrElse(Set.empty) }.toSeq.distinct + unknownsSafetyCands = unknowns(gen).flatMap(ec.reportUnknown(gen.program)(gen, _).getOrElse(Set.empty)).toSeq.distinct _ = debugInvalidVCsCandidates(invalidVCsCands) + _ = debugUnknownsSafetyCandidates(unknownsSafetyCands) trRes <- equivCheck(ec) } yield buildAnalysis(ec)(ids, gen, trRes) } @@ -156,24 +158,31 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking solvingInfo.flatMap(_.solverName), "equivalence", fd.source)) } val unsafe = trRes.unsafe.toSeq.sortBy(_._1).flatMap { - case (errn, ec.UnsafeData(_, _)) => + case (errn, _) => val fd = ec.symbols.getFunction(errn) Some(Record(errn, fd.getPos, 0L, Status.Equivalence(EquivalenceStatus.Unsafe), None, "safety", fd.source)) } + val unknwsSafety = trRes.unknownsSafety.toSeq.sortBy(_._1).flatMap { + case (fnId, _) => + val fd = ec.symbols.getFunction(fnId) + Some(Record(fnId, fd.getPos, 0L, + Status.Equivalence(EquivalenceStatus.UnknownSafety), + None, "safety", fd.source)) + } val wrgs = trRes.wrongs.toSeq.sorted.map { wrong => val fd = ec.symbols.getFunction(wrong) // No solver or time specified because it's a signature mismatch Record(wrong, fd.getPos, 0L, Status.Equivalence(EquivalenceStatus.Wrong), None, "equivalence", fd.source) } - val unknws = trRes.unknowns.toSeq.sortBy(_._1).map { + val unknwsEquiv = trRes.unknownsEquivalence.toSeq.sortBy(_._1).map { case (unknown, data) => val fd = ec.symbols.getFunction(unknown) - Record(unknown, fd.getPos, data.solvingInfo.time, Status.Equivalence(EquivalenceStatus.Unknown), data.solvingInfo.solverName, "equivalence", fd.source) + Record(unknown, fd.getPos, data.solvingInfo.time, Status.Equivalence(EquivalenceStatus.UnknownEquivalence), data.solvingInfo.solverName, "equivalence", fd.source) } - val allRecords = genRecors ++ valid ++ unsafe ++ unequiv ++ wrgs ++ unknws + val allRecords = genRecors ++ valid ++ unsafe ++ unequiv ++ wrgs ++ unknwsSafety ++ unknwsEquiv new EquivalenceCheckingAnalysis(ids.toSet, allRecords, general.extractionSummary) } @@ -226,11 +235,17 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking private def counterExamples(analysis: VerificationAnalysis) = { analysis.vrs.collect { - case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _)) => + case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _, _)) => vc -> model }.toMap } + private def unknowns(analysis: VerificationAnalysis) = { + analysis.vrs.collect { + case (vc, vcRes) if vcRes.isInconclusive => vc + } + } + private def debugInvalidVCsCandidates(cands: Seq[Identifier]): Unit = { if (cands.nonEmpty) { context.reporter.whenDebug(DebugSectionEquivChk) { debug => @@ -241,25 +256,33 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking } } + private def debugUnknownsSafetyCandidates(cands: Seq[Identifier]): Unit = { + if (cands.nonEmpty) { + context.reporter.whenDebug(DebugSectionEquivChk) { debug => + debug(s"The following candidates were pruned for having unknowns VCs:") + val candsStr = cands.sorted.map(_.fullName).mkString(" ", "\n ", "") + debug(candsStr) + } + } + } + private def debugPruned(ec: EquivalenceChecker)(pruned: Map[Identifier, ec.PruningReason]): Unit = { - def pretty(fn: Identifier, reason: ec.PruningReason): String = { + def pretty(fn: Identifier, reason: ec.PruningReason): Seq[String] = { val rsonStr = reason match { - case ec.PruningReason.SignatureMismatch => "signature mismatch" + case ec.PruningReason.SignatureMismatch => Seq("signature mismatch") case ec.PruningReason.ByTest(testId, sampleIx, ctex) => - s"""test falsification by ${testId.fullName} sample n°${sampleIx + 1} with ${prettyCtex(ec)(ctex.mapping)} - | Expected: ${ctex.expected} but got: ${ctex.got}""".stripMargin + Seq(s"test falsification by ${testId.fullName} sample n°${sampleIx + 1}:") ++ prettyCtex(ec)(ctex).map(" " ++ _) // add 2 indentation case ec.PruningReason.ByPreviousCtex(ctex) => - s"""counter-example falsification with ${prettyCtex(ec)(ctex.mapping)} - | Expected: ${ctex.expected} but got: ${ctex.got}""".stripMargin + Seq(s"counter-example falsification:") ++ prettyCtex(ec)(ctex).map(" " ++ _) } - s"${fn.fullName}: $rsonStr" + Seq(s"${fn.fullName}:") ++ rsonStr.map(" " ++ _) } if (pruned.nonEmpty) { context.reporter.whenDebug(DebugSectionEquivChk) { debug => debug("The following functions were pruned:") - val strs = pruned.toSeq.sortBy(_._1).map(pretty.tupled) - strs.foreach(s => debug(s" $s")) + val lines = pruned.toSeq.sortBy(_._1).flatMap(pretty.tupled) + lines.foreach(s => debug(s" $s")) } } } @@ -281,7 +304,7 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking val msg = classification match { case ec.Classification.Valid(model) => s"valid whose direct model is ${model.fullName}" case ec.Classification.Invalid(ctexs) => - val ctexStr = ctexs.map(prettyCtex(ec)(_)).map(s => s" $s").mkString("\n ") + val ctexStr = ctexs.flatMap(prettyCtex(ec)(_)).map(s => s" $s").mkString("\n ") s"invalid with the following counter-examples:\n $ctexStr" case ec.Classification.Unknown => "unknown" } @@ -309,10 +332,12 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking info(s"List of functions that are equivalent to model ${m.fullName}: ${lStr.mkString(", ")}") } val errns = res.unequivalent.keys.toSeq.map(_.fullName).sorted.mkString(", ") - val unknowns = res.unknowns.keys.toSeq.map(_.fullName).sorted.mkString(", ") + val unknownsSafety = res.unknownsSafety.keys.toSeq.map(_.fullName).sorted.mkString(", ") + val unknownsEquiv = res.unknownsEquivalence.keys.toSeq.map(_.fullName).sorted.mkString(", ") val wrongs = res.wrongs.toSeq.map(_.fullName).sorted.mkString(", ") info(s"List of erroneous functions: $errns") - info(s"List of timed-out functions: $unknowns") + info(s"List of timed-out functions (safety): $unknownsSafety") + info(s"List of timed-out functions (equivalence): $unknownsEquiv") info(s"List of wrong functions: $wrongs") info(s"Printing the final state:") res.valid.foreach { case (cand, data) => @@ -320,7 +345,7 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking info(s"Path for the function ${cand.fullName}: $pathStr") } res.unequivalent.foreach { case (cand, data) => - val ctexsStr = data.ctexs.map(ctex => ctex.map { case (vd, arg) => s"${vd.id.name} -> $arg" }.mkString(", ")) + val ctexsStr = data.ctexs.flatMap(prettyCtex(ec)(_)) info(s"Unequivalence counterexample for the function ${cand.fullName}:") ctexsStr.foreach(s => info(s" $s")) } @@ -343,21 +368,38 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking } } - private def prettyCtex(ec: EquivalenceChecker)(ctex: Seq[(ec.trees.ValDef, ec.trees.Expr)]): String = - ctex.map { case (vd, e) => s"${vd.id.name} -> $e" }.mkString(", ") + // This returns a Seq due to being multiline and to allow easier indentation + private def prettyCtex(ec: EquivalenceChecker)(ctex: ec.Ctex): Seq[String] = { + val args = ctex.mapping.map { case (vd, e) => s"${vd.id.name} -> $e" }.mkString(", ") + val eval = ctex.eval.map(ev => Seq(s"Expected ${ev.expected} but got ${ev.got}")).getOrElse(Seq.empty) + Seq(args) ++ eval + } private def dumpResultsJson(out: File, ec: EquivalenceChecker)(res: ec.Results): Unit = { - def ctexJson(ctex: Seq[(ec.trees.ValDef, ec.trees.Expr)]): Json = - Json.fromFields(ctex.map { case (vd, expr) => vd.id.name -> Json.fromString(expr.toString) }) + def ctexJson(ctex: ec.Ctex): Json = { + val args = Json.fromFields(ctex.mapping.map { case (vd, expr) => vd.id.name -> Json.fromString(expr.toString) }) + Json.fromFields( + Seq("args" -> args) ++ + ctex.eval.map(ev => Seq( + "expected" -> Json.fromString(ev.expected.toString), + "got" -> Json.fromString(ev.got.toString) + )).getOrElse(Seq.empty) + ) + } + def unsafeCtexJson(data: ec.UnsafeCtex): Json = Json.fromFields(Seq( "kind" -> Json.fromString(data.kind.name), "position" -> Json.fromString(s"${data.pos.line}:${data.pos.col}"), - "ctex" -> data.ctex.map(ctexJson).getOrElse(Json.Null) + "ctex" -> data.ctex.map(mapping => ctexJson(ec.Ctex(mapping, None))).getOrElse(Json.Null) + )) + def unknownVCJson(data: ec.UnknownSafetyVC): Json = Json.fromFields(Seq( + "kind" -> Json.fromString(data.kind.name), + "position" -> Json.fromString(s"${data.pos.line}:${data.pos.col}"), )) val equivs = res.equiv.map { case (m, l) => m.fullName -> l.map(_.fullName).toSeq.sorted } .toSeq.sortBy(_._1) - val unknowns = res.unknowns.keys.toSeq.map(_.fullName).sorted + val unknownsEquiv = res.unknownsEquivalence.keys.toSeq.map(_.fullName).sorted val wrongs = res.wrongs.toSeq.map(_.fullName).sorted val weights = res.weights.map { case (mod, w) => mod.fullName -> w }.toSeq .sortBy { case (mod, w) => (-w, mod) } @@ -386,7 +428,18 @@ class EquivalenceCheckingRun private(override val component: EquivalenceChecking ) )) }), - "timeout" -> Json.fromValues(unknowns.sorted.map(Json.fromString)), + "unknownSafety" -> Json.fromValues(res.unknownsSafety.toSeq.sortBy(_._1).map { case (cand, data) => + Json.fromFields(Seq( + "function" -> Json.fromString(cand.fullName), + "self" -> Json.fromValues(data.self.map(unknownVCJson)), + "auxiliaries" -> Json.fromFields( + data.auxiliaries.toSeq.map { case (aux, ctexs) => + aux.fullName -> Json.fromValues(ctexs.map(unknownVCJson)) + }.sortBy(_._1) + ) + )) + }), + "unknownEquivalence" -> Json.fromValues(unknownsEquiv.sorted.map(Json.fromString)), "wrong" -> Json.fromValues(wrongs.sorted.map(Json.fromString)), "weights" -> Json.fromFields(weights.map { case (mod, w) => mod -> Json.fromInt(w) }) )) diff --git a/core/src/main/scala/stainless/equivchk/EquivalenceCheckingReport.scala b/core/src/main/scala/stainless/equivchk/EquivalenceCheckingReport.scala index 4b454dfb68..563e483269 100644 --- a/core/src/main/scala/stainless/equivchk/EquivalenceCheckingReport.scala +++ b/core/src/main/scala/stainless/equivchk/EquivalenceCheckingReport.scala @@ -44,7 +44,7 @@ object EquivalenceCheckingReport { def isInconclusive: Boolean = this match { case Verification(status) => status.isInconclusive - case Equivalence(EquivalenceStatus.Unknown) => true + case Equivalence(EquivalenceStatus.UnknownSafety | EquivalenceStatus.UnknownEquivalence) => true case _ => false } } @@ -54,7 +54,8 @@ object EquivalenceCheckingReport { case Unequivalent case Unsafe case Wrong - case Unknown + case UnknownSafety + case UnknownEquivalence } case class Record(id: Identifier, pos: inox.utils.Position, time: Long, @@ -90,12 +91,13 @@ class EquivalenceCheckingReport(override val results: Seq[EquivalenceCheckingRep case Status.Equivalence(EquivalenceStatus.Wrong) => "signature mismatch" case Status.Equivalence(EquivalenceStatus.Unequivalent) => "not equivalent" case Status.Equivalence(EquivalenceStatus.Unsafe) => "unsafe" - case Status.Equivalence(EquivalenceStatus.Unknown) => "unknown" + case Status.Equivalence(EquivalenceStatus.UnknownSafety) => "unknown safety" + case Status.Equivalence(EquivalenceStatus.UnknownEquivalence) => "unknown equivalence" } val level = levelOf(status) val solver = solverName getOrElse "" val extra = Seq(kind, statusName, solver) - RecordRow(id, pos, level, extra, time) + RecordRow(id, pos, level, extra, time, None) } lazy val totalConditions: Int = results.size lazy val totalTime: Long = results.map(_.time).sum diff --git a/core/src/main/scala/stainless/evaluators/CodeGenEvaluator.scala b/core/src/main/scala/stainless/evaluators/CodeGenEvaluator.scala index ba1343741d..a52e91c7c8 100644 --- a/core/src/main/scala/stainless/evaluators/CodeGenEvaluator.scala +++ b/core/src/main/scala/stainless/evaluators/CodeGenEvaluator.scala @@ -19,7 +19,7 @@ import inox.evaluators._ import evaluators._ import scala.util.Try -import scala.collection.JavaConverters._ +import scala.jdk.CollectionConverters._ import scala.collection.mutable.{Map => MutableMap} class CodeGenEvaluator(override val program: Program, diff --git a/core/src/main/scala/stainless/evaluators/EvaluatorReport.scala b/core/src/main/scala/stainless/evaluators/EvaluatorReport.scala index dfabef26ba..cd3812d779 100644 --- a/core/src/main/scala/stainless/evaluators/EvaluatorReport.scala +++ b/core/src/main/scala/stainless/evaluators/EvaluatorReport.scala @@ -57,7 +57,7 @@ class EvaluatorReport(val results: Seq[EvaluatorReport.Record], val sources: Set override lazy val annotatedRows = results map { case Record(id, pos, status, time) => - RecordRow(id, pos, levelOf(status), Seq(descriptionOf(status)), time) + RecordRow(id, pos, levelOf(status), Seq(descriptionOf(status)), time, None) } private lazy val totalTime = (results map { _.time }).sum diff --git a/core/src/main/scala/stainless/extraction/imperative/AntiAliasing.scala b/core/src/main/scala/stainless/extraction/imperative/AntiAliasing.scala index 79b7c3f1a1..76a74c4bca 100644 --- a/core/src/main/scala/stainless/extraction/imperative/AntiAliasing.scala +++ b/core/src/main/scala/stainless/extraction/imperative/AntiAliasing.scala @@ -256,34 +256,201 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override } // NOTE: `args` must refer to the arguments of the function invocation before transformation (the original args) - def mapApplication(formalArgs: Seq[ValDef], args: Seq[Expr], nfi: Expr, nfiType: Type, fiEffects: Set[Effect], env: Env): Expr = { + // IMPORTANT NOTICE: + // In the `Let` case transformation of `transform`, we need to compute the targets of the transformed binding. + // If this binding happens to contain a function call, the returned targets may be invalid because + // `getTargets` works on functions prior to the AntiAliasing function purification, not after (as it's done it this `Let` case). + // + // It turns out that if we bind the result of the function call to a `var` (for mutable type) the target computation + // will fail which will result in rejection (with the message "Unsupported `val` definition in AntiAliasing"). + // Not doing so will very likely result in a crash later on in unrelated part of the code (due to invalid targets being applied). + // + // To properly fix this, we would need to distinguish pre/post transformation `getTargets` computation, + // which would require significant changes to EffectsAnalyzer. + def mapApplication(formalArgs: Seq[ValDef], args: Seq[Expr], nfi: Expr, nfiType: Type, fiEffects: Set[Effect], isOpaqueOrExtern: Boolean, env: Env): Expr = { + + def affectedBindings(updTarget: Target, isReplacement: Boolean): Map[ValDef, Set[Target]] = { + def isAffected(t: Target): Boolean = { + if (isReplacement) t.maybeProperPrefixOf(updTarget) + else t.maybePrefixOf(updTarget) || updTarget.maybePrefixOf(t) + } + env.targets.map { + case (vd, targets) => + val affected = targets.filter(isAffected) + vd -> affected + }.filter(_._2.nonEmpty) + } + if (fiEffects.exists(e => formalArgs contains e.receiver.toVal)) { - val localEffects: Seq[Set[(Effect, Set[(Effect, Option[Expr])])]] = (formalArgs zip args) - .map { case (vd, arg) => (fiEffects.filter(_.receiver == vd.toVariable), arg) } - .filter { case (effects, _) => effects.nonEmpty } - .map { case (effects, arg) => effects map (e => (e, e on arg)) } + val localEffects = formalArgs.zip(args) + .map { case (vd, arg) => + // Effects for each parameter + (vd.toVariable, fiEffects.filter(_.receiver == vd.toVariable), arg) + }.filter { case (_, effects, _) => effects.nonEmpty } val freshRes = ValDef(FreshIdentifier("res"), nfiType).copiedFrom(nfi) - val assgns = (for { - (effects, index) <- localEffects.zipWithIndex - (outerEffect0, innerEffects) <- effects - (effect0, effectCond) <- innerEffects - } yield { - val outerEffect = outerEffect0.removeUnknownAccessor - val effect = effect0.removeUnknownAccessor - val pos = args(index).getPos - val resSelect = TupleSelect(freshRes.toVariable, index + 2) - // Follow all aliases of the updated target (may include self if it has no alias) - val primaryTargs = dealiasTarget(effect.toTarget(effectCond), env) - val assignedPrimaryTargs = primaryTargs.toSeq - .map(t => makeAssignment(pos, resSelect, outerEffect.path.path, t, dropVcs = true)) - val updAliasingValDefs = updatedAliasingValDefs(primaryTargs, env, pos) - - assignedPrimaryTargs ++ updAliasingValDefs - }).flatten - val extractResults = Block(assgns, TupleSelect(freshRes.toVariable, 1)) + val assgns = localEffects.zipWithIndex.flatMap { + case ((vd, effects, arg), effIndex) => + // +1 because we are a tuple and +1 because the first component is for the result of the function + val resSelect = TupleSelect(freshRes.toVariable, effIndex + 2) + // All effects on the given parameter, applied to the given argument + val paramWithArgsEffect = for { + outerEffect0 <- effects + (effect0, effectCond) <- outerEffect0 on arg + } yield { + val outerEffect = outerEffect0.removeUnknownAccessor + val effect = effect0.removeUnknownAccessor + val primaryTargs = dealiasTarget(effect.toTarget(effectCond), env) + (outerEffect, primaryTargs) + } + // Suppose we have the following definitions: + // case class Ref(var x: Int, var y: Int) + // case class RefRef(var lhs: Ref, var rhs: Ref) + // + // def modifyLhs(rr: RefRef, v: Int): Unit = { + // rr.lhs.x = v + // rr.lhs.y = v + // } + // def test1(testRR: RefRef): Unit = { + // val rrAlias = testRR + // val lhsAlias = testRR.lhs + // modifyLhs(testRR, 123) + // // ... + // } + // `modifyLhs` is (essentially) transformed as follows by `AntiAliasing` (not here in `mapApplication`): + // def modifyLhs(rr: RefRef, v: Int): (Unit, RefRef) = { + // ((), RefRef(Ref(v, v), rr.rhs) + // } + // The transformed `modifyLhs` returns a copy of the "updated" `rr`. + // + // Our task here in `mapApplication` is to transform the call to `modifyLhs`. + // Intuitively, in this case, we can "update" `testRR` to point to the "updated" version + // returned by `modifyLhs`, and update the aliases accordingly: + // def test1(testRR: RefRef): Unit = { + // val rrAlias = testRR + // val lhsAlias = testRR.lhs + // val res = modifyLhs(testRR, 123) + // testRR = res._2 + // rrAlias = testRR + // lhsAlias = testRR.lhs + // // ... + // } + // We can do so because we know precisely the `Targets` of the argument, namely `testRR` + // and we can update its aliases accordingly. + // This correspond to the `Success` case of having a `ModifyingEffect` on `vd` (here: `rr`) + // applied on `arg` (here: `testRR`). + // + // However, sometimes, we may not always succeed in computing the precise targets, + // as in the following example: + // def test2(testRR: RefRef): Unit = { + // val lhsAlias = testRR.lhs + // val rhsAlias = testRR.rhs + // modifyLhs(RefRef(lhsAlias, rhsAlias), 123) + // } + // Here, we are not able to compute the targets of `RefRef(lhsAlias, rhsAlias)`, + // which corresponds to the `Failure` case. As such, we cannot simply "update" + // the `testRR` variable using the returned result as-is (as we did for `test1`). + // + // Instead, we need to apply each effect of `modifyLhs` *individually* on the argument. + // The effects for `modifyLhs` are (stored in `localEffects`): + // rr -> Set(ReplacementEffect(rr.lhs.x), ReplacementEffect(rr.lhs.y))) + // So we need to apply two `ReplacementEffect`, one on `rr.lhs.x` and one on `rr.lhs.y` on the argument. + // Doing so with `paramWithArgsEffect` gives us: + // ReplacementEffect(rr.lhs.x) -> Set(Target(testRR, None, .lhs.x)) + // ReplacementEffect(rr.lhs.y) -> Set(Target(testRR, None, .lhs.y)) + // which we can then use to update `testRR` (alongside their aliases): + // def test2(testRR: RefRef): Unit = { + // var lhsAlias: Ref = testRR.lhs + // val rhsAlias: Ref = testRR.rhs + // val res: (Unit, RefRef) = modifyLhs(RefRef(lhsAlias, rhsAlias), 123) + // // Note that we "update" each field individually, this is due to + // // having each effect applied separately! + // testRR = RefRef(Ref(res._2.lhs.x, testRR.lhs.y), testRR.rhs) + // lhsAlias = testRR.lhs + // testRR = RefRef(Ref(testRR.lhs.x, res._2.lhs.y), testRR.rhs) + // lhsAlias = testRR.lhs + // // ... + // } + // + // Note that we can always apply this second technique even if we have precise aliases. + // However, this tends to "rebuild" the object instead of reusing the "updated" result + // which can lead to verification inefficiency (and does not work well in presence of + // @opaque or @extern functions). + Try(ModifyingEffect(vd, Path.empty).on(arg)) match { + case Success(modEffect) => + // Update everything that the argument is aliasing + val primaryTargs = modEffect.flatMap { case (eff, cond) => dealiasTarget(eff.toTarget(cond), env) } + val assignedPrimaryTargs = primaryTargs + // The order of assignments does not matter between "primary targets" + // but it must precede the update of aliases (`updAliasingValDefs`) + .toSeq + .map(t => makeAssignment(arg.getPos, resSelect, Seq.empty, t, dropVcs = true)) + // We need to be careful with what we are updating here. + // If we expand on the above example with the following function: + // def t3(refref: RefRef): Unit = { + // val lhs = refref.lhs + // val oldLhs = lhs.x + // replaceLhs(refref, 123) + // assert(lhs.x == oldLhs) + // assert(refref.lhs.x == 123) + // } + // In `replaceLhs`, we have a ReplacementEffect on `rr.lhs`, this means + // that `rr.lhs` is replaced with a new `Ref`, leaving all aliases of `rr.lhs` + // (in `t3`, the `val lhs`) untouched. So, after the call to `replaceLhs`, + // any modification to `rr.lhs` do not alter the other aliases (here, `lhs`). + // The function `t3` should be transformed as follows: + // def t3(refref: RefRef): Unit = { + // val lhs = refref.lhs + // val oldLhs = lhs.x + // val res = replaceLhs(refref, 123) + // refref = res._2 + // assert(lhs.x == oldLhs) + // assert(refref.lhs.x == 123) + // } + // In particular, note that we *do not* touch `lhs`: the following transformation is incorrect: + // var lhs = refref.lhs + // val oldLhs = lhs.x + // val res = replaceLhs(refref, 123) + // refref = res._2 + // lhs = refref.lhs + // because after the call to `replaceLhs`, `lhs` and `refref.lhs` become unrelated. + // Note that, for @opaque and @extern function, we assume the object was mutated in each of its field + // and therefore update all aliases. + val aliasingVds = { + if (isOpaqueOrExtern) { + primaryTargs.flatMap(affectedBindings(_, false)) + } else { + paramWithArgsEffect.flatMap { + case (eff, targs) => + targs.flatMap(affectedBindings(_, eff.kind == ReplacementKind)) + } + } + } + val updAliasingValDefs = aliasingVds + .toSeq // See comment on `assignedPrimaryTargs` + .flatMap { case (vd, targs) => + targs.map(t => makeAssignment(arg.getPos, t.wrap.get, Seq.empty, Target(vd.toVariable, t.condition, Path.empty), true)) + } + assignedPrimaryTargs ++ updAliasingValDefs + case Failure(_) => + paramWithArgsEffect.toSeq.flatMap { case (outerEffect, primaryTargs) => + // Update everything that the argument is aliasing + val assignedPrimaryTargs = primaryTargs + .toSeq + .map(t => makeAssignment(arg.getPos, resSelect, outerEffect.path.path, t, dropVcs = true)) + // Update everything aliasing the argument + val updAliasingValDefs = updatedAliasingValDefs(primaryTargs, env, arg.getPos) + assignedPrimaryTargs ++ updAliasingValDefs + } + } + } + val extractResults = Block(assgns, TupleSelect(freshRes.toVariable, 1)) + // FIXME: This should be `Let` and not `LetVar`, however doing so will cause a crash in e.g. `MapAliasing1` + // because `getAllTargetsDealiased` in the `Let` case will result in an invalid target due to + // this function not supporting targets computation on function application *post-transformation* + // (see important notice on above). if (isMutableType(nfiType)) { LetVar(freshRes, nfi, extractResults) } else { @@ -494,104 +661,153 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override Block(updates.init, updates.last).setPos(swap) ).setPos(swap) + case cellSwap @ CellSwap(cell1, cell2) => + // Though `cell1`, `cell2` are all normalized, we still need to recursively transform + // them, as they can refer to constructs that needs transformation, such as lambdas that mutate their params + val recCell1 = transform(cell1, env) + val recCell2 = transform(cell2, env) + + val base = recCell1.getType.asInstanceOf[ClassType].tps.head + + val cellClassDef = symbols.lookup.get[ClassDef]("stainless.lang.Cell").get + val vFieldId = cellClassDef.fields.head.id + + val temp = ValDef.fresh("temp", base).setPos(cellSwap) + val targets1 = getDirectTargetsDealiased(recCell1, ClassFieldAccessor(vFieldId), env) + .getOrElse(throw MalformedStainlessCode(cellSwap, "Unsupported cellSwap (first cell)")) + val targets2 = getDirectTargetsDealiased(recCell2, ClassFieldAccessor(vFieldId), env) + .getOrElse(throw MalformedStainlessCode(cellSwap, "Unsupported cellSwap (second cell)")) + + val updates1 = updatedTargetsAndAliases(targets2, ClassSelector(cell1, vFieldId).setPos(cellSwap), env, cellSwap.getPos) + val updates2 = updatedTargetsAndAliases(targets1, temp.toVariable, env, cellSwap.getPos) + val updates = updates1 ++ updates2 + if (updates.isEmpty) UnitLiteral().setPos(cellSwap) + else + Let(temp, transform(ClassSelector(recCell2, vFieldId).setPos(cellSwap), env), + Block(updates.init, updates.last).setPos(cellSwap) + ).setPos(cellSwap) + case l @ Let(vd, e, b) if isMutableType(vd.tpe) => // see https://github.com/epfl-lara/stainless/pull/920 for discussion val newExpr = transform(e, env) val targets = getAllTargetsDealiased(newExpr, env) + targets match { + case Some(targets) => + // This branch handles all cases when targets can be precisely computed, namely when + // 1. newExpr is a fresh expression + // 2. newExpr is a precise alias to existing variable(s) + // 3. A combination of 1. and 2. (e.g. val y = if (cond) x else Ref(123)) + + // Targets with a false condition are those that are never accessed, we can remove them. + val targs = targets.filterNot(_.condition == Some(BooleanLiteral(false))) + // If there are more than one target, then it must be the case that their conditions are all disjoint, + // due to the way they are computed by `getTargets`. + // (here, we use a weaker prop. that asserts that all their condition are defined, for sanity checks) + assert(targs.size <= 1 || targs.forall(_.condition.isDefined)) + + // extraTarget: whether we should treat `vd` as a new target or not. + val extraTarget = { + if (targs.isEmpty) { + // newExpr is a fresh expression (case 1), so `vd` is introducing a new target. + Seq(Target(vd.toVariable, None, Path.empty)) + } else if (targs.size == 1 && targs.head.condition.isEmpty) { + // newExpr is a precise and unconditional alias to an existing variable - no new targets (case 2). + Seq.empty[Target] + } else { + // Here, we have >= 1 targets and all of them are conditional: + // cond1 -> target1 + // ... + // condn -> targetn + // If cond1 && ... && condn provably cover all possible cases, then we know that `newExpr` is + // a precise alias to n existing variables, so there are no new targets to consider (case 2). + // Otherwise, it may be the case that `newExpr` refer to fresh expression and alias existing variables (case 3), + // such as in the following example: + // val y = if (cond1) x else Ref(123) + // Here, the targets of `y` would be the targets of x, with condition cond1. + // But it also conditionally introduce a new target, namely Ref(123) when !cond1. + // In such case, we introduce `y` as a new target, with condition !cond1. + assert(targs.forall(_.condition.isDefined)) + val disj = simplifyOr(targs.map(_.condition.get).toSeq) + val negatedConds = simpleNot(disj) + if (negatedConds == BooleanLiteral(false)) Seq.empty[Target] + else Seq(Target(vd.toVariable, Some(negatedConds), Path.empty)) + } + } - if (targets.isDefined) { - // This branch handles all cases when targets can be precisely computed, namely when - // 1. newExpr is a fresh expression - // 2. newExpr is a precise alias to existing variable(s) - // 3. A combination of 1. and 2. (e.g. val y = if (cond) x else Ref(123)) - - // Targets with a false condition are those that are never accessed, we can remove them. - val targs = targets.get.filterNot(_.condition == Some(BooleanLiteral(false))) - // If there are more than one target, then it must be the case that their conditions are all disjoint, - // due to the way they are computed by `getTargets`. - // (here, we use a weaker prop. that asserts that all their condition are defined, for sanity checks) - assert(targs.size <= 1 || targs.forall(_.condition.isDefined)) - - // extraTarget: whether we should treat `vd` as a new target or not. - val extraTarget = { - if (targs.isEmpty) { - // newExpr is a fresh expression (case 1), so `vd` is introducing a new target. - Seq(Target(vd.toVariable, None, Path.empty)) - } else if (targs.size == 1 && targs.head.condition.isEmpty) { - // newExpr is a precise and unconditional alias to an existing variable - no new targets (case 2). - Seq.empty[Target] + val newBody = transform(b, env.withTargets(vd, targs ++ extraTarget).withBinding(vd)) + // Note: even though there are no effects on `vd`, we still need to re-assign it + // in case it aliases a target that gets updated. + // As such, we use appearsInAssignment (on the transformed body) instead of checking for effects (on the original body) + val canLetVal = !appearsInAssignment(vd.toVariable, newBody) + if (canLetVal) Let(vd, newExpr, newBody).copiedFrom(l) + else LetVar(vd, newExpr, newBody).copiedFrom(l) + case None => + // In this scenario, where we cannot precisely compute the targets of `e`, we can observe that + // if the (dealiased) variables appearing in `e` and those appearing in `b` (dealiased as well) + // are disjoint, then we can be sure that `b` will not *directly* modify the variables in `e`. + // On the other hand, `b` is of course allowed to modify `vd`, for which we must + // apply the effects afterwards (represented below as `copyEffects`). + // + // We can further refine this observation by noting that (dealised) variables in `e` *may* appear + // in `b` as well as long as they are not constituting the evaluation result of `e`, or in other + // terms, that they do not appear in a terminal position, which is recursively define as follows: + // -Terminal of let v = e in b is the terminal in b + // -Terminal of v is v itself + // -Terminals of if (b) e1 else e2 is terminals of e1 and e2 + // -and so on + // Indeed, these occurrences will properly be updated by the recursive transformation. + // The test `imperative/valid/TargetMutation8` illustrates this refinement. + val eVars = terminalVarsOfExprDealiased(e, env) + val bVars = varsOfExprDealiased(b, env) + val commonMutable = (eVars & bVars).filter(v => isMutableType(v.tpe)) + if (commonMutable.isEmpty) { + // The above condition is similar to the one in EffectsChecker#check#traverser#traverse#Let, with the + // difference that we also account for rewrites (which may introduce other variables, as in i1099.scala). + val newBody = transform(b, env withBinding vd) + + // for all effects of `b` whose receiver is `vd` + val copyEffects = effects(b).filter(_.receiver == vd.toVariable).flatMap { eff => + // we apply the effect on the bound expression (after transformation) + eff.on(newExpr).map { case (eff2, cond2) => makeAssignment(l.getPos, eff.receiver, eff.path.path, eff2.toTarget(cond2)) } + } + val resVd = ValDef.fresh("res", b.getType).copiedFrom(b) + + // What we would like is the following: + // var vd = newExpr + // val resVd = newBody + // copyEffects // must happen after newBody and newExpr + // resVd + // However, newBody may contain an `Ensuring` clause which would get "lost" in the newBody: + // var vd = newExpr + // val resVd = { + // newBody' + // }.ensuring(...) // oh no :( + // copyEffects + // resVd + // What we would like is something as follows: + // var vd = newExpr + // { + // newBodyBlocks // e.g. assignments etc. + // val resVd = newBodyLastExpr + // copyEffect + // resVd + // }.ensuring(...) + // To achieve this, we "drill" a hole in newBody using the normalizer object, + // insert copyEffect and plug it with resVd. This should get us something like the above. + val (newBodyCtx, newBodyLastExpr) = normalizer.drill(newBody) + val (copyEffectCtx, copyEffectLastExpr) = normalizer.normalizeBlock(Block(copyEffects.toSeq, resVd.toVariable).copiedFrom(l))(using normalizer.BlockNorm.Standard) + assert(copyEffectLastExpr == resVd.toVariable) // To be sure we are on the good track. + val combined = + newBodyCtx(let(resVd, newBodyLastExpr, copyEffectCtx(resVd.toVariable))) + LetVar(vd, newExpr, combined).copiedFrom(l) } else { - // Here, we have >= 1 targets and all of them are conditional: - // cond1 -> target1 - // ... - // condn -> targetn - // If cond1 && ... && condn provably cover all possible cases, then we know that `newExpr` is - // a precise alias to n existing variables, so there are no new targets to consider (case 2). - // Otherwise, it may be the case that `newExpr` refer to fresh expression and alias existing variables (case 3), - // such as in the following example: - // val y = if (cond1) x else Ref(123) - // Here, the targets of `y` would be the targets of x, with condition cond1. - // But it also conditionally introduce a new target, namely Ref(123) when !cond1. - // In such case, we introduce `y` as a new target, with condition !cond1. - assert(targs.forall(_.condition.isDefined)) - val disj = simplifyOr(targs.map(_.condition.get).toSeq) - val negatedConds = simpleNot(disj) - if (negatedConds == BooleanLiteral(false)) Seq.empty[Target] - else Seq(Target(vd.toVariable, Some(negatedConds), Path.empty)) + val msg = + s"""Unsupported `val` definition in AntiAliasing + |The following variables of mutable types are shared between the binding and the body: + | ${commonMutable.toSeq.sortBy(_.id.name).map(v => s"${v.id}: ${v.tpe}").mkString(", ")}""".stripMargin + throw MalformedStainlessCode(l, msg) } - } - - val newBody = transform(b, env.withTargets(vd, targs ++ extraTarget).withBinding(vd)) - // Note: even though there are no effects on `vd`, we still need to re-assign it - // in case it aliases a target that gets updated. - // As such, we use appearsInAssignment (on the transformed body) instead of checking for effects (on the original body) - val canLetVal = !appearsInAssignment(vd.toVariable, newBody) - if (canLetVal) Let(vd, newExpr, newBody).copiedFrom(l) - else LetVar(vd, newExpr, newBody).copiedFrom(l) - } else if ((varsOfExprDealiased(e, env) & varsOfExprDealiased(b, env)).forall(v => !isMutableType(v.tpe))) { - // The above condition is similar to the one in EffectsChecker#check#traverser#traverse#Let, with the - // difference that we also account for rewrites (which may introduce other variables, as in i1099.scala). - val newBody = transform(b, env withBinding vd) - - // for all effects of `b` whose receiver is `vd` - val copyEffects = effects(b).filter(_.receiver == vd.toVariable).flatMap { eff => - // we apply the effect on the bound expression (after transformation) - eff.on(newExpr).map { case (eff2, cond2) => makeAssignment(l.getPos, eff.receiver, eff.path.path, eff2.toTarget(cond2)) } - } - - val resVd = ValDef.fresh("res", b.getType).copiedFrom(b) - - // What we would like is the following: - // var vd = newExpr - // val resVd = newBody - // copyEffects // must happen after newBody and newExpr - // resVd - // However, newBody may contain an `Ensuring` clause which would get "lost" in the newBody: - // var vd = newExpr - // val resVd = { - // newBody' - // }.ensuring(...) // oh no :( - // copyEffects - // resVd - // What we would like is something as follows: - // var vd = newExpr - // { - // newBodyBlocks // e.g. assignments etc. - // val resVd = newBodyLastExpr - // copyEffect - // resVd - // }.ensuring(...) - // To achieve this, we "drill" a hole in newBody using the normalizer object, - // insert copyEffect and plug it with resVd. This should get us something like the above. - val (newBodyCtx, newBodyLastExpr) = normalizer.drill(newBody) - val (copyEffectCtx, copyEffectLastExpr) = normalizer.normalizeBlock(Block(copyEffects.toSeq, resVd.toVariable).copiedFrom(l))(using normalizer.BlockNorm.Standard) - assert(copyEffectLastExpr == resVd.toVariable) // To be sure we are on the good track. - val combined = - newBodyCtx(let(resVd, newBodyLastExpr, copyEffectCtx(resVd.toVariable))) - LetVar(vd, newExpr, combined).copiedFrom(l) - } else { - throw MalformedStainlessCode(l, "Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body)") } case l @ LetVar(vd, e, b) if isMutableType(vd.tpe) => @@ -675,8 +891,8 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override val nfi = FunctionInvocation( id, tps, args.map(transform(_, env)) ).copiedFrom(fi) - - mapApplication(fd.params, args, nfi, fi.tfd.instantiate(analysis.getReturnType(fd)), effects(fd), env) + val isExternOrOpaque = symbols.getFunction(id).flags.exists(f => f == Extern || f == Opaque) + mapApplication(fd.params, args, nfi, fi.tfd.instantiate(analysis.getReturnType(fd)), effects(fd), isExternOrOpaque, env) case alr @ ApplyLetRec(id, tparams, tpe, tps, args) => val fd = Inner(env.locals(id)) @@ -703,7 +919,8 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override ).copiedFrom(alr) val resultType = typeOps.instantiateType(analysis.getReturnType(fd), (tparams zip tps).toMap) - mapApplication(fd.params, args, nfi, resultType, effects(fd), env) + val isExternOrOpaque = env.locals(id).flags.exists(f => f == Extern || f == Opaque) + mapApplication(fd.params, args, nfi, resultType, effects(fd), isExternOrOpaque, env) case app @ Application(callee, args) => checkAliasing(app, args, env) @@ -721,7 +938,7 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override case (vd, i) if ftEffects(i) => ModifyingEffect(vd.toVariable, Path.empty) } val to = makeFunctionTypeExplicit(ft).asInstanceOf[FunctionType].to - mapApplication(params, args, nfi, to, appEffects.toSet, env) + mapApplication(params, args, nfi, to, appEffects.toSet, false, env) } else { Application(transform(callee, env), args.map(transform(_, env))).copiedFrom(app) } @@ -764,6 +981,44 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override } } + def terminalVarsOfExprDealiased(expr: Expr, env: Env): Set[Variable] = { + // Like `varsOfExprDealiased` but takes into account local binding in `vars` + def varsOfExprDealiasedWithLocalBdgs(e: Expr, vars: Map[Variable, Set[Variable]]): Set[Variable] = { + for { + v <- exprOps.variablesOf(e) + v1 <- vars.getOrElse(v, Set(v)) + v2 <- env.targets.get(v1.toVal).map(_.map(_.receiver)).getOrElse(Set(v1)) + } yield v2 + } + // Note: we do not have to worry about reassignment (via FieldAssignment, ArrayUpdate, etc.) + // because these must be fresh expressions and therefore cannot add further aliasing. + def go(e: Expr, vars: Map[Variable, Set[Variable]]): Set[Variable] = e match { + case Let(vd, df, body) => + val dfTargets = getAllTargetsDealiased(df, env) + val dfVars = dfTargets match { + case Some(dfTargets) => + dfTargets.flatMap(t => vars.getOrElse(t.receiver, Set(t.receiver))) + case None => varsOfExprDealiasedWithLocalBdgs(df, vars) + } + go(body, vars + (vd.toVariable -> dfVars)) + case LetVar(vd, _, body) => + assert(!isMutableType(vd.tpe)) + // No special handling is needed since `vd` is immutable + go(body, vars) + case Block(_, last) => go(last, vars) + case LetRec(_, body) => go(body, vars) + case Ensuring(body, _) => go(body, vars) + case Assert(_, _, body) => go(body, vars) + case Assume(_, body) => go(body, vars) + case Decreases(_, body) => go(body, vars) + case Require(_, body) => go(body, vars) + case IfExpr(_, thn, elze) => go(thn, vars) ++ go(elze, vars) + case MatchExpr(_, cases) => cases.flatMap(mc => go(mc.rhs, vars)).toSet + case _ => varsOfExprDealiasedWithLocalBdgs(e, vars) + } + go(expr, Map.empty) + } + def assertReferentiallyTransparent(expr: Expr): Unit = { assert(isReferentiallyTransparent(expr), s"${expr.asString} is not referentially transparent") } @@ -849,11 +1104,12 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override throw MalformedStainlessCode(condition, s"Effects are not allowed in condition of effects: ${condition.asString}") case Target(receiver, Some(condition), path) => + val recRecv = rec(receiver, path.toSeq) Annotated( AsInstanceOf( IfExpr( condition.setPos(newValue), - rec(receiver, path.toSeq), + Annotated(recRecv, Seq(DropVCs)).copiedFrom(recRecv), receiver ).copiedFrom(newValue), receiver.getType @@ -1097,6 +1353,11 @@ class AntiAliasing(override val s: Trees)(override val t: s.type)(using override val (ctxI2, normI2) = normalizeSelector(i2) (ctxArr1 compose ctxI1 compose ctxArr2 compose ctxI2, Swap(normArr1, normI1, normArr2, normI2).copiedFrom(swp)) + case swp @ CellSwap(cell1, cell2) => + val (ctxCell1, normCell1) = normalizeForTarget(cell1) + val (ctxCell2, normCell2) = normalizeForTarget(cell2) + (ctxCell1 compose ctxCell2, CellSwap(normCell1, normCell2).copiedFrom(swp)) + case call: (Application | FunctionInvocation | ApplyLetRec) => normalizeCall(call) diff --git a/core/src/main/scala/stainless/extraction/imperative/EffectsAnalyzer.scala b/core/src/main/scala/stainless/extraction/imperative/EffectsAnalyzer.scala index 6a4f817c59..e81f38a7bc 100644 --- a/core/src/main/scala/stainless/extraction/imperative/EffectsAnalyzer.scala +++ b/core/src/main/scala/stainless/extraction/imperative/EffectsAnalyzer.scala @@ -392,7 +392,7 @@ trait EffectsAnalyzer extends oo.CachingPhase { case (tt: TupleType, TupleFieldAccessor(idx) +: xs) => 0 < idx && idx <= tt.dimension && rec(tt.bases(idx - 1), xs) - case (ArrayType(base), ArrayAccessor(idx) +: xs) => + case (ArrayType(base), (ArrayAccessor(_) | UnknownArrayAccessor) +: xs) => rec(base, xs) case (_, Nil) => @@ -698,9 +698,10 @@ trait EffectsAnalyzer extends oo.CachingPhase { case Old(_) => true case fi @ FunctionInvocation(id, _, _) if !symbols.isRecursive(id) => - BodyWithSpecs(symbols.simplifyLets(fi.inlined)) - .bodyOpt - .forall(isExpressionFresh) + val specced = BodyWithSpecs(symbols.simplifyLets(fi.inlined)) + specced.bodyOpt + .map(specced.wrapLets) + .forall(rec(_, bindings)) // other function invocations always return a fresh expression, by hypothesis (global assumption) case (_: FunctionInvocation | _: ApplyLetRec | _: Application) => true @@ -719,7 +720,9 @@ trait EffectsAnalyzer extends oo.CachingPhase { // For `Let`, it is safe to add `vd` as a fresh binding because we disallow // `FieldAssignments` with non-fresh expressions in `EffetsChecker.check(fd: FunAbstraction)`. // See discussion on: https://github.com/epfl-lara/stainless/pull/985#discussion_r614583479 - case Let(vd, e, b) => rec(e, bindings) && rec(b, bindings + vd) + case Let(vd, e, b) => + val eFresh = rec(e, bindings) + rec(b, if (eFresh) bindings + vd else bindings) // A `LetVar` can be fresh if `vd` is only assigned fresh values both // here and in all subsequent Assign statements. @@ -736,7 +739,12 @@ trait EffectsAnalyzer extends oo.CachingPhase { case Block(_, e) => rec(e, bindings) case IfExpr(_, e1, e2) => rec(e1, bindings) && rec(e2, bindings) - case MatchExpr(_, cases) => cases.forall(cse => rec(cse.rhs, bindings)) + case MatchExpr(scrut, cases) => + val scrutFresh = rec(scrut, bindings) + cases.forall { cse => + val extraBdgs = if (scrutFresh) cse.pattern.binders else Set.empty + rec(cse.rhs, bindings ++ extraBdgs) + } //any other expression is conservatively assumed to be non-fresh if //any sub-expression is non-fresh @@ -761,7 +769,7 @@ trait EffectsAnalyzer extends oo.CachingPhase { case _: (ArraySelect | MutableMapApply) => false case _: (Literal[t] | Lambda) => true case fi @ FunctionInvocation(_, _, _) => functionTypeEffects(fi.tfd.functionType).isEmpty - case _: (Application | ApplyLetRec | Swap | ArrayUpdate | MutableMapUpdate | FieldAssignment | Assignment) => false + case _: (Application | ApplyLetRec | Swap | CellSwap | ArrayUpdate | MutableMapUpdate | FieldAssignment | Assignment) => false case Operator(es, _) => es.forall(isReferentiallyTransparent) } @@ -818,6 +826,12 @@ trait EffectsAnalyzer extends oo.CachingPhase { effect(array1, env).map(_.precise(ArrayAccessor(index1))) ++ effect(array2, env).map(_.precise(ArrayAccessor(index2))) + case CellSwap(cell1, cell2) => + val vFieldId = symbols.lookup.get[ClassDef]("stainless.lang.Cell").get.fields.head.id + rec(cell1, env) ++ rec(cell2, env) ++ + effect(cell1, env).map(_.precise(ClassFieldAccessor(vFieldId))) ++ + effect(cell2, env).map(_.precise(ClassFieldAccessor(vFieldId))) + case ArrayUpdate(o, idx, v) => rec(o, env) ++ rec(idx, env) ++ rec(v, env) ++ effect(o, env).map(_.precise(ArrayAccessor(idx))) @@ -857,8 +871,8 @@ trait EffectsAnalyzer extends oo.CachingPhase { case FunctionInvocation(id, _, _) => Outer(getFunction(id)) case ApplyLetRec(id, _, _, _, _) => result.locals(id) } - - if (fun.flags.contains(IsPure)) Set() + val argsEff = args.flatMap(rec(_, env)).toSet + if (fun.flags.contains(IsPure)) argsEff else { val currentEffects: Set[Effect] = result.effects(fun) val paramSubst = (fun.params.map(_.toVariable) zip args).toMap @@ -869,7 +883,7 @@ trait EffectsAnalyzer extends oo.CachingPhase { val effectsOnLocalFreeVars = currentEffects.filterNot(e => paramSubst contains e.receiver) - invocEffects ++ effectsOnLocalFreeVars ++ args.flatMap(rec(_, env)) + invocEffects ++ effectsOnLocalFreeVars ++ argsEff } case Operator(es, _) => es.flatMap(rec(_, env)).toSet diff --git a/core/src/main/scala/stainless/extraction/imperative/EffectsChecker.scala b/core/src/main/scala/stainless/extraction/imperative/EffectsChecker.scala index f897efff79..c4105d3e50 100644 --- a/core/src/main/scala/stainless/extraction/imperative/EffectsChecker.scala +++ b/core/src/main/scala/stainless/extraction/imperative/EffectsChecker.scala @@ -104,16 +104,24 @@ trait EffectsChecker { self: EffectsAnalyzer => override def traverse(e: Expr): Unit = e match { case l @ Let(vd, e, b) => - if ( - (variablesOf(e) & variablesOf(b)).exists(v => isMutableType(v.tpe)) && - !isExpressionFresh(e) && - isMutableType(vd.tpe) - ) try { - // Check if precise targets can be computed - getAllTargets(e) - } catch { - case _: MalformedStainlessCode => - throw ImperativeEliminationException(e, "Illegal aliasing: " + e.asString) + lazy val commonVars = (variablesOf(e) & variablesOf(b)).filter(v => isMutableType(v.tpe)) + if (isMutableType(vd.tpe) && commonVars.nonEmpty && !isExpressionFresh(e)) { + try { + // Check if precise targets can be computed + getAllTargets(e) + } catch { + case _: MalformedStainlessCode => + val msg = + s"""Illegal aliasing: $e + |Hint: this error occurs due to: + | -the type of ${vd.id} (${vd.tpe}) being mutable + | -the definition of ${vd.id} not being fresh + | -the definition of ${vd.id} containing variables of mutable types + | that also appear after the declaration of ${vd.id}: + | ${commonVars.toSeq.sortBy(_.id).map(v => s"-${v.id} (of type ${v.tpe})").mkString("", "\n ", "")} + |""".stripMargin + throw ImperativeEliminationException(e, msg) + } } super.traverse(l) @@ -126,25 +134,26 @@ trait EffectsChecker { self: EffectsAnalyzer => case au @ ArrayUpdate(a, i, e) => if (isMutableType(e.getType) && !isExpressionFresh(e)) - throw ImperativeEliminationException(e, "Illegal aliasing: " + e.asString) + throw ImperativeEliminationException(e, s"Cannot update an array whose element type (${e.getType}) is mutable with a non-fresh expression") super.traverse(au) case au @ ArrayUpdated(a, i, e) => if (isMutableType(e.getType) && !isExpressionFresh(e)) - throw ImperativeEliminationException(e, "Illegal aliasing: " + e.asString) + throw ImperativeEliminationException(e, s"Cannot update an array whose element type (${e.getType}) is mutable with a non-fresh expression") super.traverse(au) case mu @ MapUpdated(m, k, e) => if (isMutableType(e.getType) && !isExpressionFresh(e)) - throw ImperativeEliminationException(e, "Illegal aliasing: " + e.asString) + throw ImperativeEliminationException(e, s"Cannot update a map whose value type (${e.getType}) is mutable with a non-fresh expression") super.traverse(mu) case fa @ FieldAssignment(o, sel, e) => - if (isMutableType(fa.getField.get.getType) && !isExpressionFresh(e)) - throw ImperativeEliminationException(e, "Illegal aliasing: " + e.asString) + val fdTpe = fa.getField.get.getType + if (isMutableType(fdTpe) && !isExpressionFresh(e)) + throw ImperativeEliminationException(e, s"Cannot update a field whose type ($fdTpe) is mutable with a non-fresh expression") super.traverse(fa) @@ -154,7 +163,7 @@ trait EffectsChecker { self: EffectsAnalyzer => case l @ Lambda(args, body) => if (isMutableType(body.getType) && !isExpressionFresh(body)) - throw ImperativeEliminationException(l, "Illegal aliasing in lambda body") + throw ImperativeEliminationException(l, s"Cannot create a lambda whose return type (${body.getType}) is mutable and whose body is non-fresh") if (effects(body).exists(e => !args.contains(e.receiver.toVal))) throw ImperativeEliminationException(l, "Illegal effects in lambda body") super.traverse(l) @@ -328,16 +337,80 @@ trait EffectsChecker { self: EffectsAnalyzer => def checkPurity(fd: FunAbstraction): Unit = { val effs = effects(fd.fullBody) + val callsToImpure = computeCallsToImpure(fd) + checkEnforcedPurity(fd, effs, callsToImpure) + checkPureParameters(fd, effs, callsToImpure) + } - if ((fd.flags contains IsPure) && !effs.isEmpty) - throw ImperativeEliminationException(fd, s"Functions marked @pure cannot have side-effects") + def checkEnforcedPurity(fd: FunAbstraction, effs: Set[Effect], callsToImpure: Seq[(Identifier, Set[Variable])]): Unit = { + val isPure = fd.flags contains IsPure + val isInv = fd.flags contains IsInvariant + if ((isPure || isInv) && effs.nonEmpty) { + val isInv = fd.flags contains IsInvariant + val fnName = if (isInv) "the invariant" else fd.id.asString + val head = Seq(if (isInv) "Invariants cannot have side-effects" else "Functions marked @pure cannot have side-effects") + val externs = callsToImpure.filter { case (id, _) => symbols.getFunction(id).flags contains Extern }.map(_._1).toSet + val hint1 = { + if (callsToImpure.isEmpty) Seq.empty[String] + else Seq(s"Hint: $fnName calls the following impure functions:") ++ + callsToImpure.map { case (id, _) => + val extra = if (externs(id)) " (an @extern function)" else "" + s" -${id.asString}$extra" + } + } + val hint2 = { + if (externs.isEmpty) Seq.empty + else Seq("Hint: @extern functions taking mutable types are considered as impure, unless annotated with @pure") + } + throw ImperativeEliminationException(fd, (head ++ hint1 ++ hint2).mkString("\n")) + } + } - effs filter (_.receiver.flags.contains(IsPure)) foreach { eff => - throw ImperativeEliminationException(fd, - s"Function `${fd.id.asString}` has effect on @pure parameter `${eff.receiver.asString}`") + def checkPureParameters(fd: FunAbstraction, effs: Set[Effect], callsToImpure: Seq[(Identifier, Set[Variable])]): Unit = { + val mutatedPure = effs.filter(_.receiver.flags.contains(IsPure)) + .map(_.receiver).toSeq.sortBy(v => fd.params.indexWhere(_.id == v)) + if (mutatedPure.nonEmpty) { + val head = Seq(s"Function `${fd.id.asString}` has effect on the following @pure parameters:", + mutatedPure.map(_.id.asString).mkString(" -", ", ", "")) + val externs = callsToImpure.filter { case (id, _) => symbols.getFunction(id).flags contains Extern }.map(_._1).toSet + val hint1 = mutatedPure.flatMap { v => + val fns = callsToImpure.filter(_._2(v)).map(_._1) + if (fns.isEmpty) Seq.empty + else Seq(s"Hint: ${v.id.asString} is modified by the calls to the following functions:") ++ + fns.map { id => + val extra = if (externs(id)) " (an @extern function)" else "" + s" -${id.asString}$extra" + } + } + val hint2 = { + if (externs.isEmpty) Seq.empty + else Seq("Hint: @extern functions taking mutable types are considered as impure, unless annotated with @pure") + } + throw ImperativeEliminationException(fd, (head ++ hint1 ++ hint2).mkString("\n")) } } + // Collect all impure functions called within `fd`, alongside the + // set of parameters of `fd` that are mutated by these calls. + // Note that if an impure function is called multiple times, + // we merge the set of mutated parameters. + def computeCallsToImpure(fd: FunAbstraction): Seq[(Identifier, Set[Variable])] = { + exprOps.collect { + case FunctionInvocation(id, _, args) => + val invokedFd = symbols.getFunction(id) + val effs = effects(invokedFd) + if (effs.isEmpty) Set.empty[(Identifier, Set[Variable])] + else { + // `effs` not only contains parameter variable but also locals, so we filter these out. + val ixs = effs.map(eff => invokedFd.params.indexWhere(_.id == eff.receiver.id)).filter(_ >= 0) + val affectedParams = ixs.flatMap(ix => exprOps.variablesOf(args(ix)) & fd.params.map(_.toVariable).toSet) + Set((id, affectedParams)) + } + case _ => Set.empty[(Identifier, Set[Variable])] + }(fd.fullBody).groupMapReduce(_._1)(_._2)(_ ++ _) + .toSeq.sortBy(_._1) + } + try { // We only check the bodies of functions which are not accessors if (!isAccessor(Outer(fd))) diff --git a/core/src/main/scala/stainless/extraction/imperative/GhostChecker.scala b/core/src/main/scala/stainless/extraction/imperative/GhostChecker.scala index 517e063cfe..e2185731ea 100644 --- a/core/src/main/scala/stainless/extraction/imperative/GhostChecker.scala +++ b/core/src/main/scala/stainless/extraction/imperative/GhostChecker.scala @@ -81,6 +81,9 @@ trait GhostChecker { self: EffectsAnalyzer => case FieldAssignment(_, _, _) => false case Block(_, e) => isGhostExpression(e) + // Invariants on `while` are allowed to be ghost + case While(cond, body, _, _, flags) => flags.contains(Ghost) || isGhostExpression(cond) || isGhostExpression(body) + case Operator(es, _) => es.exists(isGhostExpression) } diff --git a/core/src/main/scala/stainless/extraction/imperative/ImperativeCleanup.scala b/core/src/main/scala/stainless/extraction/imperative/ImperativeCleanup.scala index 28147c9034..908718e031 100644 --- a/core/src/main/scala/stainless/extraction/imperative/ImperativeCleanup.scala +++ b/core/src/main/scala/stainless/extraction/imperative/ImperativeCleanup.scala @@ -54,21 +54,25 @@ class ImperativeCleanup(override val s: Trees, override val t: oo.Trees) object ReconstructTuple { def unapply(e: s.Expr): Option[s.Expr] = e match { case s.Let(vd, tuple, Lets(lets, s.Tuple(es))) => - val letsMap = lets.map { case (vd, e) => (vd.id, e) }.toMap - if ( - vd.getType.isInstanceOf[s.TupleType] && - es.length == vd.getType.asInstanceOf[s.TupleType].bases.length && - es.zipWithIndex.forall { - case (e0 : s.Variable, i) => - letsMap.contains(e0.id) && - letsMap(e0.id) == s.TupleSelect(vd.toVariable, i + 1) - case (e0, i) => - e0 == s.TupleSelect(vd.toVariable, i + 1) - } - ) - Some(tuple) - else - None + vd.getType match { + case s.TupleType(bases) => + val letsMap = lets.map { case (vd, e) => (vd.id, e) }.toMap + // All let-bindings are used in the "returned" value `es` + lazy val returnedLets = letsMap.keySet.filter(id => es.exists { case v: s.Variable => v.id == id; case _ => false }) + // All values in `es` are reconstruction of selections of `vd` + lazy val esIsTupleSel = es.zipWithIndex.forall { + case (e0: s.Variable, i) => + letsMap.contains(e0.id) && + letsMap(e0.id) == s.TupleSelect(vd.toVariable, i + 1) + case (e0, i) => + e0 == s.TupleSelect(vd.toVariable, i + 1) + } + if (es.length == bases.length && returnedLets.size == letsMap.size && esIsTupleSel) + Some(tuple) + else None + case _ => + None + } case s.Let(vd, e, Lets(Seq(), v)) if v == vd.toVariable => Some(e) @@ -87,15 +91,15 @@ class ImperativeCleanup(override val s: Trees, override val t: oo.Trees) Some(s.Variable(id, tpe, flags filterNot isImperativeFlag).copiedFrom(expr)) case s.MutableMapWithDefault(from, to, default) => - Some(s.FiniteMap(Seq(), s.Application(default, Seq()), from, to)) - case s.MutableMapApply(map, index) => Some(s.MapApply(map, index)) - case s.MutableMapUpdated(map, key, value) => Some(s.MapUpdated(map, key, value)) + Some(s.FiniteMap(Seq(), s.Application(default, Seq()).copiedFrom(default), from, to).copiedFrom(expr)) + case s.MutableMapApply(map, index) => Some(s.MapApply(map, index).copiedFrom(expr)) + case s.MutableMapUpdated(map, key, value) => Some(s.MapUpdated(map, key, value).copiedFrom(expr)) case s.MutableMapDuplicate(map) => Some(map) case ReconstructTuple(tuple) => Some(tuple) case s.LetRec(fds, body) => - Some(s.LetRec(fds.map(fd => fd.copy(flags = fd.flags filterNot isImperativeFlag)), body)) + Some(s.LetRec(fds.map(fd => fd.copy(flags = fd.flags filterNot isImperativeFlag)), body).copiedFrom(expr)) case _ => None } } (expr)) diff --git a/core/src/main/scala/stainless/extraction/imperative/ImperativeCodeElimination.scala b/core/src/main/scala/stainless/extraction/imperative/ImperativeCodeElimination.scala index 468c107bfc..b44445562a 100644 --- a/core/src/main/scala/stainless/extraction/imperative/ImperativeCodeElimination.scala +++ b/core/src/main/scala/stainless/extraction/imperative/ImperativeCodeElimination.scala @@ -279,7 +279,7 @@ class ImperativeCodeElimination(override val s: Trees)(override val t: s.type) val newReturnType = TupleType(inner.returnType +: modifiedVars.map(_.tpe)) val newSpecs = specs.map { - case Postcondition(post @ Lambda(Seq(res), postBody)) => + case spec @ Postcondition(post @ Lambda(Seq(res), postBody)) => /* Essentially translates: (res: (R, T1, T2, ...)) => { @@ -304,7 +304,7 @@ class ImperativeCodeElimination(override val s: Trees)(override val t: s.type) case (body, (vr, ix)) => LetVar(vr.toVal, TupleSelect(newRes.toVariable, ix + 2), body) } } - Postcondition(Lambda(Seq(newRes), pcScope(pcRes)).setPos(post)) + Postcondition(Lambda(Seq(newRes), pcScope(pcRes)).copiedFrom(post)).setPos(spec) case spec => spec.transform { cond => val (res, scope, _) = toFunction(cond) @@ -327,11 +327,11 @@ class ImperativeCodeElimination(override val s: Trees)(override val t: s.type) } //TODO: no support for true mutual recursion - case LetRec(fds, b) => + case lr @ LetRec(fds, b) => if (fds.isEmpty) toFunction(b) else - toFunction(LetRec(Seq(fds.head), LetRec(fds.tail, b))) + toFunction(LetRec(Seq(fds.head), LetRec(fds.tail, b).copiedFrom(lr)).copiedFrom(lr)) //TODO: handle vars in scope, just like LetRec case ld @ Lambda(params, body) => @@ -437,7 +437,7 @@ class ImperativeCodeElimination(override val s: Trees)(override val t: s.type) fd.params.map(vd => Old(vd.toVariable) -> vd.toVariable).toMap, body ) - Postcondition(Lambda(params, toFn(newBody)).copiedFrom(ld)) + Postcondition(Lambda(params, toFn(newBody)).copiedFrom(ld)).setPos(spec) case spec => spec.transform(toFn) } diff --git a/core/src/main/scala/stainless/extraction/imperative/ReturnElimination.scala b/core/src/main/scala/stainless/extraction/imperative/ReturnElimination.scala index 8ec54cb037..385e094b9e 100644 --- a/core/src/main/scala/stainless/extraction/imperative/ReturnElimination.scala +++ b/core/src/main/scala/stainless/extraction/imperative/ReturnElimination.scala @@ -411,8 +411,8 @@ class ReturnElimination(override val s: Trees, override val t: Trees) case Seq(e) => transform(e, currentType) case e +: rest if exprHasReturn(e) => - val firstType = e.getType - val firstTypeChecked = simpleWhileTransformer.transform(e.getType) + val firstType = widenTp(e.getType) + val firstTypeChecked = simpleWhileTransformer.transform(firstType) val controlFlowVal = t.ValDef.fresh("cf", ControlFlowSort.controlFlow(retTypeChecked, firstTypeChecked) @@ -458,13 +458,15 @@ class ReturnElimination(override val s: Trees, override val t: Trees) case Seq() => recons(ids, tvs, tes, ttps, tflags) case e +: rest if !exprHasReturn(e) => // We use a let-binding here to preserve execution order. - val vd = t.ValDef.fresh("x", simpleWhileTransformer.transform(e.getType), true).copiedFrom(e) + val eTpe = widenTp(e.getType) + val vd = t.ValDef.fresh("x", simpleWhileTransformer.transform(eTpe), true).copiedFrom(e) t.Let(vd, simpleWhileTransformer.transform(e), rec(rest, tes :+ vd.toVariable)).copiedFrom(e) case e +: rest => - val firstType = simpleWhileTransformer.transform(e.getType) + val eTpe = widenTp(e.getType) + val firstType = simpleWhileTransformer.transform(eTpe) ControlFlowSort.andThen( retTypeChecked, firstType, currentTypeChecked, - transform(e, e.getType), + transform(e, eTpe), (v: t.Variable) => { val transformedRest = rec(rest, tes :+ v) if (rest.exists(exprHasReturn)) @@ -495,6 +497,19 @@ class ReturnElimination(override val s: Trees, override val t: Trees) (res, fnSum) } + // Recursively widen class types to their top ancestor, and strips sigma, pi and refinement types. + // This is used to type the control flow type parameters in order to avoid triggering AdtSpecialization + // generation of refinement types. + private def widenTp(tp: s.Type)(using s.Symbols): s.Type = { + s.typeOps.postMap { + case ct @ s.ClassType(_, _) => + val ancestors = ct.tcd.ancestors + if (ancestors.isEmpty) None else Some(ancestors.last.toType) + case tp @ (s.SigmaType(_, _) | s.PiType(_, _) | s.RefinementType(_, _)) => Some(tp.getType) + case _ => None + } (tp) + } + override def combineSummaries(summaries: AllSummaries): ExtractionSummary = { val (retFns, whileFns) = summaries.fnsSummary.foldLeft((Set.empty[Identifier], Set.empty[Identifier])) { case ((retAcc, whileAcc), FunctionSummary.ReturnOnlyTransformed(fid)) => (retAcc + fid, whileAcc) diff --git a/core/src/main/scala/stainless/extraction/imperative/TransformerWithType.scala b/core/src/main/scala/stainless/extraction/imperative/TransformerWithType.scala index 05d580760f..edc8101f55 100644 --- a/core/src/main/scala/stainless/extraction/imperative/TransformerWithType.scala +++ b/core/src/main/scala/stainless/extraction/imperative/TransformerWithType.scala @@ -53,6 +53,13 @@ trait TransformerWithType extends oo.TransformerWithType { transform(index2, s.Int32Type()), ).copiedFrom(expr) + case s.CellSwap(cell1, cell2) => + val at = widen(cell1.getType): @unchecked + t.CellSwap( + transform(cell1, at), + transform(cell2, at), + ).copiedFrom(expr) + case s.MutableMapWithDefault(from, to, default) => t.MutableMapWithDefault(transform(from), transform(to), transform(default, s.FunctionType(Seq(), to)) diff --git a/core/src/main/scala/stainless/extraction/imperative/Trees.scala b/core/src/main/scala/stainless/extraction/imperative/Trees.scala index 0c417d219b..2fb9382e10 100644 --- a/core/src/main/scala/stainless/extraction/imperative/Trees.scala +++ b/core/src/main/scala/stainless/extraction/imperative/Trees.scala @@ -33,6 +33,21 @@ trait Trees extends oo.Trees with Definitions { self => } } + /* Cell Operations */ + + /** Swap values from two (not necessarily distinct) cells */ + sealed case class CellSwap(cell1: Expr, cell2: Expr) extends Expr with CachingTyped { + override protected def computeType(using s: Symbols): Type = + val cellClassDef = s.lookup.get[ClassDef]("stainless.lang.Cell") + (cell1.getType, cell2.getType) match { + case (ClassType(id1, tps1), ClassType(id2, tps2)) if cellClassDef.isDefined && id1 == cellClassDef.get.id && id1 == id2 && tps1 == tps2 => { + UnitType() + } + case _ => + Untyped + } + } + /** $encodingof `{ expr1; expr2; ...; exprn; last }` */ case class Block(exprs: Seq[Expr], last: Expr) extends Expr with CachingTyped { protected def computeType(using Symbols): Type = if (exprs.forall(_.isTyped)) last.getType else Untyped @@ -278,6 +293,9 @@ trait Printer extends oo.Printer { case Swap(array1, index1, array2, index2) => p"swap($array1, $index1, $array2, $index2)" + case CellSwap(cell1, cell2) => + p"swap($cell1, $cell2)" + case LetVar(vd, value, expr) => p"""|var $vd = $value |$expr""" @@ -429,6 +447,9 @@ trait TreeDeconstructor extends oo.TreeDeconstructor { case s.Swap(array1, index1, array2, index2) => (Seq(), Seq(), Seq(array1, index1, array2, index2), Seq(), Seq(), (_, _, es, _, _) => t.Swap(es(0), es(1), es(2), es(3))) + case s.CellSwap(cell1, cell2) => + (Seq(), Seq(), Seq(cell1, cell2), Seq(), Seq(), (_, _, es, _, _) => t.CellSwap(es(0), es(1))) + case s.MutableMapWithDefault(from, to, default) => (Seq(), Seq(), Seq(default), Seq(from, to), Seq(), (_, _, es, tps, _) => t.MutableMapWithDefault(tps(0), tps(1), es(0))) diff --git a/core/src/main/scala/stainless/extraction/inlining/FunctionInlining.scala b/core/src/main/scala/stainless/extraction/inlining/FunctionInlining.scala index b903c8bed7..a3f0292d77 100644 --- a/core/src/main/scala/stainless/extraction/inlining/FunctionInlining.scala +++ b/core/src/main/scala/stainless/extraction/inlining/FunctionInlining.scala @@ -125,18 +125,24 @@ class FunctionInlining(override val s: Trees, override val t: trace.Trees) case None => context.reporter.fatalError("In FunctionInlining, all functions should have bodies thanks to ChooseEncoder running before.") } + exprOps.preTraversal { + // Set the position of all occurrences of parameters to the position of their argument + // For other expressions, we set the position to the call site + case v: Variable => + val ix = argBinders.indexWhere(_.id == v.id) + if (ix >= 0) { + // An `argBinder` + v.setPos(argBinders(ix)) + } else { + // Some local variable + v.setPos(fi) + } + case e => e.setPos(fi) + }(inlined) val result = (argBinders zip args).foldRight(inlined) { - case ((vd, e), body) => let(vd, e, body).setPos(fi) - } - val freshened = exprOps.freshenLocals(result) - - // For some common shims, fill in missing positions with the position of the inlined call site - if (isSynthetic && hasInlineOnceFlag) { - exprOps.preTraversal { - case e if !e.getPos.isDefined => e.setPos(fi) - case _ => - }(freshened) + case ((vd, e), body) => let(vd.copiedFrom(e), e, body).setPos(fi) } + val freshened = exprOps.freshenLocals(result).copiedFrom(fi) val inliner = new Inliner(if (hasInlineOnceFlag) inlinedOnce + tfd.id else inlinedOnce) inliner.transform(freshened) diff --git a/core/src/main/scala/stainless/extraction/innerfuns/Definitions.scala b/core/src/main/scala/stainless/extraction/innerfuns/Definitions.scala index 85ff8732ad..7ebfdb11d0 100644 --- a/core/src/main/scala/stainless/extraction/innerfuns/Definitions.scala +++ b/core/src/main/scala/stainless/extraction/innerfuns/Definitions.scala @@ -21,6 +21,15 @@ trait Definitions extends extraction.Trees { self: Trees => params.foldRight(typeOps.variablesOf(returnType) ++ exprOps.variablesOf(fullBody)) { case (vd, vars) => vars - vd.toVariable ++ typeOps.variablesOf(vd.tpe) } + + def copy( + id: Identifier = id, + tparams: Seq[TypeParameterDef] = tparams, + params: Seq[ValDef] = params, + returnType: Type = returnType, + fullBody: Expr = fullBody, + flags: Seq[Flag] = flags + ): LocalFunDef = LocalFunDef(id, tparams, params, returnType, fullBody, flags).copiedFrom(this) } @@ -92,7 +101,7 @@ trait Definitions extends extraction.Trees { self: Trees => def unapply(i: Outer): Some[FunDef] = Some(i.fd) } - // We do not define Inner as a case class because the inherited `copy` method conflics with the compiler-generated one. + // We do not define Inner as a case class because the inherited `copy` method conflicts with the compiler-generated one. class Inner(val fd: LocalFunDef) extends FunAbstraction( fd.id, fd.tparams, fd.params, fd.returnType, fd.fullBody, fd.flags) { setPos(fd) diff --git a/core/src/main/scala/stainless/extraction/oo/RefinementLifting.scala b/core/src/main/scala/stainless/extraction/oo/RefinementLifting.scala index c4846f7f59..4ab1034c6f 100644 --- a/core/src/main/scala/stainless/extraction/oo/RefinementLifting.scala +++ b/core/src/main/scala/stainless/extraction/oo/RefinementLifting.scala @@ -77,8 +77,10 @@ class RefinementLifting(override val s: Trees, override val t: Trees) val (Seq(nvd), pred2) = parameterConds(Seq(vd.copy(tpe = vd2.tpe).copiedFrom(vd))) (nvd, s.exprOps.replaceFromSymbols(Map(vd2 -> nvd.toVariable), s.and(pred, pred2))) - case _ => - (vd, s.BooleanLiteral(true).copiedFrom(vd)) + case t => + // Note: t may have been dealiased, hence we need to update the type of the variable + // so it is consistent with each of its occurrence in the body of the function + (vd.copy(tpe = t), s.BooleanLiteral(true).copiedFrom(vd)) }).unzip (newParams, s.andJoin(conds)) diff --git a/core/src/main/scala/stainless/extraction/oo/TransformerWithType.scala b/core/src/main/scala/stainless/extraction/oo/TransformerWithType.scala index 17f2919668..016f90d46d 100644 --- a/core/src/main/scala/stainless/extraction/oo/TransformerWithType.scala +++ b/core/src/main/scala/stainless/extraction/oo/TransformerWithType.scala @@ -197,17 +197,17 @@ trait TransformerWithType extends TreeTransformer { val atpe = getArithmeticType(lhs, tpe) t.BVLShiftRight(transform(lhs, atpe), transform(rhs, atpe)).copiedFrom(expr) - case s.BVNarrowingCast(expr, tpe) => - t.BVNarrowingCast(transform(expr), transform(tpe).asInstanceOf[t.BVType]).copiedFrom(expr) + case s.BVNarrowingCast(e, tpe) => + t.BVNarrowingCast(transform(e), transform(tpe).asInstanceOf[t.BVType]).copiedFrom(expr) - case s.BVWideningCast(expr, tpe) => - t.BVWideningCast(transform(expr), transform(tpe).asInstanceOf[t.BVType]).copiedFrom(expr) + case s.BVWideningCast(e, tpe) => + t.BVWideningCast(transform(e), transform(tpe).asInstanceOf[t.BVType]).copiedFrom(expr) - case s.BVUnsignedToSigned(expr) => - t.BVUnsignedToSigned(transform(expr)).copiedFrom(expr) + case s.BVUnsignedToSigned(e) => + t.BVUnsignedToSigned(transform(e)).copiedFrom(expr) - case s.BVSignedToUnsigned(expr) => - t.BVSignedToUnsigned(transform(expr)).copiedFrom(expr) + case s.BVSignedToUnsigned(e) => + t.BVSignedToUnsigned(transform(e)).copiedFrom(expr) case s.Tuple(es) => widen(tpe) match { case s.TupleType(tps) => t.Tuple((es zip tps) map (p => transform(p._1, p._2))).copiedFrom(expr) diff --git a/core/src/main/scala/stainless/extraction/oo/TypeOps.scala b/core/src/main/scala/stainless/extraction/oo/TypeOps.scala index 380c4b28a4..e1795c8d5e 100644 --- a/core/src/main/scala/stainless/extraction/oo/TypeOps.scala +++ b/core/src/main/scala/stainless/extraction/oo/TypeOps.scala @@ -122,6 +122,9 @@ trait TypeOps extends innerfuns.TypeOps { self => case (MapType(f1, t1), MapType(f2, t2)) if leastUpperBound(f1, f2) == greatestLowerBound(f1, f2) => Some(MapType(f1, typeBound(t1, t2, upper))) + case (ArrayType(b1), ArrayType(b2)) if leastUpperBound(b1, b2) == greatestLowerBound(b1, b2) => + Some(ArrayType(b1)) + case _ => None }).getOrElse(if (upper) AnyType() else NothingType()).getType diff --git a/core/src/main/scala/stainless/extraction/termination/.scalafmt.conf b/core/src/main/scala/stainless/extraction/termination/.scalafmt.conf index 5733b82e76..3390ad3af9 100644 --- a/core/src/main/scala/stainless/extraction/termination/.scalafmt.conf +++ b/core/src/main/scala/stainless/extraction/termination/.scalafmt.conf @@ -1,2 +1,3 @@ +runner.dialect = "scala3" align = some maxColumn = 110 diff --git a/core/src/main/scala/stainless/extraction/xlang/ConstructsUsage.scala b/core/src/main/scala/stainless/extraction/xlang/ConstructsUsage.scala index fbca337fef..ed8d6563aa 100644 --- a/core/src/main/scala/stainless/extraction/xlang/ConstructsUsage.scala +++ b/core/src/main/scala/stainless/extraction/xlang/ConstructsUsage.scala @@ -90,16 +90,14 @@ class ConstructsUsage(override val s: Trees)(override val t: s.type)(using overr case _ => sys.error(s"Expected to be in OuterClass or InnerClass env (but is in $this)") } - def isExternFunction: Boolean = { - val flags = this match { - case OuterFreeFun => syms.functions(outermostId).flags - case InnerFreeFun(lfd) => lfd.flags - case Method(m, _) => m.flags - case InnerOfMethod(lmd, _, _) => lmd.flags - case _ => Seq.empty - } - flags.contains(Extern) + def flags: Seq[Flag] = this match { + case OuterFreeFun => syms.functions(outermostId).flags + case InnerFreeFun(lfd) => lfd.flags + case Method(m, _) => m.flags + case InnerOfMethod(lmd, _, _) => lmd.flags + case _ => Seq.empty } + def isExternOrAbstractFunction: Boolean = flags.exists(f => f == IsAbstract || f == Extern) } def buildSummary: SummaryData = { @@ -142,9 +140,9 @@ class ConstructsUsage(override val s: Trees)(override val t: s.type)(using overr addUsedConstruct(UsedConstruct.Choose, env) super.traverse(e, env) case NoTree(_) => - // Note that @extern function have their bodies replaced with "???" by the frontend. - // Therefore, we do not report @extern functions (as they are already reported for being @extern). - if (!env.isExternFunction) { + // Note that @extern and abstract function have their bodies replaced with "???" by the frontend. + // We do not report @extern functions because they are already reported for being @extern. + if (!env.isExternOrAbstractFunction) { addUsedConstruct(UsedConstruct.NotImplemented, env) } super.traverse(e, env) diff --git a/core/src/main/scala/stainless/frontend/UserFiltering.scala b/core/src/main/scala/stainless/frontend/UserFiltering.scala index b6653e2600..e226b0e21f 100644 --- a/core/src/main/scala/stainless/frontend/UserFiltering.scala +++ b/core/src/main/scala/stainless/frontend/UserFiltering.scala @@ -25,7 +25,15 @@ class UserFiltering private(override val s: xt.type, val userIds = symbols.classes.values.filterNot(cd => cd.flags.exists(notUserFlag)).map(_.id) ++ symbols.functions.values.filterNot(fd => fd.flags.exists(notUserFlag)).filter(isInOptions).map(_.id) ++ - symbols.typeDefs.values.filterNot(td => td.flags.exists(notUserFlag)).map(_.id) + symbols.typeDefs.values.filterNot(td => td.flags.exists(notUserFlag)).map(_.id) ++ + // Also consider (outer) functions for which there is an inner function + // that should be kept according to `isInOptions` + symbols.functions.values.filter { fd => + xt.exprOps.exists { + case LetRec(inners, _) => inners.exists(i => isInOptions(i.id) && i.flags.forall(f => !notUserFlag(f))) + case _ => false + }(fd.fullBody) + }.map(_.id) val userDependencies = (userIds.flatMap(symbols.dependencies) ++ userIds).toSeq val keepGroups = context.options.findOptionOrDefault(optKeep) diff --git a/core/src/main/scala/stainless/genc/GenCComponent.scala b/core/src/main/scala/stainless/genc/GenCComponent.scala index 67eb7dc10d..53a46b1a19 100644 --- a/core/src/main/scala/stainless/genc/GenCComponent.scala +++ b/core/src/main/scala/stainless/genc/GenCComponent.scala @@ -131,7 +131,7 @@ case class GenCReport(results: Seq[Record], sources: Set[Identifier], override v override val name = GenCComponent.name override def annotatedRows: Seq[RecordRow] = results.map { - case Record(id, pos, status, time) => RecordRow(id, pos, levelOf(status), Seq(descriptionOf(status)), time) + case Record(id, pos, status, time) => RecordRow(id, pos, levelOf(status), Seq(descriptionOf(status)), time, None) } protected def build(results: Seq[Record], sources: Set[Identifier]): GenCReport = diff --git a/core/src/main/scala/stainless/genc/phases/Scala2IRPhase.scala b/core/src/main/scala/stainless/genc/phases/Scala2IRPhase.scala index d551ea2d05..6256b8fb09 100644 --- a/core/src/main/scala/stainless/genc/phases/Scala2IRPhase.scala +++ b/core/src/main/scala/stainless/genc/phases/Scala2IRPhase.scala @@ -930,6 +930,22 @@ private class S2IRImpl(override val s: tt.type, CIR.Assign(CIR.ArrayAccess(a2, i2), tmp), )) + case CellSwap(cell1, cell2) => + val cellBaseType = cell1.getType.asInstanceOf[ClassType].tps.head + val cellClassDef = symbols.lookup.get[ClassDef]("stainless.lang.Cell").get + val vFieldId: Identifier = cellClassDef.fields.head.id + val c1 = rec(cell1) + val c2 = rec(cell2) + val tmpId = rec(FreshIdentifier("tmp")) + val tmpVd = CIR.ValDef(tmpId, rec(cellBaseType), false) + val tmp = CIR.Binding(tmpVd) + CIR.buildBlock(Seq( + CIR.Decl(tmpVd, Some(CIR.FieldAccess(c2, rec(vFieldId, withUnique = false)))), + CIR.Assign(CIR.FieldAccess(c2, rec(vFieldId, withUnique = false)), CIR.FieldAccess(c1, rec(vFieldId, withUnique = false))), + CIR.Assign(CIR.FieldAccess(c1, rec(vFieldId, withUnique = false)), tmp), + )) + + case array @ FiniteArray(elems, base) => val arrayType = CIR.ArrayType(rec(base), None) val length = elems.size diff --git a/core/src/main/scala/stainless/termination/SelfCallsProcessor.scala b/core/src/main/scala/stainless/termination/SelfCallsProcessor.scala index 611f7e3475..887516171c 100644 --- a/core/src/main/scala/stainless/termination/SelfCallsProcessor.scala +++ b/core/src/main/scala/stainless/termination/SelfCallsProcessor.scala @@ -5,7 +5,7 @@ package termination import scala.collection.mutable.{Set => MutableSet} -class SelfCallsProcessor(chker: ProcessingPipeline) extends Processor("Self Calls Processor", chker) { +class SelfCallsProcessor(override val checker: ProcessingPipeline) extends Processor("Self Calls Processor", checker) { import checker._ import checker.context._ import checker.program._ diff --git a/core/src/main/scala/stainless/termination/TerminationReport.scala b/core/src/main/scala/stainless/termination/TerminationReport.scala index 728852cf62..682c9f25ba 100644 --- a/core/src/main/scala/stainless/termination/TerminationReport.scala +++ b/core/src/main/scala/stainless/termination/TerminationReport.scala @@ -68,7 +68,7 @@ class TerminationReport(val results: Seq[TerminationReport.Record], val sources: val symbol = if (status.isTerminating) "\u2713" else "\u2717" val extra = Seq(s"$symbol") - RecordRow(id, pos, level, extra, 0L) + RecordRow(id, pos, level, extra, 0L, None) } private def levelOf(status: Status) = status match { diff --git a/core/src/main/scala/stainless/testgen/GenCTestGen.scala b/core/src/main/scala/stainless/testgen/GenCTestGen.scala index 75d8df66c0..064c8d1da4 100644 --- a/core/src/main/scala/stainless/testgen/GenCTestGen.scala +++ b/core/src/main/scala/stainless/testgen/GenCTestGen.scala @@ -19,7 +19,7 @@ object GenCTestGen { (res: Map[VC[p.trees.type], VCResult[p.Model]]) (using ctx: inox.Context): Unit = { val counterExs = res.toSeq.collect { - case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _)) => (vc, model) + case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _, _)) => (vc, model) } if (counterExs.isEmpty) { return diff --git a/core/src/main/scala/stainless/testgen/ScalaTestGen.scala b/core/src/main/scala/stainless/testgen/ScalaTestGen.scala index 198583db8b..77c17e1084 100644 --- a/core/src/main/scala/stainless/testgen/ScalaTestGen.scala +++ b/core/src/main/scala/stainless/testgen/ScalaTestGen.scala @@ -25,7 +25,7 @@ object ScalaTestGen { (res: Map[VC[p.trees.type], VCResult[p.Model]]) (using ctx: inox.Context): Unit = { val counterExs = res.toSeq.collect { - case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _)) => (vc, model) + case (vc, VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), _, _, _)) => (vc, model) } if (counterExs.isEmpty) { return diff --git a/core/src/main/scala/stainless/utils/CheckFilter.scala b/core/src/main/scala/stainless/utils/CheckFilter.scala index a256865bd7..64ed4dbaa0 100644 --- a/core/src/main/scala/stainless/utils/CheckFilter.scala +++ b/core/src/main/scala/stainless/utils/CheckFilter.scala @@ -15,7 +15,7 @@ trait CheckFilter { functions map CheckFilter.fullNameToPath } - private def isInOptions(fid: Identifier): Boolean = pathsOpt match { + def isInOptions(fid: Identifier): Boolean = pathsOpt match { case None => true case Some(paths) => // Support wildcard `_` as specified in the documentation. @@ -47,13 +47,42 @@ trait CheckFilter { case trees.Derived(None) => true case _ => false } - - val init = ids.flatMap(id => symbols.lookupFunction(id).toSeq).filter(shouldBeChecked).map(_.id).toSet + val keptFromIds = ids.flatMap(id => symbols.lookupFunction(id).toSeq).filter(shouldBeChecked).map(_.id).toSet + // Computes all transitively derived functions (values) from a given one (key) + // NOTE: `fixpoint` is a generic utility and uses equality to determine whether we have reached a fixpoint + // Consider a more optimized solution if this causes significant slowdowns. + val derivedFrom = inox.utils.fixpoint { (acc: Map[Identifier, Set[Identifier]]) => + symbols.functions.values.foldLeft(acc) { + case (acc, fd) => + val origins = fd.flags.flatMap { + case trees.Derived(Some(orig)) => Some(orig) + case _ => None + }.toSet + val transitiveOrigins = origins ++ acc.filter(_._2.intersect(origins).nonEmpty).keySet + transitiveOrigins.foldLeft(acc) { + case (acc, origin) => acc.updatedWith(origin)(_.map(_ + fd.id).orElse(Some(Set(fd.id)))) + } + } + } (Map.empty) + + // Take all functions that are derived from the ones found in `ids` and that should be checked for. + // This takes care of inner functions. + // Note that `ids` comes from syms.function where the `syms` are *before* the lowering phase + // and filtered according to `UserFiltering`. + // This means in particular that inner functions are not hoisted and will not be contained in `ids`. + // Here, the symbols are the lowered one, so the inner functions are hoisted and contained in `symbols.function` + // `UserFiltering` will also make sure to go through the definition of the (outer) functions + // and include those that contain at least one inner function that should be kept (according to --functions) + val derivedKept = ids.flatMap(derivedFrom.getOrElse(_, Set.empty).filter(derived => shouldBeChecked(symbols.functions(derived)))) + val init = keptFromIds ++ derivedKept val toCheck = inox.utils.fixpoint { (ids: Set[Identifier]) => + // Note that we do not filter derived functions and always consider them + // for checking since the user is expecting these to be checked + // (e.g. when checking for a function containing a while loop, the + // function derived from the while loop should also be checked). ids ++ symbols.functions.values.toSeq .filter(isDerivedFrom(ids)) - .filter(shouldBeChecked) .map(_.id) } (init) diff --git a/core/src/main/scala/stainless/utils/FileWatcher.scala b/core/src/main/scala/stainless/utils/FileWatcher.scala index 692839a16f..c8c450ad82 100644 --- a/core/src/main/scala/stainless/utils/FileWatcher.scala +++ b/core/src/main/scala/stainless/utils/FileWatcher.scala @@ -5,7 +5,7 @@ package stainless.utils import java.io.{ File, PrintWriter } import java.util.concurrent.TimeUnit -import scala.collection.JavaConverters._ +import scala.jdk.CollectionConverters._ import scala.collection.mutable.{ Map => MutableMap } import scala.io.{Source, BufferedSource} import scala.concurrent.Future diff --git a/core/src/main/scala/stainless/utils/Serialization.scala b/core/src/main/scala/stainless/utils/Serialization.scala index 545d2dc19b..fbad83ae8e 100644 --- a/core/src/main/scala/stainless/utils/Serialization.scala +++ b/core/src/main/scala/stainless/utils/Serialization.scala @@ -99,9 +99,9 @@ class XLangSerializer(override val trees: extraction.xlang.Trees, serializeProdu /** An extension to the set of registered classes in the `StainlessSerializer`. * occur within Stainless programs. * - * The new identifiers in the mapping range from 180 to 261. + * The new identifiers in the mapping range from 180 to 262. * - * NEXT ID: 262 + * NEXT ID: 263 */ override protected def classSerializers: Map[Class[_], Serializer[_]] = super.classSerializers ++ Map( @@ -146,6 +146,7 @@ class XLangSerializer(override val trees: extraction.xlang.Trees, serializeProdu stainlessClassSerializer[MutableMapUpdated] (252), stainlessClassSerializer[MutableMapDuplicate] (253), stainlessClassSerializer[Swap] (259), + stainlessClassSerializer[CellSwap] (262), stainlessClassSerializer[FreshCopy] (260), stainlessClassSerializer[Reads] (182), stainlessClassSerializer[Modifies] (210), diff --git a/core/src/main/scala/stainless/verification/AssertionInjector.scala b/core/src/main/scala/stainless/verification/AssertionInjector.scala index bd80f61fa9..8ccd093e93 100644 --- a/core/src/main/scala/stainless/verification/AssertionInjector.scala +++ b/core/src/main/scala/stainless/verification/AssertionInjector.scala @@ -72,6 +72,17 @@ class AssertionInjector(override val s: ast.Trees, override val t: ast.Trees, va ).copiedFrom(e) }}} + case s.LargeArray(elems, default, size, base) => + val recElems = elems.view.mapValues(transform).toMap + val recDefault = transform(default) + bindIfCannotDuplicate(size, "sz") { sz => + t.Assert( + t.GreaterEquals(sz, t.Int32Literal(0)), + Some("Non-negative array size"), + t.LargeArray(recElems, recDefault, sz, transform(base)).copiedFrom(e) + ).copiedFrom(e) + } + case sel @ s.ADTSelector(recv, selector) => if (sel.constructor.sort.constructors.size == 1) t.ADTSelector(transform(recv), selector).copiedFrom(e) diff --git a/core/src/main/scala/stainless/verification/CoqVerificationChecher.scala b/core/src/main/scala/stainless/verification/CoqVerificationChecher.scala index 0f318a4e4e..a4cfd1cf93 100644 --- a/core/src/main/scala/stainless/verification/CoqVerificationChecher.scala +++ b/core/src/main/scala/stainless/verification/CoqVerificationChecher.scala @@ -38,7 +38,7 @@ trait CoqVerificationChecker { self => val pCoq = CoqEncoder.transformProgram(program, context) CoqIO.makeOutputDirectory() val files = CoqIO.writeToCoqFile(pCoq.map { case (id, name, com) => (name, com) } ) - val unknownResult: VCResult = VCResult(VCStatus.Unknown, None, None) + val unknownResult: VCResult = VCResult(VCStatus.Unknown, None, None, None) val vcs: Seq[VC] = pCoq map { case(fun, _, _) => VC(getFunction(fun).fullBody, fun, VCKind.CoqMethod, true).setPos(symbols.getFunction(fun)) } @@ -76,7 +76,7 @@ trait CoqVerificationChecker { self => case Failure(e) => reporter.internalError(e) } - vc -> VCResult(vcres, None, Some(time)) + vc -> VCResult(vcres, None, Some(time), None) }}.toMap initMap ++ res } diff --git a/core/src/main/scala/stainless/verification/TypeChecker.scala b/core/src/main/scala/stainless/verification/TypeChecker.scala index b6a833ebeb..d50c813b0b 100644 --- a/core/src/main/scala/stainless/verification/TypeChecker.scala +++ b/core/src/main/scala/stainless/verification/TypeChecker.scala @@ -648,8 +648,8 @@ class TypeChecker(val program: StainlessProgram, val context: inox.Context, val case m: MatchExpr => inferType(tc, matchToIfThenElse(e, false)) case IfExpr(b, e1, e2) => - val (tpe1, tr1) = inferType(tc.withTruth(b).setPos(e1), e1) - val (tpe2, tr2) = inferType(tc.withTruth(Not(b)).setPos(e2), e2) + val (tpe1, tr1) = inferType(tc.withTruth(b).withLastPos(e1), e1) + val (tpe2, tr2) = inferType(tc.withTruth(Not(b)).withLastPos(e2), e2) (ite(b, tpe1, tpe2), checkType(tc.setPos(b).withVCKind(VCKind.CheckType), b, BooleanType()) ++ tr1 ++ tr2) case Error(tpe, descr) => @@ -717,7 +717,7 @@ class TypeChecker(val program: StainlessProgram, val context: inox.Context, val val trValue = checkType(tc.setPos(value).withVCKind(VCKind.CheckType), value, vd.tpe) val (tc2, id2) = tc.freshBindWithValue(vd, value) val freshBody: Expr = Substituter(immutable.Map(vd.id -> id2)).transform(body) - val (tpe, trBody) = inferType(tc2.setPos(body), freshBody) + val (tpe, trBody) = inferType(tc2.withLastPos(body), freshBody) (insertFreshLets(Seq(vd), Seq(value), tpe), trValue ++ trBody) case Assume(cond, body) => @@ -1082,7 +1082,7 @@ class TypeChecker(val program: StainlessProgram, val context: inox.Context, val val (tc2, id2) = tc.freshBindWithValue(vd, value) val freshBody: Expr = Substituter(immutable.Map(vd.id -> id2)).transform(body) checkType(tc.setPos(value).withVCKind(VCKind.CheckType), value, vd.tpe) ++ - checkType(tc2.setPos(body), freshBody, tpe) + checkType(tc2.withLastPos(body), freshBody, tpe) case (Assert(cond, optErr, body), _) => val kind = VCKind.fromErr(optErr) @@ -1094,8 +1094,8 @@ class TypeChecker(val program: StainlessProgram, val context: inox.Context, val case (IfExpr(b, e1, e2), _) => checkType(tc.setPos(b).withVCKind(VCKind.CheckType), b, BooleanType()) ++ - checkType(tc.withTruth(b).setPos(e1), e1, tpe) ++ - checkType(tc.withTruth(Not(b)).setPos(e2), e2, tpe) + checkType(tc.withTruth(b).withLastPos(e1), e1, tpe) ++ + checkType(tc.withTruth(Not(b)).withLastPos(e2), e2, tpe) case (e, TrueBoolean()) => checkType(tc.withVCKind(VCKind.CheckType), e, BooleanType()) ++ buildVC(tc, e) diff --git a/core/src/main/scala/stainless/verification/TypeCheckerContext.scala b/core/src/main/scala/stainless/verification/TypeCheckerContext.scala index f5de3e38bb..eeca6e1525 100644 --- a/core/src/main/scala/stainless/verification/TypeCheckerContext.scala +++ b/core/src/main/scala/stainless/verification/TypeCheckerContext.scala @@ -101,6 +101,8 @@ object TypeCheckerContext { ) } + def withLastPos(e: Expr): TypingContext = setPos(lastPos(e)) + def withTypeVariables(vars: Set[TypeParameter])(using opts: PrinterOptions, ctx: inox.Context): TypingContext = { checkFreshTypeVariables(vars) copy(typeVariables = typeVariables ++ vars).setPos(this) diff --git a/core/src/main/scala/stainless/verification/TypeCheckerUtils.scala b/core/src/main/scala/stainless/verification/TypeCheckerUtils.scala index f076f0ca94..7e45df207b 100644 --- a/core/src/main/scala/stainless/verification/TypeCheckerUtils.scala +++ b/core/src/main/scala/stainless/verification/TypeCheckerUtils.scala @@ -222,4 +222,14 @@ object TypeCheckerUtils { } def pred(e: Expr) = Minus(e, IntegerLiteral(1)) + + def lastPos(e: Expr): inox.utils.Position = e match { + case Assert(_, _, body) => lastPos(body) + case Assume(_, body) => lastPos(body) + case Require(_, body) => lastPos(body) + case Decreases(_, body) => lastPos(body) + case Ensuring(body, _) => lastPos(body) + case Let(_, _, body) => lastPos(body) + case _ => e.getPos + } } diff --git a/core/src/main/scala/stainless/verification/VerificationAnalysis.scala b/core/src/main/scala/stainless/verification/VerificationAnalysis.scala index a7934a9d0a..88f11ccc52 100644 --- a/core/src/main/scala/stainless/verification/VerificationAnalysis.scala +++ b/core/src/main/scala/stainless/verification/VerificationAnalysis.scala @@ -22,7 +22,8 @@ trait VerificationAnalysis extends AbstractAnalysis { val time = vr.time.getOrElse(0L) // TODO make time mandatory (?) val status = VerificationReport.Status(program)(vr.status) val source = symbols.getFunction(vc.fid).source - VerificationReport.Record(vc.fid, vc.getPos, time, status, vr.solverName, vc.kind.name, derivedFrom = source) + val smtFileId = vr.smtLibFileId + VerificationReport.Record(vc.fid, vc.getPos, time, status, vr.solverName, vc.kind.name, derivedFrom = source, smtId = smtFileId) } override val name = VerificationComponent.name diff --git a/core/src/main/scala/stainless/verification/VerificationCache.scala b/core/src/main/scala/stainless/verification/VerificationCache.scala index 8fb7529494..dfed78709a 100644 --- a/core/src/main/scala/stainless/verification/VerificationCache.scala +++ b/core/src/main/scala/stainless/verification/VerificationCache.scala @@ -61,7 +61,7 @@ trait VerificationCache extends VerificationChecker { self => debugVC(vc, origVC) reporter.debug("--------------") } - VCResult(VCStatus.ValidFromCache, None, None) + VCResult(VCStatus.ValidFromCache, None, None, None) } else { reporter.synchronized { reporter.debug(s"Cache miss: '${vc.kind}' VC for ${vc.fid.asString} @${vc.getPos}...") @@ -94,7 +94,7 @@ trait VerificationCache extends VerificationChecker { self => // Update the result with the correct processing time tryResult match { case Failure(e) => reporter.internalError(e) - case Success(VCResult(status, solver, _)) => VCResult(status, solver, Some(time)) + case Success(VCResult(status, solver, _, smtLibFileId)) => VCResult(status, solver, Some(time), smtLibFileId) } } } diff --git a/core/src/main/scala/stainless/verification/VerificationChecker.scala b/core/src/main/scala/stainless/verification/VerificationChecker.scala index b7fc05f077..933580ee8c 100644 --- a/core/src/main/scala/stainless/verification/VerificationChecker.scala +++ b/core/src/main/scala/stainless/verification/VerificationChecker.scala @@ -117,7 +117,7 @@ trait VerificationChecker { self => } } - private lazy val unknownResult: VCResult = VCResult(VCStatus.Unknown, None, None) + private lazy val unknownResult: VCResult = VCResult(VCStatus.Unknown, None, None, None) def checkVCs(vcs: Seq[VC], stopWhen: VCResult => Boolean = defaultStop): Future[Map[VC, VCResult]] = { if (!VerificationChecker.startedVerification.getAndSet(true)) reporter.info("Starting verification...") @@ -280,7 +280,7 @@ trait VerificationChecker { self => import SolverResponses._ val cond = vc.condition if (cond == BooleanLiteral(true)) { - return VCResult(VCStatus.Trivial, None, None) + return VCResult(VCStatus.Trivial, None, None, None) } val s = sf.getNewSolver() @@ -300,10 +300,10 @@ trait VerificationChecker { self => s.check(Model) } } - + val vcres = tryRes match { case _ if interruptManager.isInterrupted => - VCResult(VCStatus.Cancelled, Some(s.name), Some(time)) + VCResult(VCStatus.Cancelled, Some(s.name), Some(time), s.getSmtLibFileId) case Success(res) => res match { case Unknown => @@ -313,35 +313,35 @@ trait VerificationChecker { self => case _ => VCStatus.Unknown } case _ => VCStatus.Unknown - }, Some(s.name), Some(time)) + }, Some(s.name), Some(time), s.getSmtLibFileId) case Unsat if !vc.satisfiability => - VCResult(VCStatus.Valid, s.getResultSolver.map(_.name), Some(time)) + VCResult(VCStatus.Valid, s.getResultSolver.map(_.name), Some(time), s.getSmtLibFileId) case SatWithModel(model) if checkModels && vc.kind.isInstanceOf[VCKind.AdtInvariant] => val VCKind.AdtInvariant(invId) = vc.kind: @unchecked val status = checkAdtInvariantModel(vc, invId, model) - VCResult(status, s.getResultSolver.map(_.name), Some(time)) + VCResult(status, s.getResultSolver.map(_.name), Some(time), s.getSmtLibFileId) case SatWithModel(model) if !vc.satisfiability => - VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), s.getResultSolver.map(_.name), Some(time)) + VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), s.getResultSolver.map(_.name), Some(time), s.getSmtLibFileId) case Sat if vc.satisfiability => - VCResult(VCStatus.Valid, s.getResultSolver.map(_.name), Some(time)) + VCResult(VCStatus.Valid, s.getResultSolver.map(_.name), Some(time), s.getSmtLibFileId) case Unsat if vc.satisfiability => - VCResult(VCStatus.Invalid(VCStatus.Unsatisfiable), s.getResultSolver.map(_.name), Some(time)) + VCResult(VCStatus.Invalid(VCStatus.Unsatisfiable), s.getResultSolver.map(_.name), Some(time), s.getSmtLibFileId) case _ => sys.error(s"Unreachable: $res") } case Failure(u: inox.Unsupported) => reporter.warning(u.getMessage) - VCResult(VCStatus.Unsupported, Some(s.name), Some(time)) + VCResult(VCStatus.Unsupported, Some(s.name), Some(time), s.getSmtLibFileId) case Failure(e @ NotWellFormedException(d, info)) => reporter.error(d.getPos, e.getMessage) - VCResult(VCStatus.Crashed, Some(s.name), Some(time)) + VCResult(VCStatus.Crashed, Some(s.name), Some(time), s.getSmtLibFileId) case Failure(e) => reporter.internalError(e) } diff --git a/core/src/main/scala/stainless/verification/VerificationComponent.scala b/core/src/main/scala/stainless/verification/VerificationComponent.scala index f0bf555b48..c6a83541bf 100644 --- a/core/src/main/scala/stainless/verification/VerificationComponent.scala +++ b/core/src/main/scala/stainless/verification/VerificationComponent.scala @@ -116,11 +116,11 @@ class VerificationRun private(override val component: VerificationComponent.type val opaqueEncoder = inox.transformers.ProgramEncoder(vcGenEncoder.targetProgram)(OpaqueChooseInjector(vcGenEncoder.targetProgram)) val res: Future[Map[VC[p.trees.type], VCResult[p.Model]]] = if (context.options.findOptionOrDefault(optAdmitVCs)) { - Future(vcs.map(vc => vc -> VCResult(VCStatus.Admitted, None, None)).toMap) + Future(vcs.map(vc => vc -> VCResult(VCStatus.Admitted, None, None, None)).toMap) } else { VerificationChecker.verify(opaqueEncoder.targetProgram, context)(vcs).map(_.view.mapValues { - case VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), s, t) => - VCResult(VCStatus.Invalid(VCStatus.CounterExample(model.encode(opaqueEncoder.reverse.andThen(vcGenEncoder.reverse)))), s, t) + case VCResult(VCStatus.Invalid(VCStatus.CounterExample(model)), s, t, smtid) => + VCResult(VCStatus.Invalid(VCStatus.CounterExample(model.encode(opaqueEncoder.reverse.andThen(vcGenEncoder.reverse)))), s, t, smtid) case res => res.asInstanceOf[VCResult[p.Model]] }.toMap) } diff --git a/core/src/main/scala/stainless/verification/VerificationConditions.scala b/core/src/main/scala/stainless/verification/VerificationConditions.scala index a20bc4ddca..b89db0bdf0 100644 --- a/core/src/main/scala/stainless/verification/VerificationConditions.scala +++ b/core/src/main/scala/stainless/verification/VerificationConditions.scala @@ -112,7 +112,8 @@ object VCStatus { case class VCResult[+Model]( status: VCStatus[Model], solverName: Option[String], - time: Option[Long] + time: Option[Long], + smtLibFileId: Option[Int] ) { def isValid = status == VCStatus.Valid || isValidFromCache || isTrivial def isValidFromCache = status == VCStatus.ValidFromCache diff --git a/core/src/main/scala/stainless/verification/VerificationReport.scala b/core/src/main/scala/stainless/verification/VerificationReport.scala index b3820fc86a..76872cb9b3 100644 --- a/core/src/main/scala/stainless/verification/VerificationReport.scala +++ b/core/src/main/scala/stainless/verification/VerificationReport.scala @@ -51,7 +51,7 @@ object VerificationReport { case class Record( id: Identifier, pos: inox.utils.Position, time: Long, status: Status, solverName: Option[String], kind: String, - derivedFrom: Identifier + derivedFrom: Identifier, smtId: Option[Int] ) extends AbstractReportHelper.Record given recordDecoder: Decoder[Record] = deriveDecoder @@ -84,12 +84,12 @@ class VerificationReport(val results: Seq[VerificationReport.Record], val source override val name = VerificationComponent.name override lazy val annotatedRows = results map { - case Record(id, pos, time, status, solverName, kind, _) => + case Record(id, pos, time, status, solverName, kind, _, smtId) => val level = levelOf(status) val solver = solverName getOrElse "" val extra = Seq(kind, status.name, solver) - RecordRow(id, pos, level, extra, time) + RecordRow(id, pos, level, extra, time, smtId) } diff --git a/core/src/sphinx/gettingstarted.rst b/core/src/sphinx/gettingstarted.rst index 1d2c72f94b..7758004303 100644 --- a/core/src/sphinx/gettingstarted.rst +++ b/core/src/sphinx/gettingstarted.rst @@ -29,7 +29,7 @@ distributed with Stainless: .. code-block:: bash - $ stainless --solvers=smt-cvc4 frontends/benchmarks/verification/invalid/InsertionSort.scala + $ stainless --solvers=smt-cvc5 frontends/benchmarks/verification/invalid/InsertionSort.scala and get something like this (some output cropped) diff --git a/core/src/sphinx/imperative.rst b/core/src/sphinx/imperative.rst index 96ed4f7015..819ea74877 100644 --- a/core/src/sphinx/imperative.rst +++ b/core/src/sphinx/imperative.rst @@ -10,6 +10,8 @@ On the technical side, these extensions do not have specific treatment in the back-end of Stainless. Instead, they are desugared into :doc:`Pure Scala ` constructs during a preprocessing phase in the Stainless front-end. +These transformations are partly documented in the `EPFL PhD thesis of Régis Blanc `_. + Imperative Code --------------- @@ -227,6 +229,114 @@ properties: assert(x == 2) } +Another useful and similar construct is ``snapshot`` that semantically makes a deep copy of a mutable object. +Contrarily to ``old``, ``snapshot`` allows to refer to the state of an object prior to its mutation within +the body of the function, as long as it is used in a :doc:`ghost context `. + +For instance: + +.. code-block:: scala + + def updateArray(a: Array[BigInt], i: Int, x: BigInt): Unit = { + require(0 <= i && i < a.length - 1) + require(a(i) == 0 && a(i + 1) == 0) + @ghost val a0 = snapshot(a) + a(i) = x + // a0 is unaffected by the update of a + // Note: using StaticChecks assert, which introduces a ghost context + assert(a0(i) == 0 && a(i) == x) + @ghost val a1 = snapshot(a) + a(i + 1) = 2 * x + assert(a1(i + 1) == 0 && a(i + 1) == 2 * x) + } + + +Extern functions and abstract methods +------------------------------------- + +``@extern`` functions and abstract methods of non-sealed trait taking mutable objects as parameters are treated as-if +they were applying arbitrary modifications to them. +For instance, the assertions in the following snippet are invalid: + +.. code-block:: scala + + @extern + def triple(mc: MutableClass): BigInt = ??? + + trait UnsealedTrait { + def quadruple(mc: MutableClass): BigInt + } + + def test1(mc: MutableClass): Unit = { + val i = mc.i + triple(mc) + assert(i == mc.i) // Invalid, mc.i could be anything + } + + def test2(ut: UnsealedTrait, mc: MutableClass): Unit = { + val i = mc.i + ut.quadruple(mc) + assert(i == mc.i) // Invalid as well + } + +Annotating such methods or functions with ``@pure`` tells Stainless to assume the parameters are not mutated: + +.. code-block:: scala + + case class MutableClass(var i: BigInt) + + @pure @extern + def triple(mc: MutableClass): BigInt = ??? + + trait UnsealedTrait { + @pure + def quadruple(mc: MutableClass): BigInt + } + + def test1(mc: MutableClass): Unit = { + val i = mc.i + triple(mc) + assert(i == mc.i) // Ok + } + + def test2(ut: UnsealedTrait, mc: MutableClass): Unit = { + val i = mc.i + ut.quadruple(mc) + assert(i == mc.i) // Ok + } + +Note that Stainless will enforce purity for visible implementations of ``quadruple``. + +Sometimes, a method or ``@extern`` function may mutate some parameters but not all of them. +In such cases, the untouched parameters can be annotated with ``@pure``: + +.. code-block:: scala + + case class MutableClass(var i: BigInt) + + @extern + def sum(@pure mc1: MutableClass, mc2: MutableClass): BigInt = ??? + + trait UnsealedTrait { + def doubleSum(@pure mc1: MutableClass, mc2: MutableClass): BigInt + } + + def test1(mc1: MutableClass, mc2: MutableClass): Unit = { + val i1 = mc1.i + val i2 = mc2.i + sum(mc1, mc2) + assert(i1 == mc1.i) // Ok + assert(i2 == mc2.i) // Invalid, mc2.i may have any value + } + + def test2(ut: UnsealedTrait, mc1: MutableClass, mc2: MutableClass): Unit = { + val i1 = mc1.i + val i2 = mc2.i + ut.doubleSum(mc1, mc2) + assert(i1 == mc1.i) // Ok + assert(i2 == mc2.i) // Invalid + } + Trait Variables --------------- diff --git a/core/src/sphinx/installation.rst b/core/src/sphinx/installation.rst index a72d2e6f88..63e5cf670e 100644 --- a/core/src/sphinx/installation.rst +++ b/core/src/sphinx/installation.rst @@ -19,7 +19,7 @@ Stainless bundles Scala compiler front-end and runs it before it starts compilat Use Standalone Release (recommended) ------------------------------------ -1. Download the latest Stainless release from the `Releases page on GitHub `_, under the **Assets** section. Make sure to pick the appropriate ZIP for your operating system. This release is bundled with Z3 4.8.14. +1. Download the latest Stainless release from the `Releases page on GitHub `_, under the **Assets** section. Make sure to pick the appropriate ZIP for your operating system. This release is bundled with Z3 4.12.2 and cvc5 1.0.8. 2. Unzip the the file you just downloaded to a directory. @@ -45,7 +45,7 @@ Use Standalone Release (recommended) .. code-block:: bash - $ /path/to/unzipped/directory/stainless.sh HelloStainless.scala + $ /path/to/unzipped/directory/stainless HelloStainless.scala 6. The output should read: @@ -192,27 +192,27 @@ where ``X.Y.Z`` is the Stainless version and ``A-BCDEFGHI`` is some hash (which External Solver Binaries ------------------------ -If no external SMT solvers (such as Z3 or CVC4) are found, Stainless will use the bundled Scala-based `Princess solver `_ +If no external SMT solvers (such as Z3 or cvc5) are found, Stainless will use the bundled Scala-based `Princess solver `_ To improve performance, we highly recommend that you install the following two additional external SMT solvers as binaries for your platform: -* CVC4 1.8, http://cvc4.cs.stanford.edu -* Z3 4.8.14, https://github.com/Z3Prover/z3 +* cvc5 1.0.8, https://cvc5.github.io/ +* Z3 4.12.2, https://github.com/Z3Prover/z3 -You can enable these solvers using ``--solvers=smt-z3`` and ``--solvers=smt-cvc4`` flags. +You can enable these solvers using ``--solvers=smt-z3`` and ``--solvers=smt-cvc5`` flags. -Solver binaries that you install should match your operating system and your architecture. We recommend that you install these solvers as a binary and have their binaries available in the ``$PATH`` (as ``z3`` or ``cvc4``). +Solver binaries that you install should match your operating system and your architecture. We recommend that you install these solvers as a binary and have their binaries available in the ``$PATH`` (as ``z3`` or ``cvc5``). Note that somewhat lower version numbers of solvers should work as well and might even have different sets of soundness-related issues. -You can use multiple solvers in portfolio mode, as with the options ``--timeout=15 --solvers=smt-z3,smt-cvc4``, where verification succeeds if at least one of the solvers proves (within the given number of seconds) each the verification conditions. We suggest to order the solvers starting from the one most likely to succeed quickly. +You can use multiple solvers in portfolio mode, as with the options ``--timeout=15 --solvers=smt-z3,smt-cvc5``, where verification succeeds if at least one of the solvers proves (within the given number of seconds) each the verification conditions. We suggest to order the solvers starting from the one most likely to succeed quickly. For final verification runs of highly critical software, we recommend that (instead of the portfolio mode) you obtain several solvers and their versions, then try a single solver at a time and ensure that each verification run succeeds (thus applying N-version programming to SMT solver implementations). -Install Z3 4.8.14 (Linux & macOS) -********************************* +Install Z3 4.12.2 +***************** -1. Download Z3 4.8.14 from https://github.com/Z3Prover/z3/releases/tag/z3-4.8.14 +1. Download Z3 4.12.2 from https://github.com/Z3Prover/z3/releases/tag/z3-4.12.2 2. Unzip the downloaded archive 3. Copy the ``z3`` binary found in the ``bin/`` directory of the inflated archive to a directory in your ``$PATH``, eg., ``/usr/local/bin``. 4. Make sure ``z3`` can be found, by opening a new terminal window and typing: @@ -225,44 +225,27 @@ Install Z3 4.8.14 (Linux & macOS) .. code-block:: text - Z3 version 4.8.14 - 64 bit` + Z3 version 4.12.2 - 64 bit` -Install CVC 1.8 (Linux) -*********************** +Install cvc5 1.0.8 +****************** -1. Download CVC4 1.8 from http://cvc4.cs.stanford.edu/downloads/builds/x86_64-linux-opt/ (reachable from https://cvc4.github.io/ ) +1. Download cvc5 1.0.8 from https://github.com/cvc5/cvc5/releases/tag/cvc5-1.0.8 for your platform. -2. Copy or link the downloaded binary under name ``cvc4`` to a directory in your ``$PATH``, eg., ``/usr/local/bin``. +2. Copy or link the downloaded binary under name ``cvc5`` to a directory in your ``$PATH``, eg., ``/usr/local/bin``. -4. Make sure ``cvc4`` can be found, by opening a new terminal window and typing: +4. Make sure ``cvc5`` can be found, by opening a new terminal window and typing: .. code-block:: bash - $ cvc4 --version | head + $ cvc5 --version | head 5. The output should begin with: .. code-block:: text - This is CVC4 version 1.8 - -Install CVC 1.6 (macOS) -*********************** - -1. Install `Homebrew `_ -2. Install CVC4 using the Homebrew tap at https://github.com/CVC4/homebrew-cvc4 -3. Make sure ``cvc4`` can be found, by opening a new terminal window and typing: - -.. code-block:: bash - - $ cvc4 --version - -4. The output should begin with: - -.. code-block:: text - - This is CVC4 version 1.6 + This is cvc5 version 1.0.8 Build from Source on Linux & macOS @@ -273,7 +256,7 @@ in an attempt to be more reproducible and independent from SBT cache and path, t **Install SBT** -Follow the instructions at http://www.scala-sbt.org/ to install ``sbt`` 1.5.6 (or somewhat later version). +Follow the instructions at http://www.scala-sbt.org/ to install ``sbt`` 1.7.3 (or somewhat later version). **Check out sources** @@ -333,7 +316,7 @@ Instead, please use ``sbt stainless-scalac-standalone/assembly`` as follows: $ sbt stainless-scalac-standalone/assembly // takes about 1 minutes -Running Stainless can then be done with the command: ``java -jar frontends\stainless-dotty-standalone\target\scala-3.0.2\stainless-dotty-standalone-{VERSION}.jar``, where ``VERSION`` denotes Stainless version. +Running Stainless can then be done with the command: ``java -jar frontends\stainless-dotty-standalone\target\scala-3.3.3\stainless-dotty-standalone-{VERSION}.jar``, where ``VERSION`` denotes Stainless version. Running Tests ------------- diff --git a/core/src/sphinx/options.rst b/core/src/sphinx/options.rst index 0e95eff886..ec5017e56e 100644 --- a/core/src/sphinx/options.rst +++ b/core/src/sphinx/options.rst @@ -134,11 +134,11 @@ These options are available to all Stainless components: Native Z3 with z3-templates for unfolding recursive functions (default). - * ``smt-cvc4`` + * ``smt-cvc5`` - CVC4 through SMT-LIB. An algorithm within Stainless takes up the unfolding + cvc5 through SMT-LIB. An algorithm within Stainless takes up the unfolding of recursive functions, handling of lambdas etc. To use this or any - of the following CVC4-based solvers, you need to have the ``cvc4`` + of the following cvc5-based solvers, you need to have the ``cvc5`` executable in your system path (the latest unstable version is recommended). * ``smt-z3`` @@ -146,7 +146,7 @@ These options are available to all Stainless components: Z3 through SMT-LIB. To use this or the next solver, you need to have the ``z3`` executable in your program path (the latest stable version is recommended). Inductive reasoning happens on the Stainless side - (similarly to ``smt-cvc4``). + (similarly to ``smt-cvc5``). * ``unrollz3`` @@ -276,12 +276,12 @@ Unrolling Solver -CVC4 Solver +cvc5 Solver *********** -* ``--solver:cvc4=`` +* ``--solver:cvc5=`` - Pass extra command-line arguments to CVC4. + Pass extra command-line arguments to cvc5. diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index d5402e8c34..2c841c8829 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,5 +1,61 @@ # Release Notes +## Version 0.9.8.7 (2024-05-06) + +### Stainless frontend, library and internals + +- Add `stainless.lang.Try` allowing for a monadic try construct (#1515) +- Upgrade the Scala 3 frontend to Scala 3.3.3 (#1514 and #1516) +- Avoid rebuilding mutated objects when possible in AntiAliasing (#1507 and #1509) +- Show the ID of the corresponding generated SMT-lib file for each VC in the summary when `debug=smt` option is enabled (#1508) +- Have `isExpressionFresh` consider arguments when computing freshness of a function call (#1505) +- Avoid pairwise matching of "external" methods in Equivalence checking mode (#1503) +- Fix in documentation (#1502) +- Add support for exported methods (#1501) +- Fix ImperativeCleanup phase to not clean some unused bindings that should not be removed (#1500) +- Fix a bug in Equivalence checking mode that was considering the wrong preconditions in some cases (#1499) +- Fix type alias of opaque triggering missing dependencies (#1498) +- Add missing dropVCs annotation in AntiAliasing (#1496) +- Relax fallback condition of AntiAliasing handling of Let (#1495) +- Add missing case for Array typeBounds, make private final, fix minor bug in RefinementLifting (#1494) +- Fix MatchError in EffectsAnalyzer leading to a crash in some cases (#1492) +- Fix parametric extension methods (#1490) +- Allow for redundant type checks for pattern matching (#1489) +- Add 'unknown safety' category for Equivalence checkign (#1488) +- Allows for let-binding in tests and output 'expected but got' results in Equivalence checking (#1485) +- Add a tail recursive implementation of `map` for mutable tail lists as a benchmark (#1484) + +### Build + +- Fix the `stainless-cli` script (#1486) + +## Version 0.9.8.2 (2023-11-10) + +### Stainless frontend, library and internals + +- Add `stainless.lang.Cell` and `stainless.lang.swap`, allowing to swap the content of two `Cells` (#1461) +- Add support for cvc5 (#1444) +- Upgrade the Scala 3 frontend to Scala 3.3 (#1442) +- Upgrade the Scala 2 frontend to Scala 2.13.12 (#1469) +- Support for `new Array(len)` constructor for primitive types (#1453) +- Remove ensuring clause in ghost elimination (#1454) +- Fix while loops being mistakenly considered as ghost (#1467) +- Fix occasionally incorrect positions (#1447, #1455, #1473, #1477) +- Enforce purity for class invariants (#1475) +- Do not treat inline methods or functions as ghost (#1481) +- Applying some type widening in `ReturnElimination` to avoid triggering `AdtSpecialization` (#1466) +- Relax freshness condition and improve aliasing error messages (#1468) + +### Build + +- Upgrade the shipped Z3 version (`smt-z3`) to 4.12.2 (#1469) +- Add cvc5 1.0.8 to the archive (#1469) +- Add arm64 build for macOS (#1469) +- Add the compiled and source code of the Stainless library to the archive, under `lib` (#1469) +- Add a `stainless-cli` script to invoke scala-cli with the library (#1469) +- Rename `stainless.sh` to `stainless` (#1469) + + ## Version 0.9.8.1 (2023-09-21) ### Stainless frontend, library and internals diff --git a/frontends/benchmarks/dotty-specific/invalid/ExportedMethods1.scala b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods1.scala new file mode 100644 index 0000000000..42150aeca2 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods1.scala @@ -0,0 +1,20 @@ +object ExportedMethods1 { + case class Counter(var x: BigInt) { + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt): Unit = { + add(y) // invalid + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/invalid/ExportedMethods2.scala b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods2.scala new file mode 100644 index 0000000000..cabeaf8df5 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods2.scala @@ -0,0 +1,11 @@ +object ExportedMethods2 { + import ExportedMethodsExt.SimpleCounter.* + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt): Unit = { + add(y) // invalid + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/invalid/ExportedMethods3.scala b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods3.scala new file mode 100644 index 0000000000..cdad66e335 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/invalid/ExportedMethods3.scala @@ -0,0 +1,11 @@ +object ExportedMethods3 { + import ExportedMethodsExt.CounterWithInvariant.* + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt): Unit = { + x = y + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/invalid/ExportedMethodsExt.scala b/frontends/benchmarks/dotty-specific/invalid/ExportedMethodsExt.scala new file mode 100644 index 0000000000..09594e14d2 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/invalid/ExportedMethodsExt.scala @@ -0,0 +1,87 @@ +object ExportedMethodsExt { + + // This object is used for the other ExportedMethods + // but we need to have at least one invalid VC to pass the "invalid" test suite + def dummyInvalid(x: BigInt): Unit = { + assert(x == 0) + } + + object SimpleCounter { + case class Counter(var x: BigInt) { + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object CounterWithInvariant { + case class Counter(var x: BigInt) { + require(x >= 0) + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object AbstractCounter { + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object AbstractBaseAndCounter { + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + abstract case class Base() { + val cnt: Counter + export cnt.* + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/invalid/i1479.scala b/frontends/benchmarks/dotty-specific/invalid/i1479.scala new file mode 100644 index 0000000000..58a776e2d1 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/invalid/i1479.scala @@ -0,0 +1,15 @@ +object i1479 { + case class MyClass(var x: BigInt, var y: BigInt, var isValid: Boolean = true) { + require(!isValid || x <= y) + } + + inline def changeMyClass(m: MyClass)(inline body: => Unit): Unit = + m.isValid = false // Note that we may not have mc.x <= mc.y so this is invalid + body + m.isValid = true + + def inc(m: MyClass): Unit = + changeMyClass(m): + m.x += 1 + m.y += 1 +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/ExportedMethods.scala b/frontends/benchmarks/dotty-specific/valid/ExportedMethods.scala new file mode 100644 index 0000000000..78c1f97c16 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/ExportedMethods.scala @@ -0,0 +1,384 @@ +object ExportedMethods { + object Local { + object SimpleCounter { + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Counter(var x: BigInt) { + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + + def useCounterFromBase(y: BigInt): Unit = { + require(y >= 0) + x += y + val abc = x + add(y) + } + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + object CounterWithInvariant { + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Counter(var x: BigInt) { + require(x >= 0) + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + + def useCounterFromBase(y: BigInt): Unit = { + require(y >= 0) + x += y + val abc = x + add(y) + } + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + + object AbstractCounter { + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + + def useCounterFromBase(y: BigInt): Unit = { + require(y >= 0) + x += y + val abc = x + add(y) + } + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + + object AbstractBaseAndCounter { + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + abstract case class Base() { + val cnt: Counter + export cnt.* + + def useCounterFromBase(y: BigInt): Unit = { + require(y >= 0) + x += y + val abc = x + add(y) + } + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + } + + object Ext { + object SimpleCounter { + import ExportedMethodsExt.SimpleCounter.* + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + + object CounterWithInvariant { + import ExportedMethodsExt.CounterWithInvariant.* + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + + object AbstractCounter { + import ExportedMethodsExt.AbstractCounter.* + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + + object AbstractBaseAndCounter { + import ExportedMethodsExt.AbstractBaseAndCounter.* + + def accessExported(w: Wrapper, y: BigInt): Unit = { + require(y >= 0) + w.add(y) + val abc = w.x + w.x = y + } + + def useCounterFromBaseOutside(b: Base, y: BigInt): Unit = { + require(y >= 0) + b.x += y + val abc = b.x + b.add(y) + } + + case class Wrapper(base: Base) { + export base.* + + def addWith(y: BigInt, z: BigInt): Unit = { + require(y >= 0) + require(z >= 0) + x = z + val abc = x + add(y) + add(z) + } + + def parametricAddWith[T](y: BigInt, t: T): Unit = { + require(y >= 0) + parametricAdd(y, t) + } + } + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/ExportedMethodsExt.scala b/frontends/benchmarks/dotty-specific/valid/ExportedMethodsExt.scala new file mode 100644 index 0000000000..37106f179b --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/ExportedMethodsExt.scala @@ -0,0 +1,81 @@ +object ExportedMethodsExt { + + object SimpleCounter { + case class Counter(var x: BigInt) { + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object CounterWithInvariant { + case class Counter(var x: BigInt) { + require(x >= 0) + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object AbstractCounter { + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + case class Base(cnt: Counter) { + export cnt.* + } + } + + object AbstractBaseAndCounter { + abstract case class Counter() { + var x: BigInt + + def add(y: BigInt): Unit = { + require(y >= 0) + x += y + } + + def parametricAdd[T](y: BigInt, t: T): Unit = { + require(y >= 0) + x += y + } + } + + abstract case class Base() { + val cnt: Counter + export cnt.* + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/MutableMapTR.scala b/frontends/benchmarks/dotty-specific/valid/MutableMapTR.scala new file mode 100644 index 0000000000..04ce8e378f --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/MutableMapTR.scala @@ -0,0 +1,40 @@ +import scala.annotation.tailrec +import stainless.lang.* +import stainless.collection.* +import stainless.annotation.* + +object MTailList: + sealed abstract class MutableList[T] + case class MNil[T]() extends MutableList[T] + case class MCons[T](val hd: T, var tail: MutableList[T]) extends MutableList[T] + + extension[T] (ml: MutableList[T]) + @pure + def toList: List[T] = + ml match + case MNil() => Nil() + case MCons(h, t) => Cons(h, t.toList) + + def mapTR[T,U](l: MutableList[T], f: T => U): MutableList[U] = { + l match + case MNil() => MNil[U]() + case MCons(hd, tl) => + val acc: MCons[U] = MCons[U](f(hd), MNil()) + mapTRWorker[T,U](tl, f, acc) + acc + } ensuring(_.toList == l.toList.map(f)) + + @tailrec + def mapTRWorker[T,U]( + l: MutableList[T], + f: T => U, + acc: MCons[U] + ): Unit = { + require(acc.tail == MNil[U]()) + l match + case MNil() => () + case MCons(h, t) => + acc.tail = MCons[U](f(h), MNil()) + mapTRWorker(t, f, acc.tail.asInstanceOf[MCons[U]]) + assert(acc.tail.asInstanceOf[MCons[U]].toList == f(h) :: t.toList.map(f)) + } ensuring(_ => acc.toList == old(acc).hd :: l.toList.map(f)) \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/ParametricExtensionMetho.scala b/frontends/benchmarks/dotty-specific/valid/ParametricExtensionMetho.scala new file mode 100644 index 0000000000..c802167f51 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/ParametricExtensionMetho.scala @@ -0,0 +1,16 @@ +object ParametricExtensionMetho { + sealed trait Opt[+T] + case object Non extends Opt[Nothing] // Where is Oui? + final case class Som[+T](content: T) extends Opt[T] + + extension[T](m: Opt[T]) + def flatMap[U](f: T => Opt[U]): Opt[U] = + m match + case Non => Non + case Som(t) => f(t) + + def test[A, B](a: A, f: A => B): Unit = + assert(Som(a).flatMap(a => Som(f(a))) == Som(f(a))) + assert(Som(a).flatMap(_ => Non) == Non) + assert((Non : Opt[A]).flatMap(a => Som(f(a))) == Non) +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/TypeAliasOpaque.scala b/frontends/benchmarks/dotty-specific/valid/TypeAliasOpaque.scala new file mode 100644 index 0000000000..6dad9c3a9f --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/TypeAliasOpaque.scala @@ -0,0 +1,26 @@ +object TypeAliasOpaque { + object OpaqueLongWrap { + opaque type OpaqueLong = Long + extension (ol: OpaqueLong) { + def toLong: Long = ol + } + object OpaqueLong { + def fromLong(l: Long): OpaqueLong = l + } + } + + import OpaqueLongWrap.* + + type AliasedOpaqueLong = OpaqueLong + type Tayst = Int + + def test1(x: AliasedOpaqueLong): Unit = { + assert(OpaqueLong.fromLong(x.toLong) == x) + } + + def test2(x: Long): Unit = { + assert(OpaqueLong.fromLong(x).toLong == x) + } + + def mkAliasedOpaqueLong(x: Long): AliasedOpaqueLong = OpaqueLong.fromLong(x).ensuring(res => res.toLong == x) +} \ No newline at end of file diff --git a/frontends/benchmarks/dotty-specific/valid/i1479.scala b/frontends/benchmarks/dotty-specific/valid/i1479.scala new file mode 100644 index 0000000000..5cab60ae49 --- /dev/null +++ b/frontends/benchmarks/dotty-specific/valid/i1479.scala @@ -0,0 +1,17 @@ +object i1479 { + case class MyClass(var x: BigInt, var y: BigInt, var isValid: Boolean = true) { + require(!isValid || x <= y) + } + + inline def changeMyClass(m: MyClass)(inline body: => Unit): Unit = + require(m.isValid) + m.isValid = false + body + m.isValid = true + + def inc(m: MyClass): Unit = + require(m.isValid) + changeMyClass(m): + m.x += 1 + m.y += 1 +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/addHorn/expected_outcome.json b/frontends/benchmarks/equivalence/addHorn/expected_outcome.json deleted file mode 100644 index 3bb0c4abc4..0000000000 --- a/frontends/benchmarks/equivalence/addHorn/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "AddHorn.add_horn_1", - "functions": [ - "AddHorn.add_horn_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/addHorn/test_conf.json b/frontends/benchmarks/equivalence/addHorn/test_conf.json index 73e7f72641..3d374e8853 100644 --- a/frontends/benchmarks/equivalence/addHorn/test_conf.json +++ b/frontends/benchmarks/equivalence/addHorn/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "AddHorn.add_horn_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "AddHorn.add_horn_1", + "functions": [ + "AddHorn.add_horn_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/boardgame/expected_outcome_1.json b/frontends/benchmarks/equivalence/boardgame/expected_outcome_1.json deleted file mode 100644 index 3e0aeb507c..0000000000 --- a/frontends/benchmarks/equivalence/boardgame/expected_outcome_1.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.validCitySettlement", - "functions": [ - "Candidate1.validCitySettlement" - ] - } - ], - "unequivalent": [ - "Candidate2.validCitySettlement", - "Candidate3.validCitySettlement", - "Candidate4.validCitySettlement" - ], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/boardgame/expected_outcome_2.json b/frontends/benchmarks/equivalence/boardgame/expected_outcome_2.json deleted file mode 100644 index 8fe13de836..0000000000 --- a/frontends/benchmarks/equivalence/boardgame/expected_outcome_2.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.adjacencyBonus2", - "functions": [ - "Candidate1.adjacencyBonus" - ] - }, - { - "model": "Model.adjacencyBonus1", - "functions": [ - "Candidate2.adjacencyBonus" - ] - } - ], - "unequivalent": [ - "Candidate4.adjacencyBonus" - ], - "unsafe": [ - "Candidate3.adjacencyBonus" - ], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/boardgame/test_conf_1.json b/frontends/benchmarks/equivalence/boardgame/test_conf_1.json index 46834a115b..b3df7b3d2d 100644 --- a/frontends/benchmarks/equivalence/boardgame/test_conf_1.json +++ b/frontends/benchmarks/equivalence/boardgame/test_conf_1.json @@ -10,5 +10,24 @@ ], "tests": [ "Model.testsValidCitySettlement" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.validCitySettlement", + "functions": [ + "Candidate1.validCitySettlement" + ] + } + ], + "unequivalent": [ + "Candidate2.validCitySettlement", + "Candidate3.validCitySettlement", + "Candidate4.validCitySettlement" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/boardgame/test_conf_2.json b/frontends/benchmarks/equivalence/boardgame/test_conf_2.json index 6a839debd4..2517ee5e2f 100644 --- a/frontends/benchmarks/equivalence/boardgame/test_conf_2.json +++ b/frontends/benchmarks/equivalence/boardgame/test_conf_2.json @@ -11,5 +11,30 @@ ], "tests": [ "Model.testsAdjacencyBonus" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.adjacencyBonus2", + "functions": [ + "Candidate1.adjacencyBonus" + ] + }, + { + "model": "Model.adjacencyBonus1", + "functions": [ + "Candidate2.adjacencyBonus" + ] + } + ], + "unequivalent": [ + "Candidate4.adjacencyBonus" + ], + "unsafe": [ + "Candidate3.adjacencyBonus" + ], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/cloudSculpting/expected_outcome.json b/frontends/benchmarks/equivalence/cloudSculpting/expected_outcome.json deleted file mode 100644 index 27859b02f3..0000000000 --- a/frontends/benchmarks/equivalence/cloudSculpting/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.sculpteurDeNuage", - "functions": [ - "Candidate.sculpteurDeNuage" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/cloudSculpting/test_conf.json b/frontends/benchmarks/equivalence/cloudSculpting/test_conf.json index 546e496e65..ec368fc8bf 100644 --- a/frontends/benchmarks/equivalence/cloudSculpting/test_conf.json +++ b/frontends/benchmarks/equivalence/cloudSculpting/test_conf.json @@ -6,5 +6,20 @@ "Candidate.sculpteurDeNuage" ], "tests": [], - "max-perm": 144 + "max-perm": 144, + "outcome": { + "equivalent": [ + { + "model": "Model.sculpteurDeNuage", + "functions": [ + "Candidate.sculpteurDeNuage" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/dup/expected_outcome.json b/frontends/benchmarks/equivalence/dup/expected_outcome.json deleted file mode 100644 index 87b8a66ba6..0000000000 --- a/frontends/benchmarks/equivalence/dup/expected_outcome.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.dup", - "functions": [ - "Candidate1.dup", - "Candidate5.dup" - ] - } - ], - "unequivalent": [ - "Candidate2.dup", - "Candidate4.dup" - ], - "unsafe": [], - "timeout": [], - "wrong": [ - "Candidate3.dup" - ] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/dup/test_conf.json b/frontends/benchmarks/equivalence/dup/test_conf.json index bf1b2b0f62..00030d5c5e 100644 --- a/frontends/benchmarks/equivalence/dup/test_conf.json +++ b/frontends/benchmarks/equivalence/dup/test_conf.json @@ -10,5 +10,26 @@ "Candidate5.dup" ], "tests": [], - "norm": "Model.norm" + "norm": "Model.norm", + "outcome": { + "equivalent": [ + { + "model": "Model.dup", + "functions": [ + "Candidate1.dup", + "Candidate5.dup" + ] + } + ], + "unequivalent": [ + "Candidate2.dup", + "Candidate4.dup" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [ + "Candidate3.dup" + ] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/factorial/expected_outcome.json b/frontends/benchmarks/equivalence/factorial/expected_outcome.json deleted file mode 100644 index 07681f07a4..0000000000 --- a/frontends/benchmarks/equivalence/factorial/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Factorial.fact14_1", - "functions": [ - "Factorial.fact14_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/factorial/test_conf.json b/frontends/benchmarks/equivalence/factorial/test_conf.json index 761a7d7390..d9c658f3f5 100644 --- a/frontends/benchmarks/equivalence/factorial/test_conf.json +++ b/frontends/benchmarks/equivalence/factorial/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Factorial.fact14_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Factorial.fact14_1", + "functions": [ + "Factorial.fact14_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/fibonacci/expected_outcome.json b/frontends/benchmarks/equivalence/fibonacci/expected_outcome.json deleted file mode 100644 index 150b63e095..0000000000 --- a/frontends/benchmarks/equivalence/fibonacci/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Fibonacci.f1", - "functions": [ - "Fibonacci.f2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/fibonacci/test_conf.json b/frontends/benchmarks/equivalence/fibonacci/test_conf.json index 7d988fdbfc..5868cb1465 100644 --- a/frontends/benchmarks/equivalence/fibonacci/test_conf.json +++ b/frontends/benchmarks/equivalence/fibonacci/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Fibonacci.f2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Fibonacci.f1", + "functions": [ + "Fibonacci.f2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/foolproof/expected_outcome.json b/frontends/benchmarks/equivalence/foolproof/expected_outcome.json deleted file mode 100644 index f56ad0fd91..0000000000 --- a/frontends/benchmarks/equivalence/foolproof/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.funnyZip", - "functions": [ - "Candidate.funnyZip" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/foolproof/test_conf.json b/frontends/benchmarks/equivalence/foolproof/test_conf.json index 4934488db4..5f93a8d4cb 100644 --- a/frontends/benchmarks/equivalence/foolproof/test_conf.json +++ b/frontends/benchmarks/equivalence/foolproof/test_conf.json @@ -7,5 +7,20 @@ ], "unequivalent": [], "unsafe": [], - "tests": [] + "tests": [], + "outcome": { + "equivalent": [ + { + "model": "Model.funnyZip", + "functions": [ + "Candidate.funnyZip" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/fullAlternation/expected_outcome.json b/frontends/benchmarks/equivalence/fullAlternation/expected_outcome.json deleted file mode 100644 index def07e8ce0..0000000000 --- a/frontends/benchmarks/equivalence/fullAlternation/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "FullAlternation.m1", - "functions": [ - "FullAlternation.m2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/fullAlternation/test_conf.json b/frontends/benchmarks/equivalence/fullAlternation/test_conf.json index 17c09be5b2..9e7b8bb4d0 100644 --- a/frontends/benchmarks/equivalence/fullAlternation/test_conf.json +++ b/frontends/benchmarks/equivalence/fullAlternation/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "FullAlternation.m2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "FullAlternation.m1", + "functions": [ + "FullAlternation.m2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith1/expected_outcome.json b/frontends/benchmarks/equivalence/funnyarith1/expected_outcome.json deleted file mode 100644 index 0571b98249..0000000000 --- a/frontends/benchmarks/equivalence/funnyarith1/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.eval", - "functions": [ - "Candidate.eval" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith1/test_conf.json b/frontends/benchmarks/equivalence/funnyarith1/test_conf.json index 6da1f221fc..e4c1198490 100644 --- a/frontends/benchmarks/equivalence/funnyarith1/test_conf.json +++ b/frontends/benchmarks/equivalence/funnyarith1/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Candidate.eval" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.eval", + "functions": [ + "Candidate.eval" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith2/expected_outcome.json b/frontends/benchmarks/equivalence/funnyarith2/expected_outcome.json deleted file mode 100644 index 0571b98249..0000000000 --- a/frontends/benchmarks/equivalence/funnyarith2/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.eval", - "functions": [ - "Candidate.eval" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith2/test_conf.json b/frontends/benchmarks/equivalence/funnyarith2/test_conf.json index ae55f50bef..63619a8608 100644 --- a/frontends/benchmarks/equivalence/funnyarith2/test_conf.json +++ b/frontends/benchmarks/equivalence/funnyarith2/test_conf.json @@ -5,5 +5,20 @@ "comparefuns": [ "Candidate.eval" ], - "max-perm": 32 + "max-perm": 32, + "outcome": { + "equivalent": [ + { + "model": "Model.eval", + "functions": [ + "Candidate.eval" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith3/expected_outcome.json b/frontends/benchmarks/equivalence/funnyarith3/expected_outcome.json deleted file mode 100644 index 0571b98249..0000000000 --- a/frontends/benchmarks/equivalence/funnyarith3/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.eval", - "functions": [ - "Candidate.eval" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/funnyarith3/test_conf.json b/frontends/benchmarks/equivalence/funnyarith3/test_conf.json index 6da1f221fc..e4c1198490 100644 --- a/frontends/benchmarks/equivalence/funnyarith3/test_conf.json +++ b/frontends/benchmarks/equivalence/funnyarith3/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Candidate.eval" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.eval", + "functions": [ + "Candidate.eval" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/halfAlternation/expected_outcome.json b/frontends/benchmarks/equivalence/halfAlternation/expected_outcome.json deleted file mode 100644 index 1bb24af19a..0000000000 --- a/frontends/benchmarks/equivalence/halfAlternation/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "HalfAlternation.h1", - "functions": [ - "HalfAlternation.h2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/halfAlternation/test_conf.json b/frontends/benchmarks/equivalence/halfAlternation/test_conf.json index 98070c62f0..0e9ec7dce5 100644 --- a/frontends/benchmarks/equivalence/halfAlternation/test_conf.json +++ b/frontends/benchmarks/equivalence/halfAlternation/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "HalfAlternation.h2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "HalfAlternation.h1", + "functions": [ + "HalfAlternation.h2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/i1264/expected_outcome.json b/frontends/benchmarks/equivalence/i1264/expected_outcome.json deleted file mode 100644 index e65947b74e..0000000000 --- a/frontends/benchmarks/equivalence/i1264/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "i1264.replace", - "functions": [ - "i1264.slowReplace" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/i1264/test_conf.json b/frontends/benchmarks/equivalence/i1264/test_conf.json index 1516d6ab48..8b6b5f7924 100644 --- a/frontends/benchmarks/equivalence/i1264/test_conf.json +++ b/frontends/benchmarks/equivalence/i1264/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "i1264.slowReplace" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "i1264.replace", + "functions": [ + "i1264.slowReplace" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/inlining/expected_outcome.json b/frontends/benchmarks/equivalence/inlining/expected_outcome.json deleted file mode 100644 index ad15d95b2e..0000000000 --- a/frontends/benchmarks/equivalence/inlining/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Inlining.inlining_1", - "functions": [ - "Inlining.inlining_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/inlining/test_conf.json b/frontends/benchmarks/equivalence/inlining/test_conf.json index 49ee99fb0d..106ac53632 100644 --- a/frontends/benchmarks/equivalence/inlining/test_conf.json +++ b/frontends/benchmarks/equivalence/inlining/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Inlining.inlining_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Inlining.inlining_1", + "functions": [ + "Inlining.inlining_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/isSorted/expected_outcome.json b/frontends/benchmarks/equivalence/isSorted/expected_outcome.json deleted file mode 100644 index 3f6241ab5b..0000000000 --- a/frontends/benchmarks/equivalence/isSorted/expected_outcome.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "equivalent": [ - { - "model": "IsSorted.isSortedR", - "functions": [ - "IsSorted.isSortedB" - ] - }, - { - "model": "IsSorted.isSortedB", - "functions": [ - "IsSorted.isSortedC" - ] - } - ], - "unequivalent": [ - "IsSorted.isSortedA" - ], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/isSorted/test_conf.json b/frontends/benchmarks/equivalence/isSorted/test_conf.json index 63a98c2325..d7e693f272 100644 --- a/frontends/benchmarks/equivalence/isSorted/test_conf.json +++ b/frontends/benchmarks/equivalence/isSorted/test_conf.json @@ -6,5 +6,28 @@ "IsSorted.isSortedA", "IsSorted.isSortedB", "IsSorted.isSortedC" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "IsSorted.isSortedR", + "functions": [ + "IsSorted.isSortedB" + ] + }, + { + "model": "IsSorted.isSortedB", + "functions": [ + "IsSorted.isSortedC" + ] + } + ], + "unequivalent": [ + "IsSorted.isSortedA" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/iseven1/expected_outcome.json b/frontends/benchmarks/equivalence/iseven1/expected_outcome.json deleted file mode 100644 index 31e310d065..0000000000 --- a/frontends/benchmarks/equivalence/iseven1/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.isEvenTopLvl", - "functions": [ - "Candidate.isEvenTopLvl" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/iseven1/test_conf.json b/frontends/benchmarks/equivalence/iseven1/test_conf.json index b7c388b38c..cc6e06f1f3 100644 --- a/frontends/benchmarks/equivalence/iseven1/test_conf.json +++ b/frontends/benchmarks/equivalence/iseven1/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Candidate.isEvenTopLvl" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.isEvenTopLvl", + "functions": [ + "Candidate.isEvenTopLvl" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/iseven2/expected_outcome.json b/frontends/benchmarks/equivalence/iseven2/expected_outcome.json deleted file mode 100644 index 31e310d065..0000000000 --- a/frontends/benchmarks/equivalence/iseven2/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.isEvenTopLvl", - "functions": [ - "Candidate.isEvenTopLvl" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/iseven2/test_conf.json b/frontends/benchmarks/equivalence/iseven2/test_conf.json index b7c388b38c..cc6e06f1f3 100644 --- a/frontends/benchmarks/equivalence/iseven2/test_conf.json +++ b/frontends/benchmarks/equivalence/iseven2/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Candidate.isEvenTopLvl" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.isEvenTopLvl", + "functions": [ + "Candidate.isEvenTopLvl" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit1/expected_outcome.json b/frontends/benchmarks/equivalence/limit1/expected_outcome.json deleted file mode 100644 index f745e41e4f..0000000000 --- a/frontends/benchmarks/equivalence/limit1/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Limit1.limit1_1", - "functions": [ - "Limit1.limit1_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit1/test_conf.json b/frontends/benchmarks/equivalence/limit1/test_conf.json index d171903c54..6665b95f09 100644 --- a/frontends/benchmarks/equivalence/limit1/test_conf.json +++ b/frontends/benchmarks/equivalence/limit1/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Limit1.limit1_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Limit1.limit1_1", + "functions": [ + "Limit1.limit1_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit2/expected_outcome.json b/frontends/benchmarks/equivalence/limit2/expected_outcome.json deleted file mode 100644 index 023292b94d..0000000000 --- a/frontends/benchmarks/equivalence/limit2/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Limit2.limit2_1", - "functions": [ - "Limit2.limit2_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit2/test_conf.json b/frontends/benchmarks/equivalence/limit2/test_conf.json index d4b1e2d229..cf75a0ad03 100644 --- a/frontends/benchmarks/equivalence/limit2/test_conf.json +++ b/frontends/benchmarks/equivalence/limit2/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Limit2.limit2_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Limit2.limit2_1", + "functions": [ + "Limit2.limit2_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit3/expected_outcome.json b/frontends/benchmarks/equivalence/limit3/expected_outcome.json deleted file mode 100644 index 6eec0c4dc0..0000000000 --- a/frontends/benchmarks/equivalence/limit3/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Limit3.limit3_1", - "functions": [ - "Limit3.limit3_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/limit3/test_conf.json b/frontends/benchmarks/equivalence/limit3/test_conf.json index 6f3db04488..76d685c226 100644 --- a/frontends/benchmarks/equivalence/limit3/test_conf.json +++ b/frontends/benchmarks/equivalence/limit3/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Limit3.limit3_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Limit3.limit3_1", + "functions": [ + "Limit3.limit3_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max1/expected_outcome.json b/frontends/benchmarks/equivalence/max1/expected_outcome.json deleted file mode 100644 index 4d8dc43c41..0000000000 --- a/frontends/benchmarks/equivalence/max1/expected_outcome.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "equivalent": [ - { - "model": "Max.maxR", - "functions": [ - "Max.maxC" - ] - }, - { - "model": "Max.maxC", - "functions": [ - "Max.maxT" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max1/test_conf.json b/frontends/benchmarks/equivalence/max1/test_conf.json index 49a434521f..999717d355 100644 --- a/frontends/benchmarks/equivalence/max1/test_conf.json +++ b/frontends/benchmarks/equivalence/max1/test_conf.json @@ -5,5 +5,26 @@ "comparefuns": [ "Max.maxC", "Max.maxT" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Max.maxR", + "functions": [ + "Max.maxC" + ] + }, + { + "model": "Max.maxC", + "functions": [ + "Max.maxT" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max2/expected_outcome.json b/frontends/benchmarks/equivalence/max2/expected_outcome.json deleted file mode 100644 index 62389b5c98..0000000000 --- a/frontends/benchmarks/equivalence/max2/expected_outcome.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.max", - "functions": [ - "Candidate1.max", - "Candidate2.max", - "Candidate3.max" - ] - } - ], - "unequivalent": [ - "Candidate4.max", - "Candidate5.max" - ], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max2/test_conf.json b/frontends/benchmarks/equivalence/max2/test_conf.json index bec47d18d7..cb5a9cc3d5 100644 --- a/frontends/benchmarks/equivalence/max2/test_conf.json +++ b/frontends/benchmarks/equivalence/max2/test_conf.json @@ -10,5 +10,25 @@ "Candidate5.max" ], "tests": [], - "norm": "Model.norm" + "norm": "Model.norm", + "outcome": { + "equivalent": [ + { + "model": "Model.max", + "functions": [ + "Candidate1.max", + "Candidate2.max", + "Candidate3.max" + ] + } + ], + "unequivalent": [ + "Candidate4.max", + "Candidate5.max" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max3/expected_outcome.json b/frontends/benchmarks/equivalence/max3/expected_outcome.json deleted file mode 100644 index 0806f9d939..0000000000 --- a/frontends/benchmarks/equivalence/max3/expected_outcome.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "equivalent": [], - "unequivalent": [], - "unsafe": [], - "timeout": ["Candidate.max"], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/max3/test_conf.json b/frontends/benchmarks/equivalence/max3/test_conf.json index 992aa74a13..1cb5d710dd 100644 --- a/frontends/benchmarks/equivalence/max3/test_conf.json +++ b/frontends/benchmarks/equivalence/max3/test_conf.json @@ -6,5 +6,13 @@ "Candidate.max" ], "tests": [], - "norm": "Model.norm" + "norm": "Model.norm", + "outcome": { + "equivalent": [], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": ["Candidate.max"], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/pascal/expected_outcome.json b/frontends/benchmarks/equivalence/pascal/expected_outcome.json deleted file mode 100644 index 26556b6143..0000000000 --- a/frontends/benchmarks/equivalence/pascal/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Pascal.p1", - "functions": [ - "Pascal.p2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/pascal/test_conf.json b/frontends/benchmarks/equivalence/pascal/test_conf.json index e04f32e076..99d6578aaf 100644 --- a/frontends/benchmarks/equivalence/pascal/test_conf.json +++ b/frontends/benchmarks/equivalence/pascal/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Pascal.p2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Pascal.p1", + "functions": [ + "Pascal.p2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/Candidate1.scala b/frontends/benchmarks/equivalence/separate/Candidate1.scala new file mode 100644 index 0000000000..332c0afa99 --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/Candidate1.scala @@ -0,0 +1,15 @@ +import defs._ + +object Candidate1 { + def separate(xs: List[Animal]): (List[Sheep], List[Goat]) = { + xs match { + case Nil => (Nil, Nil) + case (s: Sheep) :: t => + val (s2, g2) = separate(t) + (s :: s2, g2) + case (g: Goat) :: t => + val (s2, g2) = separate(t) + (s2, g :: g2) + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/Candidate2.scala b/frontends/benchmarks/equivalence/separate/Candidate2.scala new file mode 100644 index 0000000000..aefb42b06a --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/Candidate2.scala @@ -0,0 +1,15 @@ +import defs._ + +object Candidate2 { + def separate(xs: List[Animal]): (List[Sheep], List[Goat]) = { + xs match { + case Nil => (Nil, Nil) + case (s: Sheep) :: t => + val (s2, g2) = separate(t) + (s :: s2, g2) + case (g: Goat) :: t => + val (s2, g2) = separate(t) + (s2, g2) // oops, forgets g + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/Model.scala b/frontends/benchmarks/equivalence/separate/Model.scala new file mode 100644 index 0000000000..3a1f7cbdde --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/Model.scala @@ -0,0 +1,21 @@ +import stainless.lang._ +import stainless.collection.{List => SList} +import defs._ + +object Model { + def separate(xs: List[Animal]): (List[Sheep], List[Goat]) = { + xs match { + case Nil => (Nil, Nil) + case (s: Sheep) :: t => + val (s2, g2) = separate(t) + (s :: s2, g2) + case (g: Goat) :: t => + val (s2, g2) = separate(t) + (s2, g :: g2) + } + } + + def test: SList[List[Animal]] = SList( + Goat(1) :: Sheep(2) :: Nil + ) +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/defs.scala b/frontends/benchmarks/equivalence/separate/defs.scala new file mode 100644 index 0000000000..0cbb30e155 --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/defs.scala @@ -0,0 +1,64 @@ +import stainless.lang._ + +object defs { + sealed trait Animal + case class Sheep(id: BigInt) extends Animal + case class Goat(id: BigInt) extends Animal + + sealed abstract class List[+T] { + def size: Int = { + this match { + case Nil => 0 + case h :: t => + val tLen = t.size + if (tLen == Int.MaxValue) tLen + else 1 + tLen + } + } ensuring(res => 0 <= res && res <= Int.MaxValue) + + def length: Int = size + + def ++[TT >: T](that: List[TT]): List[TT] = { + this match { + case Nil => that + case x :: xs => x :: (xs ++ that) + } + } + + def head: T = { + require(this != Nil) + val h :: _ = this: @unchecked + h + } + + def tail: List[T] = { + require(this != Nil) + val _ :: t = this: @unchecked + t + } + + def apply(index: Int): T = { + require(0 <= index && index < size) + decreases(index) + if (index == 0) { + head + } else { + tail(index-1) + } + } + + def :: [TT >: T](elem: TT): List[TT] = new ::(elem, this) + + def :+[TT >: T](t: TT): List[TT] = { + this match { + case Nil => t :: this + case x :: xs => x :: (xs :+ t) + } + } + } + + case object Nil extends List[Nothing] + + final case class ::[+A](first: A, next: List[A]) extends List[A] + +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/test_conf_1.json b/frontends/benchmarks/equivalence/separate/test_conf_1.json new file mode 100644 index 0000000000..20cc4342e5 --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/test_conf_1.json @@ -0,0 +1,29 @@ +{ + "models": [ + "Model.separate" + ], + "comparefuns": [ + "Candidate1.separate", + "Candidate2.separate" + ], + "tests": [ + "Model.test" + ], + "outcome": { + "equivalent": [ + { + "model": "Model.separate", + "functions": [ + "Candidate1.separate" + ] + } + ], + "unequivalent": [ + "Candidate2.separate" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/separate/test_conf_2.json b/frontends/benchmarks/equivalence/separate/test_conf_2.json new file mode 100644 index 0000000000..6a73fd3e64 --- /dev/null +++ b/frontends/benchmarks/equivalence/separate/test_conf_2.json @@ -0,0 +1,25 @@ +{ + "models": [ + "Model.separate" + ], + "comparefuns": [ + "Candidate1.separate", + "Candidate2.separate" + ], + "tests": [], + "outcome": { + "equivalent": [ + { + "model": "Model.separate", + "functions": [ + "Candidate1.separate" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": ["Candidate2.separate"], + "wrong": [] + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/sigma/expected_outcome.json b/frontends/benchmarks/equivalence/sigma/expected_outcome.json deleted file mode 100644 index 88791f1e6e..0000000000 --- a/frontends/benchmarks/equivalence/sigma/expected_outcome.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "equivalent": [], - "unequivalent": [], - "unsafe": [], - "timeout": ["Candidate.sigma"], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/sigma/test_conf.json b/frontends/benchmarks/equivalence/sigma/test_conf.json index d61aa43dd3..9c7cfcc849 100644 --- a/frontends/benchmarks/equivalence/sigma/test_conf.json +++ b/frontends/benchmarks/equivalence/sigma/test_conf.json @@ -5,5 +5,13 @@ "comparefuns": [ "Candidate.sigma" ], - "tests": [] + "tests": [], + "outcome": { + "equivalent": [], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": ["Candidate.sigma"], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/sum/expected_outcome.json b/frontends/benchmarks/equivalence/sum/expected_outcome.json deleted file mode 100644 index ac515087ed..0000000000 --- a/frontends/benchmarks/equivalence/sum/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Sum.sum1", - "functions": [ - "Sum.sum2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/sum/test_conf.json b/frontends/benchmarks/equivalence/sum/test_conf.json index d35ea3676a..0451bb061b 100644 --- a/frontends/benchmarks/equivalence/sum/test_conf.json +++ b/frontends/benchmarks/equivalence/sum/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Sum.sum2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Sum.sum1", + "functions": [ + "Sum.sum2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unfoldingSorted/expected_outcome.json b/frontends/benchmarks/equivalence/unfoldingSorted/expected_outcome.json deleted file mode 100644 index 6e92012aa3..0000000000 --- a/frontends/benchmarks/equivalence/unfoldingSorted/expected_outcome.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.unfoldingSorted", - "functions": [ - "Candidate1.unfoldingSorted", - "Candidate5.unfoldingSorted" - ] - } - ], - "unequivalent": [ - "Candidate3.unfoldingSorted", - "Candidate4.unfoldingSorted" - ], - "unsafe": [], - "timeout": [], - "wrong": [ - "Candidate2.unfoldingSorted" - ] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unfoldingSorted/test_conf.json b/frontends/benchmarks/equivalence/unfoldingSorted/test_conf.json index d6ad3658e0..b8c86a2ab3 100644 --- a/frontends/benchmarks/equivalence/unfoldingSorted/test_conf.json +++ b/frontends/benchmarks/equivalence/unfoldingSorted/test_conf.json @@ -8,5 +8,26 @@ "Candidate3.unfoldingSorted", "Candidate4.unfoldingSorted", "Candidate5.unfoldingSorted" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.unfoldingSorted", + "functions": [ + "Candidate1.unfoldingSorted", + "Candidate5.unfoldingSorted" + ] + } + ], + "unequivalent": [ + "Candidate3.unfoldingSorted", + "Candidate4.unfoldingSorted" + ], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [ + "Candidate2.unfoldingSorted" + ] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/uniq1/expected_outcome.json b/frontends/benchmarks/equivalence/uniq1/expected_outcome.json deleted file mode 100644 index 4d8bde8d2e..0000000000 --- a/frontends/benchmarks/equivalence/uniq1/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Uniq.uniqR", - "functions": [ - "Uniq.uniqA" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/uniq1/test_conf.json b/frontends/benchmarks/equivalence/uniq1/test_conf.json index cdb25e7bd8..d6e96f9d64 100644 --- a/frontends/benchmarks/equivalence/uniq1/test_conf.json +++ b/frontends/benchmarks/equivalence/uniq1/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Uniq.uniqA" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Uniq.uniqR", + "functions": [ + "Uniq.uniqA" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/uniq2/expected_outcome.json b/frontends/benchmarks/equivalence/uniq2/expected_outcome.json deleted file mode 100644 index a6e7ce240a..0000000000 --- a/frontends/benchmarks/equivalence/uniq2/expected_outcome.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "equivalent": [ - { - "model": "Model.solution_2", - "functions": [ - "Candidate2.uniq" - ] - }, - { - "model": "Model.solution_3", - "functions": [ - "Candidate1.uniq" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/uniq2/test_conf.json b/frontends/benchmarks/equivalence/uniq2/test_conf.json index a3e7cf158a..8a65719355 100644 --- a/frontends/benchmarks/equivalence/uniq2/test_conf.json +++ b/frontends/benchmarks/equivalence/uniq2/test_conf.json @@ -8,5 +8,26 @@ "comparefuns": [ "Candidate1.uniq", "Candidate2.uniq" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Model.solution_2", + "functions": [ + "Candidate2.uniq" + ] + }, + { + "model": "Model.solution_3", + "functions": [ + "Candidate1.uniq" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unknownSafety/Candidate.scala b/frontends/benchmarks/equivalence/unknownSafety/Candidate.scala new file mode 100644 index 0000000000..3a49803988 --- /dev/null +++ b/frontends/benchmarks/equivalence/unknownSafety/Candidate.scala @@ -0,0 +1,52 @@ +import stainless.lang._ +import stainless.collection._ + +object Candidate { + + def zero(x: BigInt): BigInt = { + require(x >= 0) + if (x > 0) zero(x - 1) + else x + } + + def add(x: BigInt, y: BigInt): BigInt = { + if (x >= 0) { + val z = zero(x) + assert(z == 0) // timeout + } + x + y + } + + ///////////////////////////////////// + + def isEvenTopLvl(x: BigInt): Boolean = isEven(x) + + def isEven(x: BigInt): Boolean = { + decreases(if (x <= 0) BigInt(0) else x) + if (x >= 0) { + assert(zero(x) == 0) // timeout + } + if (x < 0) false + else if (x == 0) true + else !isOdd(x - 1) + } + + def isOdd(x: BigInt): Boolean = { + decreases(if (x <= 0) BigInt(0) else x) + if (x <= 0) false + else if (x == 1) true + else !isEven(x - 1) + } + + ///////////////////////////////////// + + def isSorted(xs: List[BigInt]): Boolean = xs match { + case Nil() => true + case Cons(h, Nil()) => + if (h >= 0) { + assert(zero(h) == 0) // timeout + } + true + case Cons(h1, Cons(h2, t)) => h1 <= h2 && isSorted(t) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unknownSafety/Model.scala b/frontends/benchmarks/equivalence/unknownSafety/Model.scala new file mode 100644 index 0000000000..1f127e7386 --- /dev/null +++ b/frontends/benchmarks/equivalence/unknownSafety/Model.scala @@ -0,0 +1,33 @@ +import stainless.lang._ +import stainless.collection._ + +object Model { + + def add(x: BigInt, y: BigInt): BigInt = x + y + + ///////////////////////////////////// + + def isEvenTopLvl(x: BigInt): Boolean = isEven(x) + + def isEven(x: BigInt): Boolean = { + decreases(if (x <= 0) BigInt(0) else x) + if (x < 0) false + else if (x == 0) true + else !isOdd(x - 1) + } + + def isOdd(x: BigInt): Boolean = { + decreases(if (x <= 0) BigInt(0) else x) + if (x <= 0) false + else if (x == 1) true + else !isEven(x - 1) + } + + ///////////////////////////////////// + + def isSorted(xs: List[BigInt]): Boolean = xs match { + case Nil() => true + case Cons(_, Nil()) => true + case Cons(h1, Cons(h2, t)) => h1 <= h2 && isSorted(t) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unknownSafety/test_conf_1.json b/frontends/benchmarks/equivalence/unknownSafety/test_conf_1.json new file mode 100644 index 0000000000..da3eaef348 --- /dev/null +++ b/frontends/benchmarks/equivalence/unknownSafety/test_conf_1.json @@ -0,0 +1,19 @@ +{ + "models": [ + "Model.add" + ], + "comparefuns": [ + "Candidate.add" + ], + "tests": [], + "outcome": { + "equivalent": [], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [ + "Candidate.add" + ], + "unknownEquivalence": [], + "wrong": [] + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unknownSafety/test_conf_2.json b/frontends/benchmarks/equivalence/unknownSafety/test_conf_2.json new file mode 100644 index 0000000000..40126d8bc2 --- /dev/null +++ b/frontends/benchmarks/equivalence/unknownSafety/test_conf_2.json @@ -0,0 +1,19 @@ +{ + "models": [ + "Model.isEvenTopLvl" + ], + "comparefuns": [ + "Candidate.isEvenTopLvl" + ], + "tests": [], + "outcome": { + "equivalent": [], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [ + "Candidate.isEvenTopLvl" + ], + "unknownEquivalence": [], + "wrong": [] + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unknownSafety/test_conf_3.json b/frontends/benchmarks/equivalence/unknownSafety/test_conf_3.json new file mode 100644 index 0000000000..a782d530f7 --- /dev/null +++ b/frontends/benchmarks/equivalence/unknownSafety/test_conf_3.json @@ -0,0 +1,19 @@ +{ + "models": [ + "Model.isSorted" + ], + "comparefuns": [ + "Candidate.isSorted" + ], + "tests": [], + "outcome": { + "equivalent": [], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [ + "Candidate.isSorted" + ], + "unknownEquivalence": [], + "wrong": [] + } +} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unrolling/expected_outcome.json b/frontends/benchmarks/equivalence/unrolling/expected_outcome.json deleted file mode 100644 index 1510331a8e..0000000000 --- a/frontends/benchmarks/equivalence/unrolling/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "Unrolling.fact13_1", - "functions": [ - "Unrolling.fact13_2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unrolling/test_conf.json b/frontends/benchmarks/equivalence/unrolling/test_conf.json index 9a74beef2b..4ee46d9346 100644 --- a/frontends/benchmarks/equivalence/unrolling/test_conf.json +++ b/frontends/benchmarks/equivalence/unrolling/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "Unrolling.fact13_2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "Unrolling.fact13_1", + "functions": [ + "Unrolling.fact13_2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unused/expected_outcome.json b/frontends/benchmarks/equivalence/unused/expected_outcome.json deleted file mode 100644 index 1aad5ce3a4..0000000000 --- a/frontends/benchmarks/equivalence/unused/expected_outcome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "FibonacciUnused.t1", - "functions": [ - "FibonacciUnused.t2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/unused/test_conf.json b/frontends/benchmarks/equivalence/unused/test_conf.json index b6f36e0f5e..4e476d6b94 100644 --- a/frontends/benchmarks/equivalence/unused/test_conf.json +++ b/frontends/benchmarks/equivalence/unused/test_conf.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "FibonacciUnused.t2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "FibonacciUnused.t1", + "functions": [ + "FibonacciUnused.t2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_1.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_1.json deleted file mode 100644 index 49c55f6747..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.andThen1", - "functions": [ - "WhacAFun.andThen2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_2.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_2.json deleted file mode 100644 index 9b1e4759bf..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_2.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.compose1", - "functions": [ - "WhacAFun.compose2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_3.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_3.json deleted file mode 100644 index d835b1da2a..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_3.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.flip1", - "functions": [ - "WhacAFun.flip2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_4.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_4.json deleted file mode 100644 index 376e569abe..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_4.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.curry1", - "functions": [ - "WhacAFun.curry2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_5.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_5.json deleted file mode 100644 index 30e3f5986c..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_5.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.uncurry1", - "functions": [ - "WhacAFun.uncurry2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_6.json b/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_6.json deleted file mode 100644 index 0da1c33ab4..0000000000 --- a/frontends/benchmarks/equivalence/whac-a-fun/expected_outcome_6.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "equivalent": [ - { - "model": "WhacAFun.repeat1", - "functions": [ - "WhacAFun.repeat2" - ] - } - ], - "unequivalent": [], - "unsafe": [], - "timeout": [], - "wrong": [] -} \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_1.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_1.json index 27665da91a..be54a7483d 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_1.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_1.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.andThen2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.andThen1", + "functions": [ + "WhacAFun.andThen2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_2.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_2.json index fc1b3981ae..96f1ca61d1 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_2.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_2.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.compose2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.compose1", + "functions": [ + "WhacAFun.compose2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_3.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_3.json index 5fabba9d50..a1bef7c775 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_3.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_3.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.flip2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.flip1", + "functions": [ + "WhacAFun.flip2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_4.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_4.json index 41df3cbaa4..51c9ef8110 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_4.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_4.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.curry2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.curry1", + "functions": [ + "WhacAFun.curry2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_5.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_5.json index eca2608a60..5f001fab9f 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_5.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_5.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.uncurry2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.uncurry1", + "functions": [ + "WhacAFun.uncurry2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_6.json b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_6.json index e7566fc19e..80ad356a8f 100644 --- a/frontends/benchmarks/equivalence/whac-a-fun/test_conf_6.json +++ b/frontends/benchmarks/equivalence/whac-a-fun/test_conf_6.json @@ -4,5 +4,20 @@ ], "comparefuns": [ "WhacAFun.repeat2" - ] + ], + "outcome": { + "equivalent": [ + { + "model": "WhacAFun.repeat1", + "functions": [ + "WhacAFun.repeat2" + ] + } + ], + "unequivalent": [], + "unsafe": [], + "unknownSafety": [], + "unknownEquivalence": [], + "wrong": [] + } } \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.check b/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.check new file mode 100644 index 0000000000..bba61843ac --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.check @@ -0,0 +1,3 @@ +[ Error ] AliasedFreshExpr.scala:14:5: Illegal passing of aliased parameters c1 (with target: Target(c1, None, )) and c1 (with target: Target(c1, None, )) + Trout(c1, c1) + ^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.scala b/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.scala new file mode 100644 index 0000000000..4a71f62e7e --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/AliasedFreshExpr.scala @@ -0,0 +1,16 @@ +object AliasedFreshExpr { + case class C1(var c2: C2) + case class C2(var c3: C3) + case class C3(var bi: BigInt) + + sealed trait Fish + case class Trout(c11: C1, c12: C1) extends Fish + case class Cod(c1: C1, c2: C2) extends Fish + case class Salmon(c1: C1, c3: C3) extends Fish + + def test1(x: BigInt): Fish = { + val c1 = C1(C2(C3(x))) + // This expression is fresh, but is not alias-free + Trout(c1, c1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ArrayShenanigans1.check b/frontends/benchmarks/extraction/invalid/ArrayShenanigans1.check index d4f45aeaea..0a3dd0d2dd 100644 --- a/frontends/benchmarks/extraction/invalid/ArrayShenanigans1.check +++ b/frontends/benchmarks/extraction/invalid/ArrayShenanigans1.check @@ -1,3 +1,3 @@ -[ Error ] ArrayShenanigans1.scala:13:14: Illegal aliasing: arrij +[ Error ] ArrayShenanigans1.scala:13:14: Cannot update an array whose element type (Ref) is mutable with a non-fresh expression arr(i) = arrij ^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ArrayShenanigans2.check b/frontends/benchmarks/extraction/invalid/ArrayShenanigans2.check index eb2a1e9b1f..b87f341938 100644 --- a/frontends/benchmarks/extraction/invalid/ArrayShenanigans2.check +++ b/frontends/benchmarks/extraction/invalid/ArrayShenanigans2.check @@ -1,3 +1,3 @@ -[ Error ] ArrayShenanigans2.scala:13:14: Illegal aliasing: arrj +[ Error ] ArrayShenanigans2.scala:13:14: Cannot update an array whose element type (Ref) is mutable with a non-fresh expression arr(i) = arrj ^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ArrayShenanigans3.check b/frontends/benchmarks/extraction/invalid/ArrayShenanigans3.check index 33a6b8ef60..ee792d22bb 100644 --- a/frontends/benchmarks/extraction/invalid/ArrayShenanigans3.check +++ b/frontends/benchmarks/extraction/invalid/ArrayShenanigans3.check @@ -1,3 +1,3 @@ -[ Error ] ArrayShenanigans3.scala:14:31: Illegal aliasing: arrjk +[ Error ] ArrayShenanigans3.scala:14:31: Cannot update an array whose element type (Ref) is mutable with a non-fresh expression val arr2 = arr.updated(i, arrjk) ^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ArrayShenanigans4.check b/frontends/benchmarks/extraction/invalid/ArrayShenanigans4.check index 4055a0922c..87ef328ed5 100644 --- a/frontends/benchmarks/extraction/invalid/ArrayShenanigans4.check +++ b/frontends/benchmarks/extraction/invalid/ArrayShenanigans4.check @@ -1,3 +1,3 @@ -[ Error ] ArrayShenanigans4.scala:12:31: Illegal aliasing: arr(j) +[ Error ] ArrayShenanigans4.scala:12:31: Cannot update an array whose element type (Ref) is mutable with a non-fresh expression val arr2 = arr.updated(i, arr(j)) ^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ArrayShenanigans5.check b/frontends/benchmarks/extraction/invalid/ArrayShenanigans5.check index ffd6490b2c..59fbe115ee 100644 --- a/frontends/benchmarks/extraction/invalid/ArrayShenanigans5.check +++ b/frontends/benchmarks/extraction/invalid/ArrayShenanigans5.check @@ -1,3 +1,3 @@ -[ Error ] ArrayShenanigans5.scala:11:31: Illegal aliasing: r +[ Error ] ArrayShenanigans5.scala:11:31: Cannot update an array whose element type (Ref) is mutable with a non-fresh expression val arr2 = arr.updated(i, r) ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/BadAliasing1.check b/frontends/benchmarks/extraction/invalid/BadAliasing1.check index 92400a01ac..783a35ca76 100644 --- a/frontends/benchmarks/extraction/invalid/BadAliasing1.check +++ b/frontends/benchmarks/extraction/invalid/BadAliasing1.check @@ -1,3 +1,3 @@ -[ Error ] BadAliasing1.scala:9:5: Illegal aliasing: a +[ Error ] BadAliasing1.scala:9:5: Cannot update a field whose type (A) is mutable with a non-fresh expression a.x = 0 ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/BadAliasing2.check b/frontends/benchmarks/extraction/invalid/BadAliasing2.check index 645e593a96..7115bd53f0 100644 --- a/frontends/benchmarks/extraction/invalid/BadAliasing2.check +++ b/frontends/benchmarks/extraction/invalid/BadAliasing2.check @@ -1,3 +1,3 @@ -[ Error ] BadAliasing2.scala:10:5: Illegal aliasing: a +[ Error ] BadAliasing2.scala:10:5: Cannot update an array whose element type (A) is mutable with a non-fresh expression a.x = 0 ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/BadOverride2.scalac.check b/frontends/benchmarks/extraction/invalid/BadOverride2.scalac.check index 0e0666b30c..dbcefb7bc1 100644 --- a/frontends/benchmarks/extraction/invalid/BadOverride2.scalac.check +++ b/frontends/benchmarks/extraction/invalid/BadOverride2.scalac.check @@ -1,3 +1,3 @@ -[ Error ] BadOverride2.scala:5:3: Abstract values `y` must be overridden with fields in concrete subclass +[ Error ] BadOverride2.scala:5:14: Abstract values `y` must be overridden with fields in concrete subclass case class AbsInvalid() extends Abs { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/BadOverride3.scalac.check b/frontends/benchmarks/extraction/invalid/BadOverride3.scalac.check index 69d35764d7..5e87e85228 100644 --- a/frontends/benchmarks/extraction/invalid/BadOverride3.scalac.check +++ b/frontends/benchmarks/extraction/invalid/BadOverride3.scalac.check @@ -1,4 +1,4 @@ -[ Error ] BadOverride3.scala:6:3: Not well formed definition case class BBB extends AAA +[ Error ] BadOverride3.scala:6:14: Not well formed definition case class BBB extends AAA [ Error ] because abstract methods BadOverride3.AAA.f were not overridden by a method, a lazy val, or a constructor parameter case class BBB() extends AAA { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ExtendNonMutable.scalac.check b/frontends/benchmarks/extraction/invalid/ExtendNonMutable.scalac.check index c02eda1161..efa13257ad 100644 --- a/frontends/benchmarks/extraction/invalid/ExtendNonMutable.scalac.check +++ b/frontends/benchmarks/extraction/invalid/ExtendNonMutable.scalac.check @@ -1,4 +1,4 @@ -[ Error ] ExtendNonMutable.scala:4:1: A mutable class (B) cannot have a non-@mutable and non-sealed parent (A). +[ Error ] ExtendNonMutable.scala:4:12: A mutable class (B) cannot have a non-@mutable and non-sealed parent (A). [ Error ] Please annotate A with @mutable. case class B(var x: BigInt) extends A { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/FieldInheritance2.scalac.check b/frontends/benchmarks/extraction/invalid/FieldInheritance2.scalac.check index f09f186f44..a7b64edbcd 100644 --- a/frontends/benchmarks/extraction/invalid/FieldInheritance2.scalac.check +++ b/frontends/benchmarks/extraction/invalid/FieldInheritance2.scalac.check @@ -1,3 +1,3 @@ -[ Error ] FieldInheritance2.scala:11:3: Abstract values `y` must be overridden with fields in concrete subclass +[ Error ] FieldInheritance2.scala:11:14: Abstract values `y` must be overridden with fields in concrete subclass case class Bar[X](override val thisIsIt: BigInt) extends Foo[X] { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/FunnyDottyInference.check b/frontends/benchmarks/extraction/invalid/FunnyDottyInference.check index b0ed129ebd..6bfc631d91 100644 --- a/frontends/benchmarks/extraction/invalid/FunnyDottyInference.check +++ b/frontends/benchmarks/extraction/invalid/FunnyDottyInference.check @@ -1,10 +1,10 @@ [ Error ] FunnyDottyInference.scala:5:3: Type `Matchable` is unsupported def matchable1(b: Boolean) = { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... -[ Error ] FunnyDottyInference.scala:5:29: Hint: the inferred return type of matchable1 is `Matchable` +[ Error ] FunnyDottyInference.scala:5:29: Hint: the inferred return type of matchable1 is `Int | String`, and is widened to Matchable def matchable1(b: Boolean) = { ^ -[ Error ] FunnyDottyInference.scala:6:5: Hint: the widened type of this if expression is `Matchable` +[ Error ] FunnyDottyInference.scala:6:5: Hint: the type of this if expression is (1 : Int) | ("2" : String) and is widened to `Matchable` if (b) 1 ^^^^^^^^... [ Error ] FunnyDottyInference.scala:6:12: Hint: this branch type is `Int` @@ -16,10 +16,10 @@ [ Error ] FunnyDottyInference.scala:11:5: Type `Matchable` is unsupported val x = if (b) 1 else if (b && b) "2" else true ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -[ Error ] FunnyDottyInference.scala:11:10: Hint: the inferred type of x is `Matchable` +[ Error ] FunnyDottyInference.scala:11:10: Hint: the inferred type of x is `Int | (String | Boolean)`, and is widened to Matchable val x = if (b) 1 else if (b && b) "2" else true ^ -[ Error ] FunnyDottyInference.scala:11:13: Hint: the widened type of this if expression is `Matchable` +[ Error ] FunnyDottyInference.scala:11:13: Hint: the type of this if expression is (1 : Int) | (("2" : String) | (true : Boolean)) and is widened to `Matchable` val x = if (b) 1 else if (b && b) "2" else true ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [ Error ] FunnyDottyInference.scala:11:20: Hint: this branch type is `Int` @@ -34,13 +34,13 @@ [ Error ] FunnyDottyInference.scala:20:3: Type `Matchable` is unsupported def matchable3(c: Color) = { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^... -[ Error ] FunnyDottyInference.scala:20:27: Hint: the inferred return type of matchable3 is `Matchable` +[ Error ] FunnyDottyInference.scala:20:27: Hint: the inferred return type of matchable3 is `Int | String | Boolean`, and is widened to Matchable def matchable3(c: Color) = { ^ -[ Error ] FunnyDottyInference.scala:21:10: Hint: the inferred type of x is `Matchable` +[ Error ] FunnyDottyInference.scala:21:10: Hint: the inferred type of x is `Int | String | Boolean`, and is widened to Matchable val x = c match { ^ -[ Error ] FunnyDottyInference.scala:21:13: Hint: the widened type of this match expression is `Matchable` +[ Error ] FunnyDottyInference.scala:21:13: Hint: the type of this match expression is (1 : Int) | ("green" : String) | (true : Boolean) and is widened to `Matchable` val x = c match { ^^^^^^^^^... [ Error ] FunnyDottyInference.scala:22:7: Hint: this case type is `Int` @@ -55,13 +55,13 @@ [ Error ] FunnyDottyInference.scala:29:3: Type `Matchable` is unsupported def matchable4(c: Color) = { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^... -[ Error ] FunnyDottyInference.scala:29:27: Hint: the inferred return type of matchable4 is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:29:27: Hint: the inferred return type of matchable4 is `stainless.collection.List[Int | String | Boolean]`, and is widened to stainless.collection.List[Matchable] def matchable4(c: Color) = { ^ -[ Error ] FunnyDottyInference.scala:30:10: Hint: the inferred type of x is `Matchable` +[ Error ] FunnyDottyInference.scala:30:10: Hint: the inferred type of x is `Int | String | Boolean`, and is widened to Matchable val x = c match { ^ -[ Error ] FunnyDottyInference.scala:30:13: Hint: the widened type of this match expression is `Matchable` +[ Error ] FunnyDottyInference.scala:30:13: Hint: the type of this match expression is (1 : Int) | ("green" : String) | (true : Boolean) and is widened to `Matchable` val x = c match { ^^^^^^^^^... [ Error ] FunnyDottyInference.scala:31:7: Hint: this case type is `Int` @@ -76,21 +76,21 @@ [ Error ] FunnyDottyInference.scala:38:3: Type `Matchable` is unsupported def matchable5(c: Color) = { ^^^^^^^^^^^^^^^^^^^^^^^^^^^^... -[ Error ] FunnyDottyInference.scala:38:27: Hint: the inferred return type of matchable5 is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:38:27: Hint: the inferred return type of matchable5 is `stainless.collection.List[Boolean | (String | Int)]`, and is widened to stainless.collection.List[Matchable] def matchable5(c: Color) = { ^ -[ Error ] FunnyDottyInference.scala:39:10: Hint: the inferred type of x is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:39:10: Hint: the inferred type of x is `stainless.collection.List[Boolean | (String | Int)]`, and is widened to stainless.collection.List[Matchable] val x = c match { ^ -[ Error ] FunnyDottyInference.scala:39:13: Hint: the widened type of this match expression is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:39:13: Hint: the type of this match expression is stainless.collection.List[Boolean | (String | Int)] and is widened to `stainless.collection.List[Matchable]` val x = c match { ^^^^^^^^^... -[ Error ] FunnyDottyInference.scala:40:7: Hint: this case type is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:40:7: Hint: this case type is `stainless.collection.List[Boolean | (String | Int)]` case Color.Red => List(1) ^^^^^^^^^^^^^^^^^^^^^^^^^ -[ Error ] FunnyDottyInference.scala:41:7: Hint: this case type is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:41:7: Hint: this case type is `stainless.collection.List[Boolean | (String | Int)]` case Color.Green => List("green") ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -[ Error ] FunnyDottyInference.scala:42:7: Hint: this case type is `stainless.collection.List[Matchable]` +[ Error ] FunnyDottyInference.scala:42:7: Hint: this case type is `stainless.collection.List[Boolean | (String | Int)]` case Color.Blue => List(true) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/GhostClass.dotty.check b/frontends/benchmarks/extraction/invalid/GhostClass.dotty.check new file mode 100644 index 0000000000..a99b2bf982 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/GhostClass.dotty.check @@ -0,0 +1,3 @@ +[ Error ] GhostClass.scala:6:31: Cannot access a ghost symbol outside of a ghost context. [ GhostClass.MyClass in method buildClass ] + def buildClass(x: BigInt) = MyClass(x, x) + ^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/GhostClass.scala b/frontends/benchmarks/extraction/invalid/GhostClass.scala new file mode 100644 index 0000000000..96cc4e3e30 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/GhostClass.scala @@ -0,0 +1,7 @@ +import stainless.annotation._ +object GhostClass { + @ghost + case class MyClass(x: BigInt, y: BigInt) + + def buildClass(x: BigInt) = MyClass(x, x) +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/GhostClass.scalac.check b/frontends/benchmarks/extraction/invalid/GhostClass.scalac.check new file mode 100644 index 0000000000..3cba0b5513 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/GhostClass.scalac.check @@ -0,0 +1,3 @@ +[ Error ] GhostClass.scala:6:31: Cannot access a ghost symbol outside of a ghost context. [ GhostClass.this.MyClass in method buildClass ] + def buildClass(x: BigInt) = MyClass(x, x) + ^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/IllegalAliasing.check b/frontends/benchmarks/extraction/invalid/IllegalAliasing.check index ddb6489b2f..f254c37993 100644 --- a/frontends/benchmarks/extraction/invalid/IllegalAliasing.check +++ b/frontends/benchmarks/extraction/invalid/IllegalAliasing.check @@ -1,3 +1,10 @@ [ Error ] IllegalAliasing.scala:9:13: Illegal aliasing: Mut[S](a) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of b (Mut[S]) being mutable +[ Error ] -the definition of b not being fresh +[ Error ] -the definition of b containing variables of mutable types +[ Error ] that also appear after the declaration of b: +[ Error ] -a (of type S) +[ Error ] val b = Mut(a) ^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/IllegalAliasing4.check b/frontends/benchmarks/extraction/invalid/IllegalAliasing4.check new file mode 100644 index 0000000000..88329b4d23 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/IllegalAliasing4.check @@ -0,0 +1,5 @@ +[ Error ] IllegalAliasing4.scala:15:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] counter: A + val b = { + ^^^^^^^^^... \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/IllegalAliasing4.scala b/frontends/benchmarks/extraction/invalid/IllegalAliasing4.scala new file mode 100644 index 0000000000..1c160f514e --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/IllegalAliasing4.scala @@ -0,0 +1,26 @@ +object IllegalAliasing4 { + + case class A(var i: BigInt) + case class B(a: A) + case class C(a: A, b: B) + + def createA(i: BigInt, counter: A): A = { + counter.i += 1 + A(i) + } + + def test(n: BigInt, counter: A): Unit = { + val origCount = counter.i + val alias = counter + val b = { + if (n > 0) { + createA(n, alias) + B(alias) + } + else B(A(0)) + } + b.a.i += 1 + assert(n <= 0 || counter.i == origCount + 2) + assert(n > 0 || counter.i == origCount + 1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ImpureInvariant.check b/frontends/benchmarks/extraction/invalid/ImpureInvariant.check new file mode 100644 index 0000000000..0f41c275db --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/ImpureInvariant.check @@ -0,0 +1,5 @@ +[ Error ] ImpureInvariant.scala:9:13: Invariants cannot have side-effects +[ Error ] Hint: the invariant calls the following impure functions: +[ Error ] -take + require(mt.take(mic) == 0) + ^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ImpureInvariant.scala b/frontends/benchmarks/extraction/invalid/ImpureInvariant.scala new file mode 100644 index 0000000000..f8ae5191e2 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/ImpureInvariant.scala @@ -0,0 +1,11 @@ +object ImpureInvariant { + case class MyImpureClass(var i: BigInt) + + trait MyTrait { + def take(m: MyImpureClass): BigInt + } + + case class HasImpureInvariant(mt: MyTrait, mic: MyImpureClass) { + require(mt.take(mic) == 0) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ImpurePure1.scalac.check b/frontends/benchmarks/extraction/invalid/ImpurePure1.scalac.check index c1d1feda93..5a710efb02 100644 --- a/frontends/benchmarks/extraction/invalid/ImpurePure1.scalac.check +++ b/frontends/benchmarks/extraction/invalid/ImpurePure1.scalac.check @@ -1,3 +1,3 @@ -[ Error ] ImpurePure1.scala:7:9: Functions marked @pure cannot have side-effects +[ Error ] ImpurePure1.scala:7:13: Functions marked @pure cannot have side-effects @pure def f(b: Box): Unit = { // A lying "pure" function! - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/ImpurePure2.scalac.check b/frontends/benchmarks/extraction/invalid/ImpurePure2.scalac.check index 705db03170..f3ae6f352b 100644 --- a/frontends/benchmarks/extraction/invalid/ImpurePure2.scalac.check +++ b/frontends/benchmarks/extraction/invalid/ImpurePure2.scalac.check @@ -1,3 +1,3 @@ -[ Error ] ImpurePure2.scala:7:3: Functions marked @pure cannot have side-effects +[ Error ] ImpurePure2.scala:7:7: Functions marked @pure cannot have side-effects def outer(b: Box): Unit = { // A lying "pure" function! - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Initialization1.scalac.check b/frontends/benchmarks/extraction/invalid/Initialization1.scalac.check index b790f8f577..0871850691 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization1.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Initialization1.scalac.check @@ -1,4 +1,5 @@ [ Error ] Initialization1.scala:3:5: Not well formed definition @private +[ Error ] @final [ Error ] @field [ Error ] @fieldDefPosition(1) [ Error ] @method(InitA) diff --git a/frontends/benchmarks/extraction/invalid/Initialization2.scalac.check b/frontends/benchmarks/extraction/invalid/Initialization2.scalac.check index 53b4340036..d98ba75dd6 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization2.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Initialization2.scalac.check @@ -1,4 +1,5 @@ [ Error ] Initialization2.scala:3:5: Not well formed definition @private +[ Error ] @final [ Error ] @field [ Error ] @fieldDefPosition(1) [ Error ] @method(Hello) diff --git a/frontends/benchmarks/extraction/invalid/Initialization3.scalac.check b/frontends/benchmarks/extraction/invalid/Initialization3.scalac.check index 474e579b78..17072d51a2 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization3.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Initialization3.scalac.check @@ -1,4 +1,5 @@ [ Error ] Initialization3.scala:3:5: Not well formed definition @private +[ Error ] @final [ Error ] @field [ Error ] @fieldDefPosition(1) [ Error ] @method(NoThis) diff --git a/frontends/benchmarks/extraction/invalid/Initialization4.scalac.check b/frontends/benchmarks/extraction/invalid/Initialization4.scalac.check index d8db39db0b..8298512775 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization4.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Initialization4.scalac.check @@ -1,4 +1,5 @@ [ Error ] Initialization4.scala:3:5: Not well formed definition @private +[ Error ] @final [ Error ] @field [ Error ] @fieldDefPosition(1) [ Error ] @method(NoThis) diff --git a/frontends/benchmarks/extraction/invalid/Initialization5.dotty.check b/frontends/benchmarks/extraction/invalid/Initialization5.dotty.check index 84c785755e..aafcdcf2ec 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization5.dotty.check +++ b/frontends/benchmarks/extraction/invalid/Initialization5.dotty.check @@ -1,10 +1,7 @@ -[ Error ] Initialization5.scala:6:17: Cannot prove the argument is fully initialized. Only fully initialized values are safe to leak. Calling trace: -[ Error ] -> case class NoThis() { [ Initialization5.scala:2 ] -[ Error ] ^ -[ Error ] -> val nothis1 = f() [ Initialization5.scala:3 ] -[ Error ] ^^^ -[ Error ] -> def f() = g(this) [ Initialization5.scala:6 ] -[ Error ] ^^^^ -[ Error ] - def f() = g(this) - ^ \ No newline at end of file +[ Error ] Initialization5.scala:3:9: Not well formed definition @field +[ Error ] @fieldDefPosition(8) +[ Error ] @method(NoThis) +[ Error ] def nothis1: Int = this.f +[ Error ] because field `nothis1` can only refer to previous fields, not to `nothis2` + val nothis1 = f() + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Initialization5.scalac.check b/frontends/benchmarks/extraction/invalid/Initialization5.scalac.check index 8d8dea6769..1e01a8627f 100644 --- a/frontends/benchmarks/extraction/invalid/Initialization5.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Initialization5.scalac.check @@ -1,4 +1,5 @@ [ Error ] Initialization5.scala:3:5: Not well formed definition @private +[ Error ] @final [ Error ] @field [ Error ] @fieldDefPosition(1) [ Error ] @method(NoThis) diff --git a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.check b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.check new file mode 100644 index 0000000000..224fd2e7d1 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.check @@ -0,0 +1,3 @@ +[ Error ] InnerClassesInvariants2.scala:13:20: Local classes cannot close over mutable variables + assert(0 < y.value && y.value < 10) // Local classes cannot close over mutable variables + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.dotty.check b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.dotty.check deleted file mode 100644 index 45b9940703..0000000000 --- a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.dotty.check +++ /dev/null @@ -1,3 +0,0 @@ -[ Error ] InnerClassesInvariants2.scala:6:21: value value is not a member of ref - require(0 < ref.value && ref.value < 10) - ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scala b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scala index 46ea269cf0..ea8b1b7d02 100644 --- a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scala +++ b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scala @@ -3,18 +3,18 @@ object InnerClassesInvariants1 { case class Ref(var value: BigInt) def rejected(x: Ref): Unit = { - require(0 < ref.value && ref.value < 10) + require(0 < x.value && x.value < 10) var y = x val z = y case class Local() { def smth: Unit = { assert(0 < x.value && x.value < 10) - assert(0 < y.value && y.value < 10) + assert(0 < y.value && y.value < 10) // Local classes cannot close over mutable variables assert(0 < z.value && z.value < 10) } } val local = Local() - y.value = 42 // Illegal aliasing + y.value = 42 } } \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scalac.check b/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scalac.check deleted file mode 100644 index 15df4f2ec7..0000000000 --- a/frontends/benchmarks/extraction/invalid/InnerClassesInvariants2.scalac.check +++ /dev/null @@ -1,6 +0,0 @@ -[ Error ] InnerClassesInvariants2.scala:6:21: object value is not a member of package ref - require(0 < ref.value && ref.value < 10) - ^ -[ Error ] InnerClassesInvariants2.scala:6:34: object value is not a member of package ref - require(0 < ref.value && ref.value < 10) - ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.dotty.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.dotty.check new file mode 100644 index 0000000000..c0a1abb8d9 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.dotty.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns1.scala:13:47: Unsupported pattern: _:B + case MyOtherClass(s1: A, MyOtherClass(s2: B, moc2: MyOtherClass[A, B])) => s1 // s2: B is invalid + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scala b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scala new file mode 100644 index 0000000000..83e34c0f9c --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scala @@ -0,0 +1,15 @@ +import stainless.lang._ + +object InvalidTypedPatterns1 { + + case class MyOtherClass[S, T](s1: S, s2: S, t: T) + + object MyOtherClass { + def unapply[S, T](moc: MyOtherClass[S, T]): Option[(S, MyOtherClass[S, T])] = Some((moc.s1, moc)) + } + + + def test[A, B](moc: MyOtherClass[A, B]): A = moc match { + case MyOtherClass(s1: A, MyOtherClass(s2: B, moc2: MyOtherClass[A, B])) => s1 // s2: B is invalid + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scalac.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scalac.check new file mode 100644 index 0000000000..38bef44ccb --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns1.scalac.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns1.scala:13:45: Unsupported pattern: (_: B) + case MyOtherClass(s1: A, MyOtherClass(s2: B, moc2: MyOtherClass[A, B])) => s1 // s2: B is invalid + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.dotty.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.dotty.check new file mode 100644 index 0000000000..ed7cfff05f --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.dotty.check @@ -0,0 +1 @@ +[ Error ] Stainless does not support type B & A \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scala b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scala new file mode 100644 index 0000000000..3d0d0bb9af --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scala @@ -0,0 +1,5 @@ +object InvalidTypedPatterns2 { + def test[A, B](a: A, b: B): Unit = { + val (aa1: A, bb: A) = (a, b) // bb: A is invalid + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scalac.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scalac.check new file mode 100644 index 0000000000..5d65653f0b --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns2.scalac.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns2.scala:3:20: Unsupported pattern: (_: A) + val (aa1: A, bb: A) = (a, b) // bb: A is invalid + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.dotty.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.dotty.check new file mode 100644 index 0000000000..cc9b7c844b --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.dotty.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns3.scala:5:29: Unsupported pattern: _:B + case MyClass(a1: A, a2: B, b: B) => a1 // a2: B is invalid + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scala b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scala new file mode 100644 index 0000000000..b7647a95dd --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scala @@ -0,0 +1,7 @@ +object InvalidTypedPatterns3 { + case class MyClass[S, T](s1: S, s2: S, t: T) + + def test[A, B](mc: MyClass[A, B]): A = mc match { + case MyClass(a1: A, a2: B, b: B) => a1 // a2: B is invalid + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scalac.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scalac.check new file mode 100644 index 0000000000..7d9bc3be82 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns3.scalac.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns3.scala:5:27: Unsupported pattern: (_: B) + case MyClass(a1: A, a2: B, b: B) => a1 // a2: B is invalid + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.dotty.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.dotty.check new file mode 100644 index 0000000000..197317cdd8 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.dotty.check @@ -0,0 +1,3 @@ +[ Error ] InvalidTypedPatterns4.scala:5:31: Unsupported pattern: _:String + case MyClass(a1: Int, a2: String, b: String) => a1 // a2: String is invalid + ^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scala b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scala new file mode 100644 index 0000000000..7645b63c2f --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scala @@ -0,0 +1,7 @@ +object InvalidTypedPatterns4 { + case class MyClass[S, T](s1: S, s2: S, t: T) + + def test5(mc: MyClass[Int, String]): Int = mc match { + case MyClass(a1: Int, a2: String, b: String) => a1 // a2: String is invalid + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scalac.check b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scalac.check new file mode 100644 index 0000000000..39924a7f21 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/InvalidTypedPatterns4.scalac.check @@ -0,0 +1,5 @@ +[ Error ] InvalidTypedPatterns4.scala:5:31: scrutinee is incompatible with pattern type; +[ Error ] found : String +[ Error ] required: Int + case MyClass(a1: Int, a2: String, b: String) => a1 // a2: String is invalid + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/MapAliasing1.check b/frontends/benchmarks/extraction/invalid/MapAliasing1.check index 662bdc005b..962812b805 100644 --- a/frontends/benchmarks/extraction/invalid/MapAliasing1.check +++ b/frontends/benchmarks/extraction/invalid/MapAliasing1.check @@ -1,3 +1,5 @@ -[ Error ] MapAliasing1.scala:17:5: Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body) +[ Error ] MapAliasing1.scala:17:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] m: MutableMap[BigInt,A] val a = f(m, 123) ^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/MapAliasing2.check b/frontends/benchmarks/extraction/invalid/MapAliasing2.check index 81ca98ec75..380c8f016d 100644 --- a/frontends/benchmarks/extraction/invalid/MapAliasing2.check +++ b/frontends/benchmarks/extraction/invalid/MapAliasing2.check @@ -1,3 +1,5 @@ -[ Error ] MapAliasing2.scala:15:5: Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body) +[ Error ] MapAliasing2.scala:15:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] m: MutableMap[BigInt,A] val a = f(m, 123) ^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/MutableField.scalac.check b/frontends/benchmarks/extraction/invalid/MutableField.scalac.check index 8e31835f0c..2e967cca94 100644 --- a/frontends/benchmarks/extraction/invalid/MutableField.scalac.check +++ b/frontends/benchmarks/extraction/invalid/MutableField.scalac.check @@ -1,4 +1,4 @@ -[ Error ] MutableField.scala:13:3: A mutable class (Nat) cannot have a non-@mutable and non-sealed parent (Box). +[ Error ] MutableField.scala:13:14: A mutable class (Nat) cannot have a non-@mutable and non-sealed parent (Box). [ Error ] Please annotate Box with @mutable. case class Nat() extends Box { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/MutateInside11.check b/frontends/benchmarks/extraction/invalid/MutateInside11.check index a187e04e90..a904806976 100644 --- a/frontends/benchmarks/extraction/invalid/MutateInside11.check +++ b/frontends/benchmarks/extraction/invalid/MutateInside11.check @@ -1,3 +1,10 @@ [ Error ] MutateInside11.scala:14:15: Illegal aliasing: Mut[Thing[Int]](thing) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of mut (Mut[Thing[Int]]) being mutable +[ Error ] -the definition of mut not being fresh +[ Error ] -the definition of mut containing variables of mutable types +[ Error ] that also appear after the declaration of mut: +[ Error ] -thing (of type Thing[Int]) +[ Error ] val mut = Mut(thing) ^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/MutateInside12.check b/frontends/benchmarks/extraction/invalid/MutateInside12.check index 448024182b..dcfbc165a3 100644 --- a/frontends/benchmarks/extraction/invalid/MutateInside12.check +++ b/frontends/benchmarks/extraction/invalid/MutateInside12.check @@ -1,3 +1,10 @@ [ Error ] MutateInside12.scala:14:15: Illegal aliasing: Mut[Thing[Int]](thing) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of mut (Mut[Thing[Int]]) being mutable +[ Error ] -the definition of mut not being fresh +[ Error ] -the definition of mut containing variables of mutable types +[ Error ] that also appear after the declaration of mut: +[ Error ] -thing (of type Thing[Int]) +[ Error ] val mut = Mut(thing) ^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.check b/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.check new file mode 100644 index 0000000000..dadab44b47 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.check @@ -0,0 +1,4 @@ +[ Error ] NewArrayNonPrimitive.scala:5:15: Cannot use array constructor for non-primitive type String +[ Error ] Hint: you may use `Array.fill` instead + val arr = new Array[String](len) + ^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.scala b/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.scala new file mode 100644 index 0000000000..7235c18365 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/NewArrayNonPrimitive.scala @@ -0,0 +1,7 @@ +object NewArrayNonPrimitive { + def test(len: Int): Unit = { + require(len >= 0) + // Cannot use array constructor for non-primitive type String + val arr = new Array[String](len) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/NonMutableTypeParameter.scalac.check b/frontends/benchmarks/extraction/invalid/NonMutableTypeParameter.scalac.check index 7bfb38dfac..e60597febf 100644 --- a/frontends/benchmarks/extraction/invalid/NonMutableTypeParameter.scalac.check +++ b/frontends/benchmarks/extraction/invalid/NonMutableTypeParameter.scalac.check @@ -1,3 +1,3 @@ -[ Error ] NonMutableTypeParameter.scala:4:1: Cannot extend non-mutable type parameter X with mutable type X @mutable. +[ Error ] NonMutableTypeParameter.scala:4:7: Cannot extend non-mutable type parameter X with mutable type X @mutable. trait MutableTypeParameter[@mutable X] extends NonMutableTypeParameters[X, X] { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/OpaqueInline.scalac.check b/frontends/benchmarks/extraction/invalid/OpaqueInline.scalac.check index e251f8ce9c..b6f6c2f687 100644 --- a/frontends/benchmarks/extraction/invalid/OpaqueInline.scalac.check +++ b/frontends/benchmarks/extraction/invalid/OpaqueInline.scalac.check @@ -1,3 +1,3 @@ -[ Error ] OpaqueInline.scala:7:3: Can't inline opaque or extern function, use @inlineOnce instead +[ Error ] OpaqueInline.scala:7:7: Can't inline opaque or extern function, use @inlineOnce instead def f(x: BigInt): Unit = () - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity1.scalac.check b/frontends/benchmarks/extraction/invalid/Purity1.scalac.check index c3c5ab3170..413801b135 100644 --- a/frontends/benchmarks/extraction/invalid/Purity1.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Purity1.scalac.check @@ -1,3 +1,3 @@ -[ Error ] Purity1.scala:11:5: Functions marked @pure cannot have side-effects +[ Error ] Purity1.scala:11:9: Functions marked @pure cannot have side-effects def bad(x: Int): Int = { - ^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity3.dotty.check b/frontends/benchmarks/extraction/invalid/Purity3.dotty.check index 31e471ef05..a0875a88b3 100644 --- a/frontends/benchmarks/extraction/invalid/Purity3.dotty.check +++ b/frontends/benchmarks/extraction/invalid/Purity3.dotty.check @@ -1,3 +1,6 @@ -[ Error ] Purity3.scala:15:7: Function `mutation` has effect on @pure parameter `box` +[ Error ] Purity3.scala:15:7: Function `mutation` has effect on the following @pure parameters: +[ Error ] -box +[ Error ] Hint: box is modified by the calls to the following functions: +[ Error ] -doStuff def mutation(@pure box: Box): Boolean = { ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity3.scalac.check b/frontends/benchmarks/extraction/invalid/Purity3.scalac.check index 6b8734fe47..1682ee4ff3 100644 --- a/frontends/benchmarks/extraction/invalid/Purity3.scalac.check +++ b/frontends/benchmarks/extraction/invalid/Purity3.scalac.check @@ -1,3 +1,6 @@ -[ Error ] Purity3.scala:15:3: Function `mutation` has effect on @pure parameter `box` +[ Error ] Purity3.scala:15:7: Function `mutation` has effect on the following @pure parameters: +[ Error ] -box +[ Error ] Hint: box is modified by the calls to the following functions: +[ Error ] -doStuff def mutation(@pure box: Box): Boolean = { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity4.dotty.check b/frontends/benchmarks/extraction/invalid/Purity4.dotty.check new file mode 100644 index 0000000000..7ff6a8e41a --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity4.dotty.check @@ -0,0 +1,7 @@ +[ Error ] Purity4.scala:7:7: Function `test` has effect on the following @pure parameters: +[ Error ] -b2 +[ Error ] Hint: b2 is modified by the calls to the following functions: +[ Error ] -externFn (an @extern function) +[ Error ] Hint: @extern functions taking mutable types are considered as impure, unless annotated with @pure + def test(b1: Box, @pure b2: Box): Unit = { + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity4.scala b/frontends/benchmarks/extraction/invalid/Purity4.scala new file mode 100644 index 0000000000..95123979e6 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity4.scala @@ -0,0 +1,16 @@ +import stainless.annotation._ + +object Purity4 { + + case class Box(var value: BigInt) + + def test(b1: Box, @pure b2: Box): Unit = { + externFn(b1, b2) + } + + @extern + def externFn(b1: Box, b2: Box): Unit = { + ??? + } + +} diff --git a/frontends/benchmarks/extraction/invalid/Purity4.scalac.check b/frontends/benchmarks/extraction/invalid/Purity4.scalac.check new file mode 100644 index 0000000000..9cca764b8e --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity4.scalac.check @@ -0,0 +1,7 @@ +[ Error ] Purity4.scala:7:7: Function `test` has effect on the following @pure parameters: +[ Error ] -b2 +[ Error ] Hint: b2 is modified by the calls to the following functions: +[ Error ] -externFn (an @extern function) +[ Error ] Hint: @extern functions taking mutable types are considered as impure, unless annotated with @pure + def test(b1: Box, @pure b2: Box): Unit = { + ^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity5.dotty.check b/frontends/benchmarks/extraction/invalid/Purity5.dotty.check new file mode 100644 index 0000000000..cd2b945487 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity5.dotty.check @@ -0,0 +1,6 @@ +[ Error ] Purity5.scala:9:7: Function `test` has effect on the following @pure parameters: +[ Error ] -b2 +[ Error ] Hint: b2 is modified by the calls to the following functions: +[ Error ] -sum + def test(st: SomeTrait, b1: Box, @pure b2: Box): BigInt = st.sum(b1, b2) + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/Purity5.scala b/frontends/benchmarks/extraction/invalid/Purity5.scala new file mode 100644 index 0000000000..cf4019ce52 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity5.scala @@ -0,0 +1,10 @@ +import stainless.annotation._ + +object Purity5 { + trait SomeTrait { + def sum(b1: Box, b2: Box): BigInt + } + case class Box(var value: BigInt) + + def test(st: SomeTrait, b1: Box, @pure b2: Box): BigInt = st.sum(b1, b2) +} diff --git a/frontends/benchmarks/extraction/invalid/Purity5.scalac.check b/frontends/benchmarks/extraction/invalid/Purity5.scalac.check new file mode 100644 index 0000000000..c8a90731c7 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/Purity5.scalac.check @@ -0,0 +1,6 @@ +[ Error ] Purity5.scala:9:7: Function `test` has effect on the following @pure parameters: +[ Error ] -b2 +[ Error ] Hint: b2 is modified by the calls to the following functions: +[ Error ] -sum + def test(st: SomeTrait, b1: Box, @pure b2: Box): BigInt = st.sum(b1, b2) + ^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr1.check b/frontends/benchmarks/extraction/invalid/RottenExpr1.check new file mode 100644 index 0000000000..bca662791a --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr1.check @@ -0,0 +1,10 @@ +[ Error ] RottenExpr1.scala:14:17: Illegal aliasing: buildFromC2Rotten(c1.c2) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of c2Cpy (C2) being mutable +[ Error ] -the definition of c2Cpy not being fresh +[ Error ] -the definition of c2Cpy containing variables of mutable types +[ Error ] that also appear after the declaration of c2Cpy: +[ Error ] -c1 (of type C1) +[ Error ] + val c2Cpy = buildFromC2Rotten(c1.c2) + ^^^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr1.scala b/frontends/benchmarks/extraction/invalid/RottenExpr1.scala new file mode 100644 index 0000000000..e510d8abeb --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr1.scala @@ -0,0 +1,17 @@ +object RottenExpr1 { + case class C1(var c2: C2) + case class C2(var c3: C3) + case class C3(var bi: BigInt) + + // This creates a C2 by sharing c2.c3 (which is mutable), + // therefore, this expression is not fresh + def buildFromC2Rotten(c2: C2) = C2(c2.c3) + + def test(x: BigInt): Unit = { + val c1 = C1(C2(C3(x))) + // This is not a fresh expression: we are not allowed to + // refer to `c1` in the body (illegal aliasing). + val c2Cpy = buildFromC2Rotten(c1.c2) + c1.c2.c3.bi = 3 + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr2.check b/frontends/benchmarks/extraction/invalid/RottenExpr2.check new file mode 100644 index 0000000000..e3824a00e3 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr2.check @@ -0,0 +1,11 @@ +[ Error ] RottenExpr2.scala:23:19: Illegal aliasing: Trout(c11, c12) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of f (Fish) being mutable +[ Error ] -the definition of f not being fresh +[ Error ] -the definition of f containing variables of mutable types +[ Error ] that also appear after the declaration of f: +[ Error ] -c11 (of type C1) +[ Error ] -c12 (of type C1) +[ Error ] + val f: Fish = Trout(c11, c12) + ^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr2.scala b/frontends/benchmarks/extraction/invalid/RottenExpr2.scala new file mode 100644 index 0000000000..c5aa9b962a --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr2.scala @@ -0,0 +1,29 @@ +object RottenExpr2 { + case class C1(var c2: C2) + case class C2(var c3: C3) + case class C3(var bi: BigInt) + + sealed trait Fish + case class Trout(c11: C1, c12: C1) extends Fish + case class Cod(c1: C1, c2: C2) extends Fish + case class Salmon(c1: C1, c3: C3) extends Fish + + def swapped(c1: C1): C1 = C1(C2(C3(-c1.c2.c3.bi))) + + def pickC2(c1: C1): C2 = c1.c2 + + def test(x: BigInt): Fish = { + val c11 = C1(C2(C3(x))) + val c12 = swapped(c11) + // `test` returns a fresh fish. + // However, we are not allowed to refer to `c11` or `c12` + // in the remaining of the function, because val-binding require the value + // to be bound *fresh w.r.t. an empty context* -- which is note the case + // (`c11` and `c12` are to be considered abstract as if we were to "forget" their values). + val f: Fish = Trout(c11, c12) + // "Illegal aliasing" triggered due to these lines + val c11bis = c11 + val c12bis = c12 + f + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr3.dotty.check b/frontends/benchmarks/extraction/invalid/RottenExpr3.dotty.check new file mode 100644 index 0000000000..b47a8d9e5c --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr3.dotty.check @@ -0,0 +1,3 @@ +[ Error ] RottenExpr3.scala:11:7: Illegal recursive functions returning non-fresh result + def test(c11: C1, x: BigInt): Fish = { + ^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr3.scala b/frontends/benchmarks/extraction/invalid/RottenExpr3.scala new file mode 100644 index 0000000000..e48297022b --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr3.scala @@ -0,0 +1,19 @@ +object RottenExpr3 { + case class C1(var c2: C2) + case class C2(var c3: C3) + case class C3(var bi: BigInt) + + sealed trait Fish + case class Trout(c11: C1, c12: C1) extends Fish + case class Cod(c1: C1, c2: C2) extends Fish + case class Salmon(c1: C1, c3: C3) extends Fish + + def test(c11: C1, x: BigInt): Fish = { + require(x >= 0) + val c12 = C1(C2(C3(x))) + // This is not a fresh fish, therefore `test` cannot be recursive + val f: Fish = Trout(c11, c12) + if (x == 0) f + else test(c11, x - 1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/RottenExpr3.scalac.check b/frontends/benchmarks/extraction/invalid/RottenExpr3.scalac.check new file mode 100644 index 0000000000..ca14347336 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/RottenExpr3.scalac.check @@ -0,0 +1,3 @@ +[ Error ] RottenExpr3.scala:11:7: Illegal recursive functions returning non-fresh result + def test(c11: C1, x: BigInt): Fish = { + ^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/TraitVar1.scalac.check b/frontends/benchmarks/extraction/invalid/TraitVar1.scalac.check index bd26995189..08ea98ad11 100644 --- a/frontends/benchmarks/extraction/invalid/TraitVar1.scalac.check +++ b/frontends/benchmarks/extraction/invalid/TraitVar1.scalac.check @@ -1,6 +1,6 @@ -[ Error ] TraitVar1.scala:13:3: Abstract values `prop` must be overridden with fields in concrete subclass +[ Error ] TraitVar1.scala:13:14: Abstract values `prop` must be overridden with fields in concrete subclass case class Bar(var stuff: BigInt) extends Foo { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... -[ Error ] TraitVar1.scala:15:5: Cannot override a `var` accessor with a non-accessor method. + ^^^ +[ Error ] TraitVar1.scala:15:9: Cannot override a `var` accessor with a non-accessor method. def prop_=(y: BigInt): Unit = { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... \ No newline at end of file + ^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1099a.check b/frontends/benchmarks/extraction/invalid/i1099a.check index a34ecd8328..f78d308caf 100644 --- a/frontends/benchmarks/extraction/invalid/i1099a.check +++ b/frontends/benchmarks/extraction/invalid/i1099a.check @@ -1,3 +1,5 @@ -[ Error ] i1099a.scala:9:5: Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body) +[ Error ] i1099a.scala:9:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] a1: A val c = C(a2) ^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1099b.check b/frontends/benchmarks/extraction/invalid/i1099b.check index 04cfc6c0cf..48dae75bb6 100644 --- a/frontends/benchmarks/extraction/invalid/i1099b.check +++ b/frontends/benchmarks/extraction/invalid/i1099b.check @@ -1,3 +1,5 @@ -[ Error ] i1099b.scala:9:5: Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body) +[ Error ] i1099b.scala:9:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] a1: A val c = C(a2) ^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1099c.check b/frontends/benchmarks/extraction/invalid/i1099c.check index dae1cbc43f..5611136482 100644 --- a/frontends/benchmarks/extraction/invalid/i1099c.check +++ b/frontends/benchmarks/extraction/invalid/i1099c.check @@ -1,3 +1,5 @@ -[ Error ] i1099c.scala:19:5: Unsupported `val` definition in AntiAliasing (couldn't compute targets and there are mutable variables shared between the binding and the body) +[ Error ] i1099c.scala:19:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] a1: A val c = C(a3) ^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1099d.check b/frontends/benchmarks/extraction/invalid/i1099d.check new file mode 100644 index 0000000000..6f67d2c249 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/i1099d.check @@ -0,0 +1,5 @@ +[ Error ] i1099d.scala:9:5: Unsupported `val` definition in AntiAliasing +[ Error ] The following variables of mutable types are shared between the binding and the body: +[ Error ] a0: A, a1: A + val c = C(a2) + ^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1099d.scala b/frontends/benchmarks/extraction/invalid/i1099d.scala new file mode 100644 index 0000000000..deea7b157d --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/i1099d.scala @@ -0,0 +1,14 @@ +object i1099d { + case class A(var x: Int) + case class C(a: A) + + def f(a0: A, a1: A, cond: Boolean) = { + require(a0.x == 0 && a1.x == 1) + val a2 = if (cond) a0 else a1 + // Illegal aliasing + val c = C(a2) + c.a.x += 1 + val useA0 = a0 + val useA1 = a1 + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1218.check b/frontends/benchmarks/extraction/invalid/i1218.check index 9edf0fcaa9..ba586a1e3d 100644 --- a/frontends/benchmarks/extraction/invalid/i1218.check +++ b/frontends/benchmarks/extraction/invalid/i1218.check @@ -1,3 +1,10 @@ [ Error ] i1218.scala:6:16: Illegal aliasing: arr.updated(1, B(42)) +[ Error ] Hint: this error occurs due to: +[ Error ] -the type of arr2 (Array[B]) being mutable +[ Error ] -the definition of arr2 not being fresh +[ Error ] -the definition of arr2 containing variables of mutable types +[ Error ] that also appear after the declaration of arr2: +[ Error ] -arr (of type Array[B]) +[ Error ] val arr2 = arr.updated(1, B(42)) ^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1474.check b/frontends/benchmarks/extraction/invalid/i1474.check new file mode 100644 index 0000000000..c6e5fa7725 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/i1474.check @@ -0,0 +1,6 @@ +[ Error ] i1474.scala:23:13: Invariants cannot have side-effects +[ Error ] Hint: the invariant calls the following impure functions: +[ Error ] -getLast (an @extern function) +[ Error ] Hint: @extern functions taking mutable types are considered as impure, unless annotated with @pure + require(last == getLast(first)) + ^^^^^^^^^^^^^^^^^^^^^^ \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i1474.scala b/frontends/benchmarks/extraction/invalid/i1474.scala new file mode 100644 index 0000000000..cfb7e26855 --- /dev/null +++ b/frontends/benchmarks/extraction/invalid/i1474.scala @@ -0,0 +1,32 @@ +object i1474 { + import stainless.lang._ + import stainless.lang.StaticChecks._ + import stainless.annotation._ + + @mutable + sealed abstract class Opt[@mutable T] + case class NONE[@mutable T]() extends Opt[T] + case class SOME[@mutable T](value: T) extends Opt[T] + + final case class Node(var head: Int, var next: Cell[Opt[Node]]) + + @extern + def getLast(n: Node): Node = { + n.next.v match { + case NONE() => n + case SOME(next) => getLast(next) + } + } + + final case class TList(var first: Node, + var last: Node) { + require(last == getLast(first)) + } + + def mkEnd(head: Int): Node = Node(head, Cell(NONE[Node]())) + + def test = { + val n1: Node = mkEnd(5) + val l: TList = TList(n1, freshCopy(n1)) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/extraction/invalid/i827b.scalac.check b/frontends/benchmarks/extraction/invalid/i827b.scalac.check index 884b048850..bce6ddc62d 100644 --- a/frontends/benchmarks/extraction/invalid/i827b.scalac.check +++ b/frontends/benchmarks/extraction/invalid/i827b.scalac.check @@ -1,6 +1,6 @@ -[ Error ] i827b.scala:4:3: test depends on missing dependencies: +[ Error ] i827b.scala:4:7: test depends on missing dependencies: def test: Char = { - ^^^^^^^^^^^^^^^^^^... + ^^^^ [ Error ] Method augmentString [ Error ] Hint: this method comes from the Scala standard library and is currently not supported. [ Error ] i827b.scala:6:5: @@ -12,9 +12,9 @@ s(0) ^^^^ [ Error ] -[ Error ] i827b.scala:9:3: test2 depends on missing dependencies: +[ Error ] i827b.scala:9:7: test2 depends on missing dependencies: def test2(x: StringBuilder): Unit = { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... + ^^^^^ [ Error ] Method += [ Error ] Hint: this method comes from the Scala standard library and is currently not supported. [ Error ] i827b.scala:10:5: @@ -26,9 +26,9 @@ def test2(x: StringBuilder): Unit = { ^^^^^^^^^^^^^^^^ [ Error ] -[ Error ] i827b.scala:13:3: test3 depends on missing dependencies: +[ Error ] i827b.scala:13:7: test3 depends on missing dependencies: def test3(y: StringBuilder): Unit = { - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^... + ^^^^^ [ Error ] Method += [ Error ] Hint: this method comes from the Scala standard library and is currently not supported. [ Error ] i827b.scala:14:5: diff --git a/frontends/benchmarks/extraction/valid/StateMonadTypeRefIssue.scala b/frontends/benchmarks/extraction/valid/StateMonadTypeRefIssue.scala new file mode 100644 index 0000000000..fbc8d3825f --- /dev/null +++ b/frontends/benchmarks/extraction/valid/StateMonadTypeRefIssue.scala @@ -0,0 +1,38 @@ +object StateMonad { + + case class St[A,S](fun: S => (A,S)) { + def ^=(that: St[A,S])(implicit anyS: S): St[A,S] = { + assert(this.fun(anyS) == that.fun(anyS)) + that + } + + def qed: Unit = + ??? + + def flatMap[B](f: A => St[B,S]): St[B,S] = + St[B,S] { + s0 => + val (a,s1) = fun(s0) + f(a).fun(s1) + } + + def unit[A,S](a: A) = + St[A,S] { + (s:S) => (a,s) + } + + def leftUnit[A,S,B](a: A, f: A => St[B,S])(implicit anyS: S): Unit = { + (unit(a).flatMap(f) ^= + St((s:S) => (a:A,s:S)).flatMap(f) ^= + St { (s0:S) => + val (a1:A,s1:S) = ((s:S) => (a,s))(s0) + f(a1).fun(s1) } ^= + St { s0 => + val (a1:A,s1:S) = (a,s0) + f(a1).fun(s1) } ^= + St(s0 => f(a).fun(s0)) ^= + f(a)) + .qed + }.ensuring(_ => unit(a).flatMap(f) == f(a)) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/InnerFuns.scala b/frontends/benchmarks/filtering/innerfuns/InnerFuns.scala new file mode 100644 index 0000000000..c054a92902 --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/InnerFuns.scala @@ -0,0 +1,28 @@ +object InnerFuns { + def myOuterFun1(x: BigInt): BigInt = { + def myInnerFun1_1(x: BigInt): BigInt = { + def myInnerInnerFun1_1(x: BigInt): BigInt = { + def myInnerInnerInnerFun1_1(x: BigInt): BigInt = x + x + } + def myInnerInnerFun1_2(x: BigInt): BigInt = x + x + } + def myInnerFun1_2(x: BigInt): BigInt = x + x + } + + + def myOuterFun2(x: BigInt): BigInt = { + def myInnerFun2_1(x: BigInt): BigInt = { + def myInnerInnerFun2_1(x: BigInt): BigInt = { + def myInnerInnerInnerFun2_1(x: BigInt): BigInt = x + x + } + def myInnerInnerFun2_2(x: BigInt): BigInt = x + x + } + def myInnerFun2_2(x: BigInt): BigInt = x + x + } +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/test_conf_1.json b/frontends/benchmarks/filtering/innerfuns/test_conf_1.json new file mode 100644 index 0000000000..a2ebebbe27 --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/test_conf_1.json @@ -0,0 +1,13 @@ +{ + "toKeep": [ + "myOuterFun1" + ], + "expected": [ + "myOuterFun1", + "myInnerFun1_1", + "myInnerFun1_2", + "myInnerInnerFun1_1", + "myInnerInnerFun1_2", + "myInnerInnerInnerFun1_1" + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/test_conf_2.json b/frontends/benchmarks/filtering/innerfuns/test_conf_2.json new file mode 100644 index 0000000000..29f9e4f4a1 --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/test_conf_2.json @@ -0,0 +1,11 @@ +{ + "toKeep": [ + "myInnerFun1_1" + ], + "expected": [ + "myInnerFun1_1", + "myInnerInnerFun1_1", + "myInnerInnerFun1_2", + "myInnerInnerInnerFun1_1" + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/test_conf_3.json b/frontends/benchmarks/filtering/innerfuns/test_conf_3.json new file mode 100644 index 0000000000..5e84a32fde --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/test_conf_3.json @@ -0,0 +1,9 @@ +{ + "toKeep": [ + "myInnerInnerFun1_1" + ], + "expected": [ + "myInnerInnerFun1_1", + "myInnerInnerInnerFun1_1" + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/test_conf_4.json b/frontends/benchmarks/filtering/innerfuns/test_conf_4.json new file mode 100644 index 0000000000..e4911e8ee3 --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/test_conf_4.json @@ -0,0 +1,8 @@ +{ + "toKeep": [ + "myInnerInnerInnerFun1_1" + ], + "expected": [ + "myInnerInnerInnerFun1_1" + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/innerfuns/test_conf_5.json b/frontends/benchmarks/filtering/innerfuns/test_conf_5.json new file mode 100644 index 0000000000..d298ec522d --- /dev/null +++ b/frontends/benchmarks/filtering/innerfuns/test_conf_5.json @@ -0,0 +1,14 @@ +{ + "toKeep": [ + "myInnerFun1_1", + "myInnerInnerFun2_1" + ], + "expected": [ + "myInnerFun1_1", + "myInnerInnerFun1_1", + "myInnerInnerFun1_2", + "myInnerInnerInnerFun1_1", + "myInnerInnerFun2_1", + "myInnerInnerInnerFun2_1" + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/while/DerivedWhile.scala b/frontends/benchmarks/filtering/while/DerivedWhile.scala new file mode 100644 index 0000000000..e4fcffe432 --- /dev/null +++ b/frontends/benchmarks/filtering/while/DerivedWhile.scala @@ -0,0 +1,35 @@ +object DerivedWhile { + def myFunction1(x: BigInt): BigInt = { + require(x >= 0) + var i = 0: BigInt + while (i < x) { + i += 1 + } + i + } + + + def myFunction2(x: BigInt): BigInt = { + require(x >= 0) + var i = 0: BigInt + while (i < x) { + i += 1 + while (i < x) { + i += 1 + } + } + i + } + + def myFunction3(x: BigInt): BigInt = { + require(x >= 0) + var i = 0: BigInt + while (i < x) { + i += 1 + } + while (i < 5) { + i += 1 + } + i + } +} \ No newline at end of file diff --git a/frontends/benchmarks/filtering/while/test_conf.json b/frontends/benchmarks/filtering/while/test_conf.json new file mode 100644 index 0000000000..1ee14e1662 --- /dev/null +++ b/frontends/benchmarks/filtering/while/test_conf.json @@ -0,0 +1,33 @@ +{ + "toKeep": [ + "myFunction1", + "myFunction2", + "myFunction3" + ], + "expected": [ + { + "fn": "myFunction1", + "occurrences": 1 + }, + { + "fn": "myFunction1While", + "occurrences": 1 + }, + { + "fn": "myFunction2", + "occurrences": 1 + }, + { + "fn": "myFunction2While", + "occurrences": 2 + }, + { + "fn": "myFunction3", + "occurrences": 1 + }, + { + "fn": "myFunction3While", + "occurrences": 2 + } + ] +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/invalid/ExternMutation.scala b/frontends/benchmarks/imperative/invalid/ExternMutation.scala index c1dc35bc50..bc961c668e 100644 --- a/frontends/benchmarks/imperative/invalid/ExternMutation.scala +++ b/frontends/benchmarks/imperative/invalid/ExternMutation.scala @@ -1,4 +1,6 @@ +import stainless.lang._ import stainless.annotation._ +import StaticChecks._ object ExternMutation { case class Box(var value: BigInt) @@ -8,7 +10,7 @@ object ExternMutation { def f2(b: Container[Box]): Unit = ??? def g2(b: Container[Box]) = { - val b0 = b + @ghost val b0 = snapshot(b) f2(b) assert(b == b0) // fails because `Container` is mutable } diff --git a/frontends/benchmarks/imperative/invalid/NegativeArrayFill.scala b/frontends/benchmarks/imperative/invalid/NegativeArrayFill.scala new file mode 100644 index 0000000000..5faac81abb --- /dev/null +++ b/frontends/benchmarks/imperative/invalid/NegativeArrayFill.scala @@ -0,0 +1,5 @@ +object NegativeArrayFill { + def test(len: Int): Unit = { + val arr = Array.fill(len)(42) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/invalid/NewArrayNeg.scala b/frontends/benchmarks/imperative/invalid/NewArrayNeg.scala new file mode 100644 index 0000000000..d80bb203eb --- /dev/null +++ b/frontends/benchmarks/imperative/invalid/NewArrayNeg.scala @@ -0,0 +1,5 @@ +object NewArrayNeg { + def test(len: Int): Unit = { + val arr = new Array[Long](len) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/invalid/OpaqueMutation1.scala b/frontends/benchmarks/imperative/invalid/OpaqueMutation1.scala new file mode 100644 index 0000000000..2e8b840f71 --- /dev/null +++ b/frontends/benchmarks/imperative/invalid/OpaqueMutation1.scala @@ -0,0 +1,32 @@ +import stainless.lang.{ghost => ghostExpr, _} +import stainless.proof._ +import stainless.annotation._ +import StaticChecks._ + +object OpaqueMutation1 { + + case class Box(var cnt: BigInt, var other: BigInt) { + @opaque // Note the opaque + def secretSauce(x: BigInt): BigInt = cnt + x // Nobody thought of it! + + @opaque // Note the opaque here as well + def increment(): Unit = { + @ghost val oldBox = snapshot(this) + cnt += 1 + ghostExpr { + unfold(secretSauce(other)) + unfold(oldBox.secretSauce(other)) + check(oldBox.secretSauce(other) + 1 == this.secretSauce(other)) + } + }.ensuring(_ => old(this).secretSauce(other) + 1 == this.secretSauce(other)) + } + + def test(b: Box): Unit = { + @ghost val oldBox = snapshot(b) + b.increment() + // Note that, even though the implementation of `increment` does not alter `other`, + // we do not have that knowledge here since the function is marked as opaque. + // Therefore, the following is incorrect (but it holds for `b.other`, see the other `valid/OpaqueMutation`) + assert(oldBox.secretSauce(oldBox.other) + 1 == b.secretSauce(oldBox.other)) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/invalid/OpaqueMutation2.scala b/frontends/benchmarks/imperative/invalid/OpaqueMutation2.scala new file mode 100644 index 0000000000..6f0498bc77 --- /dev/null +++ b/frontends/benchmarks/imperative/invalid/OpaqueMutation2.scala @@ -0,0 +1,33 @@ +import stainless.lang.{ghost => ghostExpr, _} +import stainless.proof._ +import stainless.annotation._ +import StaticChecks._ + +object OpaqueMutation2 { + case class SmallerBox(var otherCnt: BigInt) + + case class Box(var cnt: BigInt, var smallerBox: SmallerBox) { + @opaque // Note the opaque + def secretSauce(x: BigInt): BigInt = cnt + x // Nobody thought of it! + + @opaque // Note the opaque here as well + def increment(): Unit = { + @ghost val oldBox = snapshot(this) + cnt += 1 + ghostExpr { + unfold(secretSauce(smallerBox.otherCnt)) + unfold(oldBox.secretSauce(smallerBox.otherCnt)) + check(oldBox.secretSauce(smallerBox.otherCnt) + 1 == this.secretSauce(smallerBox.otherCnt)) + } + }.ensuring(_ => old(this).secretSauce(smallerBox.otherCnt) + 1 == this.secretSauce(smallerBox.otherCnt)) + } + + def test(b: Box): Unit = { + @ghost val oldBox = snapshot(b) + b.increment() + // Note that, even though the implementation of `increment` does not alter `smallerBox`, + // we do not have that knowledge here since the function is marked as opaque. + // Therefore, the following is incorrect (but it holds for `b.other`, see the other `valid/OpaqueMutation`) + assert(oldBox.secretSauce(oldBox.smallerBox.otherCnt) + 1 == b.secretSauce(oldBox.smallerBox.otherCnt)) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/ArgsEffect1.scala b/frontends/benchmarks/imperative/valid/ArgsEffect1.scala new file mode 100644 index 0000000000..582c248d66 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/ArgsEffect1.scala @@ -0,0 +1,18 @@ +import stainless.annotation._ + +object ArgsEffect1 { + + case class BitStream(bits: Array[Byte]) { + @pure + def validate_offset_bits(i: Int): Boolean = true + } + + @mutable + trait Codec { + def bitStream: BitStream + + def peekBit(): Unit = { + val isValidPrecondition = bitStream.validate_offset_bits(1) + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/ArgsEffect2.scala b/frontends/benchmarks/imperative/valid/ArgsEffect2.scala new file mode 100644 index 0000000000..fa0221ef01 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/ArgsEffect2.scala @@ -0,0 +1,19 @@ +import stainless.annotation._ + +object ArgsEffect2 { + + case class MutClass(var i: BigInt) { + def inc: MutClass = { + i += 1 + this + } + @pure + def isEqual(j: BigInt): Boolean = i == j + } + + def test(mc: MutClass): Unit = { + val old = mc.i + val res = mc.inc.isEqual(old + 1) + assert(res) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/Arithmetic.scala b/frontends/benchmarks/imperative/valid/Arithmetic.scala index 9b81122f3f..2a46aa9c49 100644 --- a/frontends/benchmarks/imperative/valid/Arithmetic.scala +++ b/frontends/benchmarks/imperative/valid/Arithmetic.scala @@ -10,12 +10,14 @@ object Arithmetic { if(y < 0) { var n = y (while(n != BigInt(0)) { + decreases(-n) r = r - x n = n + 1 }) invariant(r == x * (y - n) && 0 <= -n) } else { var n = y (while(n != BigInt(0)) { + decreases(n) r = r + x n = n - 1 }) invariant(r == x * (y - n) && 0 <= n) @@ -29,12 +31,14 @@ object Arithmetic { if(y < 0) { var n = y (while(n != BigInt(0)) { + decreases(-n) r = r - 1 n = n + 1 }) invariant(r == x + y - n && 0 <= -n) } else { var n = y (while(n != BigInt(0)) { + decreases(n) r = r + 1 n = n - 1 }) invariant(r == x + y - n && 0 <= n) @@ -48,6 +52,7 @@ object Arithmetic { var r = BigInt(0) var i = BigInt(0) (while(i < n) { + decreases(n - i) i = i + 1 r = r + i }) invariant(r >= i && i >= 0 && r >= 0) @@ -59,6 +64,7 @@ object Arithmetic { var r = x var q = BigInt(0) (while(r >= y) { + decreases(r - y) r = r - y q = q + 1 }) invariant(x == y*q + r && r >= 0) diff --git a/frontends/benchmarks/imperative/valid/Array3.scala b/frontends/benchmarks/imperative/valid/Array3.scala index 7f5fe843e9..637ca3ad58 100644 --- a/frontends/benchmarks/imperative/valid/Array3.scala +++ b/frontends/benchmarks/imperative/valid/Array3.scala @@ -9,6 +9,7 @@ object Array3 { var i = 0 var sum = 0 (while(i < a.length) { + decreases(a.length - i) sum = sum + a(i) i = i + 1 }) invariant(i >= 0) diff --git a/frontends/benchmarks/imperative/valid/Array4.scala b/frontends/benchmarks/imperative/valid/Array4.scala index b85b037591..230a255d73 100644 --- a/frontends/benchmarks/imperative/valid/Array4.scala +++ b/frontends/benchmarks/imperative/valid/Array4.scala @@ -8,6 +8,7 @@ object Array4 { var i = 0 var sum = 0 (while(i < a.length) { + decreases(a.length - i) sum = sum + a(i) i = i + 1 }) invariant(i >= 0) diff --git a/frontends/benchmarks/imperative/valid/Array5.scala b/frontends/benchmarks/imperative/valid/Array5.scala index 5fc90c5985..0c3c0b3852 100644 --- a/frontends/benchmarks/imperative/valid/Array5.scala +++ b/frontends/benchmarks/imperative/valid/Array5.scala @@ -8,6 +8,7 @@ object Array5 { var i = 0 var sum = 0 (while(i < a.length) { + decreases(a.length - i) sum = sum + a(i) i = i + 1 }) invariant(i >= 0) diff --git a/frontends/benchmarks/imperative/valid/ArrayParamMutation2.scala b/frontends/benchmarks/imperative/valid/ArrayParamMutation2.scala index db71a15d0a..d13bb12ec3 100644 --- a/frontends/benchmarks/imperative/valid/ArrayParamMutation2.scala +++ b/frontends/benchmarks/imperative/valid/ArrayParamMutation2.scala @@ -4,6 +4,7 @@ object ArrayParamMutation2 { def rec(a: Array[BigInt]): BigInt = { require(a.length > 1 && a(0) >= 0) + decreases(a(0)) if(a(0) == 0) a(1) else { diff --git a/frontends/benchmarks/imperative/valid/ArrayParamMutation3.scala b/frontends/benchmarks/imperative/valid/ArrayParamMutation3.scala index d3731de83f..c6a35328aa 100644 --- a/frontends/benchmarks/imperative/valid/ArrayParamMutation3.scala +++ b/frontends/benchmarks/imperative/valid/ArrayParamMutation3.scala @@ -4,6 +4,7 @@ object ArrayParamMutation3 { def odd(a: Array[BigInt]): Boolean = { require(a.length > 0 && a(0) >= 0) + decreases(a(0)) if(a(0) == 0) false else { a(0) = a(0) - 1 @@ -13,6 +14,7 @@ object ArrayParamMutation3 { def even(a: Array[BigInt]): Boolean = { require(a.length > 0 && a(0) >= 0) + decreases(a(0)) if(a(0) == 0) true else { a(0) = a(0) - 1 diff --git a/frontends/benchmarks/imperative/valid/ArrayParamMutation5.scala b/frontends/benchmarks/imperative/valid/ArrayParamMutation5.scala index f9f605217f..e04b3536b8 100644 --- a/frontends/benchmarks/imperative/valid/ArrayParamMutation5.scala +++ b/frontends/benchmarks/imperative/valid/ArrayParamMutation5.scala @@ -1,8 +1,10 @@ import stainless.lang._ +import stainless.annotation._ object ArrayParamMutation5 { + @inlineOnce def mutuallyRec1(a1: Array[BigInt], a2: Array[BigInt]): Unit = { require(a1.length > 0 && a1(0) > 0 && a2.length > 0) if(a1(0) == 10) { @@ -14,6 +16,7 @@ object ArrayParamMutation5 { def mutuallyRec2(a1: Array[BigInt], a2: Array[BigInt]): Unit = { require(a1.length > 0 && a2.length > 0 && a1(0) > 0) + decreases(if (a1(0) == 10) 1 else 0) a1(0) = 10 mutuallyRec1(a1, a2) } diff --git a/frontends/benchmarks/imperative/valid/ArrayParamMutation8.scala b/frontends/benchmarks/imperative/valid/ArrayParamMutation8.scala index 7a37d1db43..04fe03cfd1 100644 --- a/frontends/benchmarks/imperative/valid/ArrayParamMutation8.scala +++ b/frontends/benchmarks/imperative/valid/ArrayParamMutation8.scala @@ -6,6 +6,7 @@ object ArrayParamMutation8 { def odd(a: Array[BigInt]): Boolean = { require(a.length > 0 && a(0) >= 0) + decreases(a(0)) if(a(0) == 0) false else { a(0) = a(0) - 1 @@ -15,6 +16,7 @@ object ArrayParamMutation8 { def even(a: Array[BigInt]): Boolean = { require(a.length > 0 && a(0) >= 0) + decreases(a(0)) if(a(0) == 0) true else { a(0) = a(0) - 1 diff --git a/frontends/benchmarks/imperative/valid/ArrayParamMutation9.scala b/frontends/benchmarks/imperative/valid/ArrayParamMutation9.scala index c8750cd0de..3fa583396b 100644 --- a/frontends/benchmarks/imperative/valid/ArrayParamMutation9.scala +++ b/frontends/benchmarks/imperative/valid/ArrayParamMutation9.scala @@ -5,6 +5,7 @@ object ArrayParamMutation9 { require(a.length > 0) var i = 0; (while (i < a.length) { + decreases(a.length - i) a(i) = if (a(i) < 0) -a(i) else a(i) i = i + 1 }) invariant(i >= 0) diff --git a/frontends/benchmarks/imperative/valid/CellSwap.scala b/frontends/benchmarks/imperative/valid/CellSwap.scala new file mode 100644 index 0000000000..1ffc5ed1c2 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/CellSwap.scala @@ -0,0 +1,20 @@ +import stainless.lang.swap +import stainless.lang.Cell + +object CellSwap { + def test(c1: Cell[Int], c2: Cell[Int]): Unit = { + require(c1.v == 1 && c2.v == 2) + + swap(c1, c2) + assert(c1.v == 2) + assert(c2.v == 1) + swap(c1, c1) + assert(c1.v == 2) + c1.v = 42 + assert(c1.v == 42) + swap(c1, c2) + assert(c1.v == 1) + assert(c2.v == 42) + } + +} diff --git a/frontends/benchmarks/imperative/valid/ClassReconstruction.scala b/frontends/benchmarks/imperative/valid/ClassReconstruction.scala new file mode 100644 index 0000000000..7937a9ce80 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/ClassReconstruction.scala @@ -0,0 +1,51 @@ +import stainless.annotation._ + +object ClassReconstruction { + + case class BoundedCounter(var counter: BigInt, var bound: BigInt) { + require(0 <= counter && counter < bound) + + @opaque // Note the opaque + def bcTryAdd(i: BigInt): Unit = { + if (0 <= i && counter + i < bound) { + counter += i + } + } + } + + @mutable + trait BoundedCounterContainer { + val boundedCounter: BoundedCounter + + final def tryAdd(i: BigInt): Unit = { + boundedCounter.bcTryAdd(i) + } + + final def tryAdd2(i: BigInt): Unit = { + // After AntiAliasing, `tryAdd2` is as follows: + // + // var thiss: BoundedCounterContainer = thiss + // ({ + // var res: (Unit, BoundedCounterContainer) = tryAdd(thiss, i) + // thiss = @DropVCs (if (res._2.isInstanceOf[BoundedCounterContainerExt]) { + // @DropVCs BoundedCounterContainerExt( + // @DropVCs thiss.asInstanceOf[BoundedCounterContainerExt].__x, + // BoundedCounter( + // @DropVCs res._2.asInstanceOf[BoundedCounterContainerExt].boundedCounter.counter, + // @DropVCs thiss.asInstanceOf[BoundedCounterContainerExt].boundedCounter.bound)) + // } else { + // thiss + // }).asInstanceOf[BoundedCounterContainer] + // res._1 + // }, thiss) + // + // Note that we are constructing a new BoundedCounter with `res._2.counter` and this.boundedCounter.bound. + // Since `bcTryAdd` is opaque, the solver does not know the relationship between `res._2.counter` and `this.boundedCounter.bound`. + // So without further constraints, this will result in an invalid VC. + // However we know that calling `tryAdd` (which in turns calls `bcTryAdd`) leads to a valid `BoundedCounter`, + // and that this.boundedCounter.bound is equal to res._2.boundedCounter.bound, therefore making this VC valid by construction + // We therefore must annotate it with @DropVCs + tryAdd(i) + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/ExternMutation.scala b/frontends/benchmarks/imperative/valid/ExternMutation.scala new file mode 100644 index 0000000000..4ad7281ed1 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/ExternMutation.scala @@ -0,0 +1,15 @@ +import stainless.annotation._ + +object ExternMutation { + case class Box(var value: BigInt) + case class Container[@mutable T](t: T) + + @extern + def f2(b: Container[Box]): Unit = ??? + + def g2(b: Container[Box]) = { + val b0 = b + f2(b) + assert(b == b0) // Ok, even though `b` is assumed to be modified because `b0` is an alias of `b` + } +} diff --git a/frontends/benchmarks/imperative/valid/FreshExpr1.scala b/frontends/benchmarks/imperative/valid/FreshExpr1.scala new file mode 100644 index 0000000000..ae762831e2 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/FreshExpr1.scala @@ -0,0 +1,73 @@ +import stainless.lang._ + +object FreshExpr1 { + case class C1(var c2: C2) + case class C2(var c3: C3) + case class C3(var bi: BigInt) + + // This builds a fresh C2 because we build a fresh C3 + def buildCopyFromC2(c2: C2): C2 = C2(buildCopyFromC3(c2.c3)) + + def buildCopyFromC3(c3: C3): C3 = C3(c3.bi) + + def test1(x: BigInt): Unit = { + val c1 = C1(C2(C3(x))) + // This is a fresh expression, so we can refer to + // `c1` and `c2Cpy` freely in the remaining body + val c2Cpy = buildCopyFromC2(c1.c2) + c1.c2.c3.bi = 3 + assert(c1.c2.c3.bi == 3) + assert(c2Cpy.c3.bi == x) + } + + sealed trait Fish + case class Trout(c11: C1, c12: C1) extends Fish + case class Cod(c1: C1, c2: C2) extends Fish + case class Salmon(c1: C1, c3: C3) extends Fish + + def swapped(c1: C1): C1 = C1(C2(C3(-c1.c2.c3.bi))) + + def pickC2(c1: C1): C2 = c1.c2 + + def test2(x: BigInt): Fish = { + require(x >= 0) + decreases(x) + val c11 = C1(C2(C3(x))) + val c12 = swapped(c11) + // `test2` returns a fresh fish therefore, it is allowed to be recursive. + // However, note that we are not allowed to refer to `c11` or `c12` + // in the remaining of the function, because val-binding require the value + // to be bound *fresh w.r.t. an empty context* (i.e. `c11` and `c12` are + // considered abstract because we "forget" their values). + val f: Fish = Trout(c11, c12) + val c2 = f match { + case Trout(c11, _) => pickC2(c11) + } + c2.c3.bi = 3 + assert(f match { + case Trout(c11, _) => c11.c2.c3.bi == 3 + }) + if (x == 0) f + else test2(x - 1) + } + + // Similar to `test2` but tests pattern matching freshness propagation + def test3(x: BigInt): C1 = { + require(x >= 0) + decreases(x) + val c11 = C1(C2(C3(x))) + val c12 = swapped(c11) + val f: Fish = Trout(c11, c12) + val c2 = f match { + case Trout(c11, _) => pickC2(c11) + } + c2.c3.bi = 3 + assert(f match { + case Trout(c11, _) => c11.c2.c3.bi == 3 + }) + if (x == 0) f match { + case Trout(c11, _) => c11 + } + else test3(x - 1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/FreshExpr2.scala b/frontends/benchmarks/imperative/valid/FreshExpr2.scala new file mode 100644 index 0000000000..795e4bdc1e --- /dev/null +++ b/frontends/benchmarks/imperative/valid/FreshExpr2.scala @@ -0,0 +1,18 @@ +import stainless.lang._ + +object FreshExpr2 { + + def iden(arr: Array[Int]) = arr + + // Recursive functions must return fresh expression + def counting(i: Int): Array[Int] = { + require(0 <= i && i <= 10) + decreases(10 - i) + if (i < 10) { + counting(i + 1) + } else { + val b = Array.fill(0)(0) + iden(b) // This is a fresh expression + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/FunctionCaching.scala b/frontends/benchmarks/imperative/valid/FunctionCaching.scala index 8a17dcfca2..83363b17f2 100644 --- a/frontends/benchmarks/imperative/valid/FunctionCaching.scala +++ b/frontends/benchmarks/imperative/valid/FunctionCaching.scala @@ -7,7 +7,7 @@ object FunctionCaching { def fun(x: BigInt)(implicit funCache: FunCache): BigInt = { funCache.cached.get(x) match { - case None() => + case None() => val res = 2*x + 42 funCache.cached = funCache.cached.updated(x, res) res @@ -29,9 +29,10 @@ object FunctionCaching { def multipleCalls(args: List[BigInt], x: BigInt)(implicit funCache: FunCache): Unit = { require(funCache.cached.get(x).forall(_ == 2*x + 42)) + decreases(args) args match { case Nil() => () - case y::ys => + case y::ys => fun(y) multipleCalls(ys, x) } diff --git a/frontends/benchmarks/imperative/valid/GuessNumber.scala b/frontends/benchmarks/imperative/valid/GuessNumber.scala index 9ddf441422..cdc3089017 100644 --- a/frontends/benchmarks/imperative/valid/GuessNumber.scala +++ b/frontends/benchmarks/imperative/valid/GuessNumber.scala @@ -12,8 +12,8 @@ object GuessNumber { require(min <= max) state.seed += 1 assert(between(min, min, max)) - choose((x: Int) => between(min, x, max)) - } + choose[Int]((x: Int) => between(min, x, max)) + }.ensuring(res => min <= res && res <= max) def main()(implicit state: State): Unit = { val choice = random(0, 10) @@ -21,18 +21,21 @@ object GuessNumber { var guess = random(0, 10) var top = 10 var bot = 0 + var done = false - (while(bot < top) { + (while(!done && bot < top) { + decreases((if (done) 0 else 1, top - bot)) if(isGreater(guess, choice)) { top = guess-1 guess = random(bot, top) } else if(isSmaller(guess, choice)) { bot = guess+1 guess = random(bot, top) + } else { + done = true } - }) invariant(guess >= bot && guess <= top && bot >= 0 && top <= 10 && bot <= top && choice >= bot && choice <= top && - true) - val answer = bot + }) invariant(guess >= bot && guess <= top && bot >= 0 && top <= 10 && bot <= top && choice >= bot && choice <= top && (done ==> (guess == choice))) + val answer = guess assert(answer == choice) } diff --git a/frontends/benchmarks/imperative/valid/LoopInv.scala b/frontends/benchmarks/imperative/valid/LoopInv.scala index f2a83c6e89..1226f651cd 100644 --- a/frontends/benchmarks/imperative/valid/LoopInv.scala +++ b/frontends/benchmarks/imperative/valid/LoopInv.scala @@ -21,6 +21,7 @@ object LoopInv { var state = 0 (while (left) { + decreases(remains) remains match { case Nil() => left = false diff --git a/frontends/benchmarks/imperative/valid/MultiArray7.scala b/frontends/benchmarks/imperative/valid/MultiArray7.scala index a52dd09b08..da00291ec4 100644 --- a/frontends/benchmarks/imperative/valid/MultiArray7.scala +++ b/frontends/benchmarks/imperative/valid/MultiArray7.scala @@ -8,6 +8,7 @@ object MultiArray7 { var i = 0 (while(i < 10) { + decreases(10 - i) val iCpy = i // To not anger EffectsChecker which will complain if we do not bind `i` to an immutable value a(i) = Array.fill(10)(iCpy) i += 1 diff --git a/frontends/benchmarks/imperative/valid/MutableArrayFieldInTrait.scala b/frontends/benchmarks/imperative/valid/MutableArrayFieldInTrait.scala new file mode 100644 index 0000000000..81f855d81a --- /dev/null +++ b/frontends/benchmarks/imperative/valid/MutableArrayFieldInTrait.scala @@ -0,0 +1,35 @@ +import stainless.annotation._ +import stainless.lang._ +import StaticChecks._ + +object MutableArrayFieldInTrait { + case class Buffer(arr: Array[Byte]) + + @mutable + trait MyTrait { + val buf: Buffer + + final def modify: Unit = { + if (buf.arr.length >= 10) buf.arr(0) = 1 + } + } + case class MyClass(buf: Buffer) extends MyTrait + + def modify(buf: Buffer, i: Int): Unit = { + require(0 <= i && i < buf.arr.length) + buf.arr(i) = 42 + } + + def test(mt: MyTrait, i: Int, j: Int, k: Int): Unit = { + require(0 <= i && i < mt.buf.arr.length) + require(0 <= j && j < mt.buf.arr.length) + require(0 <= k && k < mt.buf.arr.length) + require(i != j && j != k && i != k) + val oldi = mt.buf.arr(i) + modify(mt.buf, j) + mt.buf.arr(k) = 24 + assert(mt.buf.arr(i) == oldi) + assert(mt.buf.arr(j) == 42) + assert(mt.buf.arr(k) == 24) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/MutableTuple.scala b/frontends/benchmarks/imperative/valid/MutableTuple.scala index 9a1869ff63..ae9122cf32 100644 --- a/frontends/benchmarks/imperative/valid/MutableTuple.scala +++ b/frontends/benchmarks/imperative/valid/MutableTuple.scala @@ -24,8 +24,8 @@ object MutableTuple { } def t3(): (Foo, Bar) = { - val bar = Bar(1) - val foo = Foo(2) + val bar = Bar(10) + val foo = Foo(20) (foo, bar) } diff --git a/frontends/benchmarks/imperative/valid/NestedFunState1.scala b/frontends/benchmarks/imperative/valid/NestedFunState1.scala index ec71264d74..cb75b076df 100644 --- a/frontends/benchmarks/imperative/valid/NestedFunState1.scala +++ b/frontends/benchmarks/imperative/valid/NestedFunState1.scala @@ -1,5 +1,7 @@ /* Copyright 2009-2021 EPFL, Lausanne */ +import stainless.lang._ + object NestedFunState1 { def sum(n: BigInt): BigInt = { @@ -8,7 +10,8 @@ object NestedFunState1 { var res = BigInt(0) def iter(): Unit = { - require(res >= i && i >= 0) + require(res >= i && i >= 0 && n >= i) + decreases(n - i) if(i < n) { i += 1 res += i diff --git a/frontends/benchmarks/imperative/valid/NestedFunState11.scala b/frontends/benchmarks/imperative/valid/NestedFunState11.scala index 25acf2fda3..3a96750ee1 100644 --- a/frontends/benchmarks/imperative/valid/NestedFunState11.scala +++ b/frontends/benchmarks/imperative/valid/NestedFunState11.scala @@ -9,7 +9,8 @@ object NestedFunState11 { def getI = i def rec(): Unit = { - require(getI >= 0) + require(getI >= 0 && getI <= 10) + decreases(10 - i) if(i < 10) { i += 1 rec() diff --git a/frontends/benchmarks/imperative/valid/NestedFunState3.scala b/frontends/benchmarks/imperative/valid/NestedFunState3.scala index 048fcba5c2..5e1fd32127 100644 --- a/frontends/benchmarks/imperative/valid/NestedFunState3.scala +++ b/frontends/benchmarks/imperative/valid/NestedFunState3.scala @@ -16,6 +16,7 @@ object NestedFunState3 { var i = 0 (while(i < n) { + decreases(n - i) inc() i += 1 }) invariant(i >= 0 && counter == i && i <= n) diff --git a/frontends/benchmarks/imperative/valid/NestedFunState4.scala b/frontends/benchmarks/imperative/valid/NestedFunState4.scala index 0704a60558..7547f7db30 100644 --- a/frontends/benchmarks/imperative/valid/NestedFunState4.scala +++ b/frontends/benchmarks/imperative/valid/NestedFunState4.scala @@ -16,10 +16,11 @@ object NestedFunState4 { def nestedIter(): Unit = { b += 1 - } + } var i = BigInt(0) (while(i < n) { + decreases(n - i) i += 1 nestedIter() }) invariant(i >= 0 && i <= n && b == i) @@ -30,6 +31,7 @@ object NestedFunState4 { var i = BigInt(0) (while(i < n) { + decreases(n - i) i += 1 iter() }) invariant(i >= 0 && i <= n && a >= i) diff --git a/frontends/benchmarks/imperative/valid/NestedFunState5.scala b/frontends/benchmarks/imperative/valid/NestedFunState5.scala index 3231709ba7..3f4ece070c 100644 --- a/frontends/benchmarks/imperative/valid/NestedFunState5.scala +++ b/frontends/benchmarks/imperative/valid/NestedFunState5.scala @@ -22,6 +22,7 @@ object NestedFunState5 { var i = BigInt(0) (while(i < n) { + decreases(n - i) i += 1 iter(a) }) invariant(i >= 0 && i <= n && a >= 2*i) diff --git a/frontends/benchmarks/imperative/valid/NewArrayPrimitive.scala b/frontends/benchmarks/imperative/valid/NewArrayPrimitive.scala new file mode 100644 index 0000000000..dd90387d31 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/NewArrayPrimitive.scala @@ -0,0 +1,42 @@ +object NewArrayPrimitive { + def test0(len: Int): Unit = { + require(len >= 0) + val arr = new Array[Long](len) + if (len > 0) { + assert(arr(len - 1) == (0 : Long)) + } + } + + def test1(len: Int): Unit = { + require(len >= 0) + val arr = new Array[Int](len) + if (len > 0) { + assert(arr(len - 1) == 0) + } + } + + def test2(len: Int): Unit = { + require(len >= 0) + val arr = new Array[Short](len) + if (len > 0) { + assert(arr(len - 1) == (0 : Short)) + } + } + + def test3(len: Int): Unit = { + require(len >= 0) + val arr = new Array[Byte](len) + if (len > 0) { + assert(arr(len - 1) == (0 : Byte)) + } + } + + def test4(len: Int): Unit = { + require(len >= 0) + val arr = new Array[Char](len) + if (len > 0) { + assert(arr(len - 1) == (0 : Char)) + } + } + +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/OpaqueMutation1.scala b/frontends/benchmarks/imperative/valid/OpaqueMutation1.scala new file mode 100644 index 0000000000..577b7c5bf5 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/OpaqueMutation1.scala @@ -0,0 +1,70 @@ +import stainless.lang.{ghost => ghostExpr, _} +import stainless.proof._ +import stainless.annotation._ +import StaticChecks._ + +object OpaqueMutation1 { + case class Box(var cnt: BigInt, var other: BigInt) { + @opaque // Note the opaque + def secretSauce(x: BigInt): BigInt = cnt + x // Nobody thought of it! + + @opaque // Note the opaque here as well + def increment(): Unit = { + @ghost val oldBox = snapshot(this) + cnt += 1 + ghostExpr { + unfold(secretSauce(other)) + unfold(oldBox.secretSauce(other)) + check(oldBox.secretSauce(other) + 1 == this.secretSauce(other)) + } + }.ensuring(_ => old(this).secretSauce(other) + 1 == this.secretSauce(other)) + + // What `increment` looks like after `AntiAliasing` + @opaque @pure + def incrementPure(): (Unit, Box) = { + val newThis = Box(cnt + 1, other) + ghostExpr { + unfold(newThis.secretSauce(other)) + unfold(this.secretSauce(other)) + check(this.secretSauce(other) + 1 == newThis.secretSauce(other)) + } + ((), newThis) + }.ensuring { case (_, newThis) => this.secretSauce(newThis.other) + 1 == newThis.secretSauce(newThis.other) } + } + + def test1(b: Box, cond: Boolean): Unit = { + @ghost val oldBox = snapshot(b) + val b2 = if (cond) b else Box(1, 1) + b.increment() + assert(oldBox.secretSauce(b.other) + 1 == b.secretSauce(b.other)) + assert(cond ==> (oldBox.secretSauce(b2.other) + 1 == b2.secretSauce(b.other))) + } + + // What `test1` looks like after `AntiAliasing` + def test1Pure(b: Box, cond: Boolean): Unit = { + val oldBox = b + val b2 = if (cond) b else Box(1, 1) + val (_, newB) = b.incrementPure() + val newB2 = if (cond) newB else b2 + assert(oldBox.secretSauce(newB.other) + 1 == newB.secretSauce(newB.other)) + assert(cond ==> (oldBox.secretSauce(newB2.other) + 1 == newB2.secretSauce(newB.other))) + } + + def test2(b: Box, cond: Boolean): Unit = { + val b2 = if (cond) b else Box(1, 1) + @ghost val oldB2 = snapshot(b2) + b2.increment() + assert(oldB2.secretSauce(b2.other) + 1 == b2.secretSauce(b2.other)) + assert(cond ==> (oldB2.secretSauce(b.other) + 1 == b.secretSauce(b.other))) + } + + // What `test2` looks like after `AntiAliasing` + def test2Pure(b: Box, cond: Boolean): Unit = { + val b2 = if (cond) b else Box(1, 1) + val oldB2 = b2 + val (_, newB2) = b2.incrementPure() + val newB = if (cond) newB2 else b + assert(oldB2.secretSauce(newB2.other) + 1 == newB2.secretSauce(newB2.other)) + assert(cond ==> (oldB2.secretSauce(newB.other) + 1 == newB.secretSauce(newB.other))) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/OpaqueMutation2.scala b/frontends/benchmarks/imperative/valid/OpaqueMutation2.scala new file mode 100644 index 0000000000..0e9e914f85 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/OpaqueMutation2.scala @@ -0,0 +1,44 @@ +import stainless.lang.{ghost => ghostExpr, _} +import stainless.proof._ +import stainless.annotation._ +import StaticChecks._ + +object OpaqueMutation2 { + case class SmallerBox(var otherCnt: BigInt) + + case class Box(var cnt: BigInt, var smallerBox: SmallerBox) { + @opaque // Note the opaque + def secretSauce(x: BigInt): BigInt = cnt + x // Nobody thought of it! + + @opaque // Note the opaque here as well + def increment(): Unit = { + @ghost val oldBox = snapshot(this) + cnt += 1 + ghostExpr { + unfold(secretSauce(smallerBox.otherCnt)) + unfold(oldBox.secretSauce(smallerBox.otherCnt)) + check(oldBox.secretSauce(smallerBox.otherCnt) + 1 == this.secretSauce(smallerBox.otherCnt)) + } + }.ensuring(_ => old(this).secretSauce(smallerBox.otherCnt) + 1 == this.secretSauce(smallerBox.otherCnt)) + } + + def test1(b: Box, cond: Boolean): Unit = { + @ghost val oldBox = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123)) + val smallerBoxAlias = b.smallerBox + b.increment() + assert(b.smallerBox.otherCnt == smallerBoxAlias.otherCnt) + assert(oldBox.secretSauce(b.smallerBox.otherCnt) + 1 == b.secretSauce(b.smallerBox.otherCnt)) + assert(oldBox.secretSauce(smallerBoxAlias.otherCnt) + 1 == b.secretSauce(smallerBoxAlias.otherCnt)) + assert(cond ==> (oldBox.secretSauce(b2.smallerBox.otherCnt) + 1 == b2.secretSauce(b.smallerBox.otherCnt))) + assert(cond ==> (oldBox.secretSauce(smallerBoxAlias.otherCnt) + 1 == b2.secretSauce(smallerBoxAlias.otherCnt))) + } + + def test2(b: Box, cond: Boolean): Unit = { + val b2 = if (cond) b else Box(1, SmallerBox(123)) + @ghost val oldB2 = snapshot(b2) + b2.increment() + assert(oldB2.secretSauce(b2.smallerBox.otherCnt) + 1 == b2.secretSauce(b2.smallerBox.otherCnt)) + assert(cond ==> (oldB2.secretSauce(b.smallerBox.otherCnt) + 1 == b.secretSauce(b.smallerBox.otherCnt))) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/PassByRef.scala b/frontends/benchmarks/imperative/valid/PassByRef.scala new file mode 100644 index 0000000000..a2a9ce71e5 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/PassByRef.scala @@ -0,0 +1,12 @@ +object PassByRef { + case class Box(var value: BigInt) + case class Container(b: Box) + + def f2(b: Container): Unit = b.b.value = 3 + + def g2(b: Container) = { + val b0 = b + f2(b) + assert(b == b0) // Yes, because `b` and `b0` are aliases + } +} diff --git a/frontends/benchmarks/imperative/valid/RefnChecksWithReturn.scala b/frontends/benchmarks/imperative/valid/RefnChecksWithReturn.scala new file mode 100644 index 0000000000..e8776d24f6 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/RefnChecksWithReturn.scala @@ -0,0 +1,39 @@ +import stainless.lang._ + +object RefnChecksWithReturn { + + def fun0(x: BigInt, arr: Array[BigInt]): Option[BigInt] = { + require(arr.length >= 10) + if (x <= 0){ + return Some(x) + } + arr(0) = 0 + Some(x) + }.ensuring { + case Some(xx) => (xx == x) && ((x > 0) ==> (arr(0) == 0)) + } + + def fun1(x: BigInt): (BigInt, Option[BigInt]) = { + (x, (return (x + 1, Some(x))) : (BigInt, Option[BigInt]))._2 + }.ensuring { + case (xx, Some(xx2)) => xx == x + 1 && xx2 == x + } + + def fun2(x: BigInt): (BigInt, Option[BigInt]) = { + (x, if (x == 0) return (x + 1, Some(x + 1)) else (x + 2, Some(x + 2)))._2 + }.ensuring { + case (xx, Some(xx2)) => + val delta = if (x == 0) BigInt(1) else BigInt(2) + xx == x + delta && xx2 == x + delta + } + + def fun3(x: BigInt): (BigInt, Option[BigInt]) = { + smth(0, if (x <= 0) return (0, Some(x)) else Some(0)) + (x, Some(x)) + }.ensuring { + case (xx, Some(xx2)) => + if (x <= 0) xx == 0 && xx2 == x + else xx == x && xx2 == x + } + def smth(x: BigInt, tpl: Option[BigInt]): Unit = () +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/TargetMutation10.scala b/frontends/benchmarks/imperative/valid/TargetMutation10.scala new file mode 100644 index 0000000000..aaaa5a3667 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/TargetMutation10.scala @@ -0,0 +1,193 @@ +import stainless.lang.{ghost => ghostExpr, _} +import stainless.proof._ +import stainless.annotation._ +import StaticChecks._ + +object TargetMutation10 { + case class EvenSmallerBox(var lastCnt: BigInt) + + case class SmallerBox(var otherCnt: BigInt, var evenSmallerBox: EvenSmallerBox) { + def increment(): Unit = { + otherCnt += 1 + evenSmallerBox.lastCnt += 1 + } + } + + case class Box(var cnt: BigInt, var smallerBox: SmallerBox) { + def increment(): Unit = { + cnt += 1 + smallerBox.otherCnt += 1 + smallerBox.evenSmallerBox.lastCnt += 1 + } + def replaceSmallerBox(): Unit = { + cnt += 1 + smallerBox = SmallerBox(-1, EvenSmallerBox(-2)) + } + } + + def test1(b: Box, cond: Boolean): Unit = { + @ghost val oldBox = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + val smallerBoxAlias = b.smallerBox + val smallerBoxAlias2 = if (cond) b.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + b.increment() + assert(cond ==> (b2.cnt == b.cnt)) + assert(!cond ==> (b2.cnt == 1)) + assert(oldBox.cnt + 1 == b.cnt) + assert(b.smallerBox.otherCnt == smallerBoxAlias.otherCnt) + assert(b.smallerBox.evenSmallerBox.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(evenSmallerBoxAliasBis.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(cond ==> (b2.smallerBox.otherCnt == smallerBoxAlias.otherCnt)) + assert(cond ==> (evenSmallerBoxAlias2.lastCnt == b.smallerBox.evenSmallerBox.lastCnt)) + assert(evenSmallerBoxAlias2.lastCnt == evenSmallerBoxAlias2Bis.lastCnt) + assert(!cond ==> (b2.smallerBox.otherCnt == 123)) + assert(!cond ==> (evenSmallerBoxAlias2.lastCnt == 456)) + assert(cond ==> (smallerBoxAlias2.otherCnt == b.smallerBox.otherCnt)) + assert(!cond ==> (smallerBoxAlias2.otherCnt == 456)) + } + + def test2(b: Box, cond: Boolean): Unit = { + @ghost val oldb = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + @ghost val oldb2 = snapshot(b2) + val smallerBoxAlias = b.smallerBox + val smallerBoxAlias2 = if (cond) b.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + b2.increment() + assert(cond ==> (b2.cnt == b.cnt)) + assert(!cond ==> (b2.cnt == 2)) + assert(!cond ==> (oldb.cnt == b.cnt)) + assert(b.smallerBox.otherCnt == smallerBoxAlias.otherCnt) + assert(b.smallerBox.evenSmallerBox.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(evenSmallerBoxAliasBis.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(cond ==> (b2.smallerBox.otherCnt == smallerBoxAlias.otherCnt)) + assert(cond ==> (evenSmallerBoxAlias2.lastCnt == b.smallerBox.evenSmallerBox.lastCnt)) + assert(evenSmallerBoxAlias2.lastCnt == evenSmallerBoxAlias2Bis.lastCnt) + assert(!cond ==> (b.smallerBox.otherCnt == oldb.smallerBox.otherCnt)) + assert(cond ==> (smallerBoxAlias2.otherCnt == b.smallerBox.otherCnt)) + } + + def test3(b: Box, cond: Boolean): Unit = { + @ghost val oldBox = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + val smallerBoxAlias = b.smallerBox + val smallerBoxAlias2 = if (cond) b.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + b.smallerBox.increment() + assert(b.smallerBox.otherCnt == smallerBoxAlias.otherCnt) + assert(b.smallerBox.evenSmallerBox.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(evenSmallerBoxAliasBis.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(cond ==> (b2.smallerBox.otherCnt == smallerBoxAlias.otherCnt)) + assert(cond ==> (evenSmallerBoxAlias2.lastCnt == b.smallerBox.evenSmallerBox.lastCnt)) + assert(evenSmallerBoxAlias2.lastCnt == evenSmallerBoxAlias2Bis.lastCnt) + assert(!cond ==> (b2.smallerBox.otherCnt == 123)) + assert(!cond ==> (evenSmallerBoxAlias2.lastCnt == 456)) + assert(cond ==> (smallerBoxAlias2.otherCnt == b.smallerBox.otherCnt)) + assert(!cond ==> (smallerBoxAlias2.otherCnt == 456)) + } + + def test4(b: Box, cond: Boolean): Unit = { + @ghost val oldb = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + @ghost val oldb2 = snapshot(b2) + val smallerBoxAlias = b.smallerBox + val smallerBoxAlias2 = if (cond) b.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + b2.smallerBox.increment() + assert(b.smallerBox.otherCnt == smallerBoxAlias.otherCnt) + assert(b.smallerBox.evenSmallerBox.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(evenSmallerBoxAliasBis.lastCnt == evenSmallerBoxAlias.lastCnt) + assert(cond ==> (b2.smallerBox.otherCnt == smallerBoxAlias.otherCnt)) + assert(cond ==> (evenSmallerBoxAlias2.lastCnt == b.smallerBox.evenSmallerBox.lastCnt)) + assert(evenSmallerBoxAlias2.lastCnt == evenSmallerBoxAlias2Bis.lastCnt) + assert(!cond ==> (b.smallerBox.otherCnt == oldb.smallerBox.otherCnt)) + assert(cond ==> (smallerBoxAlias2.otherCnt == b.smallerBox.otherCnt)) + } + + def test5(b: Box, cond: Boolean): Unit = { + @ghost val oldBox = snapshot(b) + val oldOtherCnt = b.smallerBox.otherCnt + val oldLastCnt = b.smallerBox.evenSmallerBox.lastCnt + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + val smallerBoxAlias = b.smallerBox + val smallerBoxAlias2 = if (cond) b.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + + b.replaceSmallerBox() + + assert(cond ==> (b2.cnt == b.cnt)) + assert(!cond ==> (b2.cnt == 1)) + assert(oldBox.cnt + 1 == b.cnt) + + assert(b.smallerBox.otherCnt == -1) + assert(b.smallerBox.evenSmallerBox.lastCnt == -2) + + assert(oldBox.smallerBox.otherCnt == oldOtherCnt) + assert(oldBox.smallerBox.evenSmallerBox.lastCnt == oldLastCnt) + + assert(smallerBoxAlias.otherCnt == oldOtherCnt) + assert(evenSmallerBoxAlias.lastCnt == oldLastCnt) + + assert(cond ==> (smallerBoxAlias.otherCnt == oldOtherCnt)) + assert(cond ==> (evenSmallerBoxAlias.lastCnt == oldLastCnt)) + assert(cond ==> (b2.smallerBox.otherCnt == -1)) + assert(cond ==> (b2.smallerBox.evenSmallerBox.lastCnt == -2)) + + assert(!cond ==> (b2.smallerBox.otherCnt == 123)) + assert(!cond ==> (evenSmallerBoxAlias2.lastCnt == 456)) + assert(!cond ==> (smallerBoxAlias2.otherCnt == 456)) + } + + def test6(b: Box, cond: Boolean): Unit = { + @ghost val oldb = snapshot(b) + val b2 = if (cond) b else Box(1, SmallerBox(123, EvenSmallerBox(10))) + val oldOtherCnt = b2.smallerBox.otherCnt + val oldLastCnt = b2.smallerBox.evenSmallerBox.lastCnt + @ghost val oldb2 = snapshot(b2) + val smallerBoxAlias = b2.smallerBox + val smallerBoxAlias2 = if (cond) b2.smallerBox else SmallerBox(456, EvenSmallerBox(20)) + val evenSmallerBoxAlias = b2.smallerBox.evenSmallerBox + val evenSmallerBoxAliasBis = smallerBoxAlias.evenSmallerBox + val evenSmallerBoxAlias2 = if (cond) b2.smallerBox.evenSmallerBox else EvenSmallerBox(456) + val evenSmallerBoxAlias2Bis = if (cond) smallerBoxAlias.evenSmallerBox else EvenSmallerBox(456) + + b2.replaceSmallerBox() + + assert(cond ==> (b2.cnt == b.cnt)) + assert(oldb2.cnt + 1 == b2.cnt) + + assert(b2.smallerBox.otherCnt == -1) + assert(b2.smallerBox.evenSmallerBox.lastCnt == -2) + assert(cond ==> (b.smallerBox.otherCnt == -1)) + assert(cond ==> (b.smallerBox.evenSmallerBox.lastCnt == -2)) + + assert(oldb2.smallerBox.otherCnt == oldOtherCnt) + assert(oldb2.smallerBox.evenSmallerBox.lastCnt == oldLastCnt) + + assert(smallerBoxAlias.otherCnt == oldOtherCnt) + assert(evenSmallerBoxAlias.lastCnt == oldLastCnt) + + assert(cond ==> (smallerBoxAlias.otherCnt == oldOtherCnt)) + assert(cond ==> (evenSmallerBoxAlias.lastCnt == oldLastCnt)) + + assert(!cond ==> (evenSmallerBoxAlias2.lastCnt == 456)) + assert(!cond ==> (smallerBoxAlias2.otherCnt == 456)) + } + +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/TargetMutation3.scala b/frontends/benchmarks/imperative/valid/TargetMutation3.scala index 7e165ad174..eb9acb7429 100644 --- a/frontends/benchmarks/imperative/valid/TargetMutation3.scala +++ b/frontends/benchmarks/imperative/valid/TargetMutation3.scala @@ -6,7 +6,7 @@ object TargetMutation3 { case class Box(var value: Int) def mutate(b: Box, v: Int): Unit = { - b.value = v; + b.value = v } def badManners(arr: Array[Box], otherArr: Array[Box], b1: Box, b2: Box, b3: Box, cond1: Boolean, cond2: Boolean, i: Int): Unit = { diff --git a/frontends/benchmarks/imperative/valid/TargetMutation4.scala b/frontends/benchmarks/imperative/valid/TargetMutation4.scala index 14b3b6023e..fc7c27f201 100644 --- a/frontends/benchmarks/imperative/valid/TargetMutation4.scala +++ b/frontends/benchmarks/imperative/valid/TargetMutation4.scala @@ -7,7 +7,7 @@ object TargetMutation4 { case class Box(var value: Int) def mutate(b: Box, v: Int): Unit = { - b.value = v; + b.value = v } def h1(x: Box, cond: Boolean): Unit = { @@ -77,4 +77,23 @@ object TargetMutation4 { assert(y.value == 111) assert(z.value == 111) } + + def h4(x: Box, z: Box, cond: Boolean): Unit = { + val oldX = x.value + val y = if (cond) x else z + y.value = 456 + assert(y.value == 456) + assert(cond ==> (x.value == 456)) + assert(!cond ==> (x.value == oldX)) + } + + def h4Mutate(x: Box, z: Box, cond: Boolean): Unit = { + val oldX = x.value + val y = if (cond) x else z + mutate(y, 456) + assert(y.value == 456) + assert(cond ==> (x.value == 456)) + assert(!cond ==> (x.value == oldX)) + } + } diff --git a/frontends/benchmarks/imperative/valid/TargetMutation5.scala b/frontends/benchmarks/imperative/valid/TargetMutation5.scala index 303c9f5742..f7e3b6a293 100644 --- a/frontends/benchmarks/imperative/valid/TargetMutation5.scala +++ b/frontends/benchmarks/imperative/valid/TargetMutation5.scala @@ -6,12 +6,12 @@ object TargetMutation5 { case class Box(var value1: Int, var value2: Int, var value3: Int) def mutate(b: Box, v: Int): Unit = { - b.value1 = v; + b.value1 = v } def mmutate(b: Box, v1: Int, v2: Int): Unit = { - b.value1 = v1; - b.value2 = v2; + b.value1 = v1 + b.value2 = v2 } def t1(arr: Array[Box], i: Int): Unit = { diff --git a/frontends/benchmarks/imperative/valid/TargetMutation6.scala b/frontends/benchmarks/imperative/valid/TargetMutation6.scala index 1d856ac079..5b3353bd23 100644 --- a/frontends/benchmarks/imperative/valid/TargetMutation6.scala +++ b/frontends/benchmarks/imperative/valid/TargetMutation6.scala @@ -6,7 +6,11 @@ object TargetMutation6 { case class RefRef(var lhs: Ref, var rhs: Ref) def replaceLhs(rr: RefRef, v: Int): Unit = { - rr.lhs = Ref(v); + rr.lhs = Ref(v) + } + + def modifyLhs(rr: RefRef, v: Int): Unit = { + rr.lhs.x = v } def t1(arr1: Array[Ref], arr2: Array[Ref], i: Int, j: Int, k: Int, cond: Boolean, gra: Ref): Unit = { @@ -27,6 +31,7 @@ object TargetMutation6 { val oldLhs = lhs.x refref.lhs = Ref(123) assert(lhs.x == oldLhs) + assert(refref.lhs.x == 123) } def t3(refref: RefRef): Unit = { @@ -34,5 +39,17 @@ object TargetMutation6 { val oldLhs = lhs.x replaceLhs(refref, 123) assert(lhs.x == oldLhs) + assert(refref.lhs.x == 123) + } + + def t4(refref: RefRef): Unit = { + val lhs = refref.lhs + val oldLhs = lhs.x + modifyLhs(refref, 123) + assert(lhs.x == 123) + assert(refref.lhs.x == 123) + refref.lhs.x = 456 + assert(lhs.x == 456) + assert(refref.lhs.x == 456) } } diff --git a/frontends/benchmarks/imperative/valid/TargetMutation7.scala b/frontends/benchmarks/imperative/valid/TargetMutation7.scala new file mode 100644 index 0000000000..7933eacd2d --- /dev/null +++ b/frontends/benchmarks/imperative/valid/TargetMutation7.scala @@ -0,0 +1,70 @@ +object TargetMutation7 { + + case class A(var i: BigInt) + case class B(var a1: A, var a2: A) + case class C(var a: A, var b: B) + + def createA(i: BigInt, counter: A): A = { + counter.i += 1 + A(i) + } + + def test1(n: BigInt, counter: A): C = { + val origCount = counter.i + val a0 = A(1234) + val b = { + if (n > 0) { + val b = B(A(0), A(1)) + b.a1 = createA(1, counter) + b + } else B(A(123), A(456)) + } + val a = createA(2, counter) + assert(n <= 0 || counter.i == origCount + 2) + assert(n > 0 || counter.i == origCount + 1) + C(a, b) + } + + def test2(n: BigInt, counter: A): Unit = { + val origCount = counter.i + val alias = counter + val b = { + if (n > 0) B(A(0), createA(n, alias)) + else B(A(0), A(0)) + } + b.a2.i += 1 + assert(n <= 0 || counter.i == origCount + 1) + assert(n > 0 || counter.i == origCount) + } + + + def test3(n: BigInt, counter: A): Unit = { + val origCount = counter.i + val alias = counter + val b = { + if (n > 0) { + createA(n, alias) + B(A(0), alias) + } + else B(A(0), alias) + } + b.a2.i += 1 + assert(n <= 0 || b.a2.i == origCount + 2) + assert(n > 0 || b.a2.i == origCount + 1) + } + + def test4(n: BigInt, counter: A): Unit = { + val origCount = counter.i + val alias = counter + val b = { + if (n > 0) { + createA(n, alias) + B(A(0), counter) + } + else B(A(0), alias) + } + b.a2.i += 1 + assert(n <= 0 || b.a2.i == origCount + 2) + assert(n > 0 || b.a2.i == origCount + 1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/TargetMutation8.scala b/frontends/benchmarks/imperative/valid/TargetMutation8.scala new file mode 100644 index 0000000000..00e859cae0 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/TargetMutation8.scala @@ -0,0 +1,32 @@ +object TargetMutation8 { + + case class A(var i: BigInt) + case class B(arr: Array[A]) + case class C(a: A, b: B) + + def createA(i: BigInt, counter: A): A = { + counter.i += 1 + A(i) + } + + def test(n: BigInt, counter: A): C = { + val origCount = counter.i + // This triggers AntiAliasing 2nd case for Let binding of mutable types + // *without* triggering EffectsChecker#check#traverser#traverse#Let + // because: + // -`b` is bound to a fresh expression (passes EffectsChecker#check#traverser#traverse#Let check) + // -the transformation of `b` produces an expression whose target cannot be computed + // -there are no sharing between the terminal variables of `b` and the rest of the body + val b = { + if (n > 0) { + val b = B(Array.fill(5)(A(0))) + b.arr(0) = createA(1, counter) + b + } else B(Array.fill(5)(A(0))) + } + val a = createA(2, counter) + assert(n <= 0 || counter.i == origCount + 2) + assert(n > 0 || counter.i == origCount + 1) + C(a, b) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/TargetMutation9.scala b/frontends/benchmarks/imperative/valid/TargetMutation9.scala new file mode 100644 index 0000000000..8f5565cde6 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/TargetMutation9.scala @@ -0,0 +1,58 @@ +object TargetMutation9 { + + case class A(var x: BigInt) + case class C(a: A) + case class D(c: C) + + def test1(n: BigInt): Unit = { + val a = A(n) + val c = C(a) + c.a.x = 3 + assert(c.a.x == 3) + } + + def test2(n: BigInt): Unit = { + val a = A(n) + if (n == 0) { + val c = C(a) + c.a.x = 3 + // `a` will be updated here by AntiAliasing (`copyEffects`) + } + assert(n != 0 || a.x == 3) + } + + def test3(n: BigInt): Unit = { + val a = A(n) + if (n == 0) { + val c = C(a) + val c2 = c + c2.a.x = 3 + // `a` will be updated here by AntiAliasing (`copyEffects`) + } + assert(n != 0 || a.x == 3) + } + + def test4(n: BigInt): Unit = { + val a = A(n) + if (n == 0) { + val c = C(a) + val d = D(c) + d.c.a.x = 3 + // `a` will be updated here by AntiAliasing (`copyEffects`) + } + assert(n != 0 || a.x == 3) + } + + def test5(n: BigInt): Unit = { + val a = A(n) + if (n == 0) { + val c = C(a) + val d = D(c) + d.c.a.x = 3 + // `a` will be updated here by AntiAliasing (`copyEffects`) + } + a.x += 1 + assert(n != 0 || a.x == 4) + assert(n == 0 || a.x == n + 1) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/Tutorial.scala b/frontends/benchmarks/imperative/valid/Tutorial.scala index f73449f374..e4b91c3a53 100644 --- a/frontends/benchmarks/imperative/valid/Tutorial.scala +++ b/frontends/benchmarks/imperative/valid/Tutorial.scala @@ -25,6 +25,7 @@ object Lists { var res: BigInt = 0 var lst: List[A] = l (while(!isEmpty(lst)) { + decreases(lst) lst = tail(lst) res += 1 }) invariant(res + sizeSpec(lst) == sizeSpec(l)) @@ -111,6 +112,7 @@ object Banking { var i = 0 var success = false (while(i < times && !success) { + decreases(times - i) success = transaction.execute() i += 1 }) invariant(i >= 0 && i <= times && success == transaction.executed) @@ -186,6 +188,20 @@ object Banking { } object SFuns { + def parallel[S1,S2,B1,B2](t1: SFun[Unit,S1,B1], + t2: SFun[Unit,S2,B2]): (B1, B2) = + (t1(()), t2(())) + + def testOrder(t1: SFun[Unit,Int,Int], t2: SFun[Unit,Int,Int], init: Int): Unit = { + t1.state.value = init + t2.state.value = init + val (v1, v2) = parallel(t1, t2) + t1.state.value = init + t2.state.value = init + val (u2, u1) = parallel(t2, t1) + assert(v1 == u1) + assert(v2 == u2) + } case class State[A](var value: A) diff --git a/frontends/benchmarks/imperative/valid/While1.scala b/frontends/benchmarks/imperative/valid/While1.scala index 6727b65ea0..1301fae721 100644 --- a/frontends/benchmarks/imperative/valid/While1.scala +++ b/frontends/benchmarks/imperative/valid/While1.scala @@ -1,15 +1,17 @@ /* Copyright 2009-2021 EPFL, Lausanne */ import stainless.annotation._ +import stainless.lang._ object While1 { def foo(): Int = { var a = 0 var i = 0 - while(i < 10) { + (while(i < 10) { + decreases(10 - i) a = a + 1 i = i + 1 - } + }).invariant(i >= 0) a } ensuring(_ == 10) diff --git a/frontends/benchmarks/imperative/valid/While2.scala b/frontends/benchmarks/imperative/valid/While2.scala index 9dc70befcc..fcc8282a2c 100644 --- a/frontends/benchmarks/imperative/valid/While2.scala +++ b/frontends/benchmarks/imperative/valid/While2.scala @@ -1,14 +1,17 @@ /* Copyright 2009-2021 EPFL, Lausanne */ +import stainless.lang._ + object While2 { def foo(): Int = { var a = 0 var i = 0 - while(i < 10) { + (while(i < 10) { + decreases(10 - i) a = a + i i = i + 1 - } + }).invariant(i >= 0) a } ensuring(_ == 45) diff --git a/frontends/benchmarks/imperative/valid/While3.scala b/frontends/benchmarks/imperative/valid/While3.scala index 465b393f93..153b1b52ec 100644 --- a/frontends/benchmarks/imperative/valid/While3.scala +++ b/frontends/benchmarks/imperative/valid/While3.scala @@ -1,14 +1,17 @@ /* Copyright 2009-2021 EPFL, Lausanne */ +import stainless.lang._ + object While3 { def foo(): Int = { var a = 0 var i = 0 - while({i = i+2; i <= 10}) { + (while({i = i+2; i <= 10}) { + decreases(10 - i) a = a + i i = i - 1 - } + }).invariant(i >= 0) a } ensuring(_ == 54) diff --git a/frontends/benchmarks/imperative/valid/WhileAsFun1.scala b/frontends/benchmarks/imperative/valid/WhileAsFun1.scala index 57af6a9a54..a334896286 100644 --- a/frontends/benchmarks/imperative/valid/WhileAsFun1.scala +++ b/frontends/benchmarks/imperative/valid/WhileAsFun1.scala @@ -11,6 +11,7 @@ object WhileAsFun1 { var i = 0 def rec(): Unit = { require(i >= 0 && i <= n) + decreases(n - i) if(i < n) { i += 1 rec() diff --git a/frontends/benchmarks/imperative/valid/WhileAsFun2.scala b/frontends/benchmarks/imperative/valid/WhileAsFun2.scala index 1ea964e497..3ea5eaca6e 100644 --- a/frontends/benchmarks/imperative/valid/WhileAsFun2.scala +++ b/frontends/benchmarks/imperative/valid/WhileAsFun2.scala @@ -16,6 +16,7 @@ object WhileAsFun2 { var i = 0 def rec(): Unit = { + decreases(n - i) require(i >= 0 && counter == i && i <= n) if(i < n) { inc() diff --git a/frontends/benchmarks/imperative/valid/WhileConditionSubexpression.scala b/frontends/benchmarks/imperative/valid/WhileConditionSubexpression.scala index e4ddc3c76f..e767634222 100644 --- a/frontends/benchmarks/imperative/valid/WhileConditionSubexpression.scala +++ b/frontends/benchmarks/imperative/valid/WhileConditionSubexpression.scala @@ -11,11 +11,12 @@ object WhileConditionSubexpression { def test(x: Int): Boolean = { var b = false var i = 0 - while(!b && i < 10) { + (while(!b && i < 10) { + decreases(10 - i) if(i == x) b = true i += 1 - } + }).invariant(i >= 0) b } ensuring(res => res || (x != 0 && x != 1 && x != 2)) diff --git a/frontends/benchmarks/imperative/valid/WhileInvGhost.scala b/frontends/benchmarks/imperative/valid/WhileInvGhost.scala new file mode 100644 index 0000000000..9d091176c1 --- /dev/null +++ b/frontends/benchmarks/imperative/valid/WhileInvGhost.scala @@ -0,0 +1,17 @@ +import stainless.lang.{WhileDecorations => _, _} +import stainless.annotation._ +import StaticChecks._ + +object WhileInvGhost { + @ghost + def inv(x: BigInt): Boolean = x >= 0 + + def appendBits(x: BigInt): Unit = { + require(x >= 0) + var i: BigInt = 0 + (while (i < x) { + decreases(x - i) + i += 1 + }).invariant(inv(i)) + } +} \ No newline at end of file diff --git a/frontends/benchmarks/imperative/valid/i1306b.scala b/frontends/benchmarks/imperative/valid/i1306b.scala index 8a6ba91b52..d5cc8ddb69 100644 --- a/frontends/benchmarks/imperative/valid/i1306b.scala +++ b/frontends/benchmarks/imperative/valid/i1306b.scala @@ -3,7 +3,7 @@ import stainless.lang._ import stainless.annotation._ object i1306b { - def root(a: BigInt): BigInt = { + def root1(a: BigInt): BigInt = { require(a >= 0) var i: BigInt = 0 def nextSquare = ((i + 1) * (i + 1)) @@ -14,9 +14,29 @@ object i1306b { val z = accessI(a, nextSquare) assert(z == i + a + (i + 1) * (i + 1)) val w = accessI(a, i) + modifyI(a, -a) assert(w == i + a + i) i = i + 1 }) invariant { i >= 0 && ((i * i) <= a) } i } ensuring { root => (root * root) <= a && a < ((root + 1) * (root + 1)) } + + // Variant that increments `i` with `modifyI` + def root2(a: BigInt): BigInt = { + require(a >= 0) + var i: BigInt = 0 + def nextSquare = ((i + 1) * (i + 1)) + def accessI(x: BigInt, y: BigInt) = i + x + y + def modifyI(x: BigInt, y: BigInt) = { i += x + y } + (while (nextSquare <= a) { + decreases(a - nextSquare) + val z = accessI(a, nextSquare) + assert(z == i + a + (i + 1) * (i + 1)) + val w = accessI(a, i) + modifyI(a, -a) + assert(w == i + a + i) + modifyI(0, 1) + }) invariant { i >= 0 && ((i * i) <= a) } + i + } ensuring { root => (root * root) <= a && a < ((root + 1) * (root + 1)) } } diff --git a/frontends/benchmarks/imperative/valid/i1306c.scala b/frontends/benchmarks/imperative/valid/i1306c.scala new file mode 100644 index 0000000000..e838a9212a --- /dev/null +++ b/frontends/benchmarks/imperative/valid/i1306c.scala @@ -0,0 +1,43 @@ +import stainless._ +import stainless.lang._ +import stainless.annotation._ + +object i1306c { + // Like coal power plants, lot of energy lost for little gain... + def laboriousIdentity1(a: BigInt): BigInt = { + require(a >= 0) + var i: BigInt = 0 + def nextIncrement = i + 1 + def accessI(x: BigInt, y: BigInt) = i + x + y + def modifyI(x: BigInt, y: BigInt) = { i += x + y } + (while (nextIncrement <= a) { + decreases(a - nextIncrement) + val z = accessI(a, nextIncrement) + assert(z == i + a + i + 1) + val w = accessI(a, i) + modifyI(a, -a) + assert(w == i + a + i) + i = i + 1 + }) invariant { i >= 0 && i <= a } + i + } ensuring { _ == a } + + // Variant that increments `i` with `modifyI` + def laboriousIdentity2(a: BigInt): BigInt = { + require(a >= 0) + var i: BigInt = 0 + def nextIncrement = i + 1 + def accessI(x: BigInt, y: BigInt) = i + x + y + def modifyI(x: BigInt, y: BigInt) = { i += x + y } + (while (nextIncrement <= a) { + decreases(a - nextIncrement) + val z = accessI(a, nextIncrement) + assert(z == i + a + i + 1) + val w = accessI(a, i) + modifyI(a, -a) + assert(w == i + a + i) + modifyI(0, 1) + }) invariant { i >= 0 && i <= a } + i + } ensuring { _ == a } +} diff --git a/frontends/benchmarks/verification/false-valid/i1506.scala b/frontends/benchmarks/verification/false-valid/i1506.scala new file mode 100644 index 0000000000..41b9336f36 --- /dev/null +++ b/frontends/benchmarks/verification/false-valid/i1506.scala @@ -0,0 +1,21 @@ +// Unsoundness due to an incorrect AntiAliasing transformation +// See https://github.com/epfl-lara/stainless/issues/1506 +object i1506 { + case class Ref(var x: Int) + case class RefRef(var lhs: Ref, var rhs: Ref) + + def replaceLhs(rr: RefRef, v: Int): Unit = { + rr.lhs = Ref(v) + } + + def t3(refref: RefRef): Unit = { + val lhs = refref.lhs + val oldLhs = lhs.x + replaceLhs(refref, 123) + assert(lhs.x == oldLhs) + assert(refref.lhs.x == 123) + refref.lhs.x = 456 + assert(lhs.x == 456) // Incorrect because `lhs` and `refref.lhs` become unrelated after the call to `replaceLhs` + assert(refref.lhs.x == 456) + } +} diff --git a/frontends/benchmarks/verification/invalid/InvalidInvariantCopy.scala b/frontends/benchmarks/verification/invalid/InvalidInvariantCopy.scala new file mode 100644 index 0000000000..cb7f3986b5 --- /dev/null +++ b/frontends/benchmarks/verification/invalid/InvalidInvariantCopy.scala @@ -0,0 +1,7 @@ +object InvalidInvariantCopy { + case class MyClass(x: BigInt) { + require(x >= 0) + } + + def copyClass(mc: MyClass, x: BigInt): MyClass = mc.copy(x + x) +} \ No newline at end of file diff --git a/frontends/benchmarks/verification/invalid/TupleCleanup.scala b/frontends/benchmarks/verification/invalid/TupleCleanup.scala new file mode 100644 index 0000000000..448b5bf81a --- /dev/null +++ b/frontends/benchmarks/verification/invalid/TupleCleanup.scala @@ -0,0 +1,21 @@ +import stainless.lang._ +import stainless.collection._ + +object TupleCleanup { + def test1(x: BigInt, y: BigInt): (BigInt, BigInt) = { + val (xx, yy) = (x, y) + val boom = Nil[BigInt]().head + (xx, yy) + } + + def test2(xs: List[BigInt]): (List[BigInt], List[BigInt]) = { + decreases(xs) + xs match { + case Nil() => (Nil(), Nil()) + case Cons(i, t) => + val (s2, g2) = test2(t) + val boom = Nil[BigInt]().head + (s2, g2) + } + } +} \ No newline at end of file diff --git a/frontends/benchmarks/verification/valid/ArrayOfTypeAlias.scala b/frontends/benchmarks/verification/valid/ArrayOfTypeAlias.scala new file mode 100644 index 0000000000..c396ebf04f --- /dev/null +++ b/frontends/benchmarks/verification/valid/ArrayOfTypeAlias.scala @@ -0,0 +1,5 @@ +object ArrayOfTypeAlias { + type AliasedLong = Long + + case class SomeClass(arr: Array[AliasedLong]) +} \ No newline at end of file diff --git a/frontends/benchmarks/verification/valid/GhostClass.scala b/frontends/benchmarks/verification/valid/GhostClass.scala new file mode 100644 index 0000000000..f3e45ffb12 --- /dev/null +++ b/frontends/benchmarks/verification/valid/GhostClass.scala @@ -0,0 +1,27 @@ +import stainless.annotation._ +import stainless.lang.{ghost => ghostExpr, _} + +object GhostClass { + + @ghost + def ghostFn(x: BigInt): BigInt = x + + @ghost + case class MyGhostClass(i: BigInt) { + // ghostMethod is treated as @ghost even though not explicitly + // annotated as so due to the class being ghost + def ghostMethod(x: BigInt): BigInt = ghostFn(x + i).ensuring(_ == x + i) + } + + @ghost + def createGhostClass(i: BigInt): MyGhostClass = MyGhostClass(i).ensuring(_.i == i) + + def useInGhost(i: BigInt): BigInt = { + @ghost val mgc = MyGhostClass(i) + ghostExpr { + val ii = mgc.ghostMethod(2 * i) + assert(ii == mgc.i + 2 * i) + } + 3 * i + } +} \ No newline at end of file diff --git a/frontends/benchmarks/verification/valid/MonadicTry1.scala b/frontends/benchmarks/verification/valid/MonadicTry1.scala new file mode 100644 index 0000000000..513523c671 --- /dev/null +++ b/frontends/benchmarks/verification/valid/MonadicTry1.scala @@ -0,0 +1,19 @@ +/* Copyright 2009-2024 EPFL, Lausanne */ + +import stainless.lang._ +import stainless.proof._ + +object MonadicTry1 { + + def success(): Try[Unit] = Success[Unit](()) + def failure(): Try[Unit] = Failure[Unit](Exception("failure")) + + def checkVal(b: Boolean): Try[Unit] = { + if (b) Success[Unit](()) + else Failure[Unit](Exception("checkVal failed")) + } ensuring(res => res match { + case Success(_) => b + case Failure(_) => !b + }) + +} diff --git a/frontends/benchmarks/verification/valid/MonadicTry2.scala b/frontends/benchmarks/verification/valid/MonadicTry2.scala new file mode 100644 index 0000000000..52c0b7e103 --- /dev/null +++ b/frontends/benchmarks/verification/valid/MonadicTry2.scala @@ -0,0 +1,39 @@ +/* Copyright 2009-2024 EPFL, Lausanne */ + +import stainless.lang._ +import stainless.proof._ + +object MonadicTry2 { + + def checkPositive(i: BigInt): Try[BigInt]= { + if (i > 0) Success[BigInt](i) + else Failure[BigInt](Exception("i is not positive")) + } ensuring(res => res match { + case Success(ii) => i > 0 && i == ii + case Failure(_) => i <= 0 + }) + + def checkEven(i: BigInt): Try[BigInt] = { + if (i % 2 == 0) Success[BigInt](i) + else Failure[BigInt](Exception("i is not even")) + } ensuring(res => res match { + case Success(ii) => i % 2 == 0 && i == ii + case Failure(_) => i % 2 != 0 + }) + + def checkEvenPositive(i: BigInt): Try[BigInt] = { + checkPositive(i).flatMap(ii => checkEven(ii)) + } ensuring(res => res match { + case Success(ii) => i > 0 && i % 2 == 0 && i == ii + case Failure(_) => i <= 0 || i % 2 != 0 + }) + + def evenPlusOne(i: BigInt): Try[BigInt] = { + checkEven(i).flatMap(ii => checkPositive(ii)).map(ii => ii + 1) + } ensuring(res => res match { + case Success(ii) => i % 2 == 0 && i > 0 && ii == i + 1 && ii % 2 == 1 + case Failure(_) => i % 2 != 0 || i <= 0 + }) + + +} diff --git a/frontends/benchmarks/verification/valid/PrivateIsFinal.scala b/frontends/benchmarks/verification/valid/PrivateIsFinal.scala new file mode 100644 index 0000000000..3bb0454dcf --- /dev/null +++ b/frontends/benchmarks/verification/valid/PrivateIsFinal.scala @@ -0,0 +1,9 @@ +object PrivateIsFinal { + trait SomeTrait { + private def someSecret(x: BigInt): BigInt = x + 42 + + final def testSecret(x: BigInt): Unit = { + assert(someSecret(x) == 42 + x) + } + } +} diff --git a/frontends/benchmarks/verification/valid/RedundantlyTypedPatterns.scala b/frontends/benchmarks/verification/valid/RedundantlyTypedPatterns.scala new file mode 100644 index 0000000000..8bb2964d95 --- /dev/null +++ b/frontends/benchmarks/verification/valid/RedundantlyTypedPatterns.scala @@ -0,0 +1,41 @@ +import stainless.lang._ + +object RedundantlyTypedPatterns { + + case class MyClass[S, T](s1: S, s2: S, t: T) + + case class MyOtherClass[S, T](s1: S, s2: S, t: T) + + case class YetAnotherClass(i1: Int, s: String, i2: Int) + + object MyOtherClass { + def unapply[S, T](moc: MyOtherClass[S, T]): Option[(S, MyOtherClass[S, T])] = Some((moc.s1, moc)) + } + + + def test1[A, B](moc: MyOtherClass[A, B]): A = moc match { + case MyOtherClass(s1: A, MyOtherClass(s2: A, moc2: MyOtherClass[A, B])) => s1 + } + + def test2[A, B](moc: MyOtherClass[A, B]): A = moc match { + case MyOtherClass(s1, MyOtherClass(s2, moc2)) => s1 + } + + def test3[A, B](a: A, b: B): Unit = { + val (aa1: A, bb: B) = (a, b) + val (aa2: A, ab1: (A, B)) = (a, (a, b)) + val (ab2: (A, B), aa3: A) = ((a, b), a) + } + + def test4[A, B](mc: MyClass[A, B]): A = mc match { + case MyClass(a1: A, a2: A, b: B) => a1 + } + + def test5(mc: MyClass[Int, String]): Int = mc match { + case MyClass(a1: Int, a2: Int, b: String) => a1 + } + + def test6(yac: YetAnotherClass): Int = yac match { + case YetAnotherClass(a1: Int, a2: String, a3: Int) => a1 + } +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/org/scalatest/FixedThreadPoolParallelExecution.scala b/frontends/common/src/it/scala/org/scalatest/FixedThreadPoolParallelExecution.scala new file mode 100644 index 0000000000..384e688caf --- /dev/null +++ b/frontends/common/src/it/scala/org/scalatest/FixedThreadPoolParallelExecution.scala @@ -0,0 +1,25 @@ +package org.scalatest + +import java.util.concurrent.Executors +import org.scalatest.tools.ConcurrentDistributor + +/** + * Trait that sets number of threads in parallel execution by setting a fixed size thread pool + * + * Taken from https://github.com/mateuszgruszczynski/scalatesttwolevelparallelism/blob/master/src/main/scala/org/scalatest/FixedThreadPoolParallelExecution.scala + */ +trait FixedThreadPoolParallelExecution extends SuiteMixin with ParallelTestExecution{ this: Suite => + + def threadPoolSize: Int + + abstract override def run(testName: Option[String], args: Args): Status = + super.run(testName, args.copy( + distributor = Some( + new ConcurrentDistributor( + args, + java.util.concurrent.Executors.newFixedThreadPool(threadPoolSize, Executors.defaultThreadFactory) + ) + ) + )) + +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/ComponentTestSuite.scala b/frontends/common/src/it/scala/stainless/ComponentTestSuite.scala index dcc3c3dad1..db96fe3e96 100644 --- a/frontends/common/src/it/scala/stainless/ComponentTestSuite.scala +++ b/frontends/common/src/it/scala/stainless/ComponentTestSuite.scala @@ -4,6 +4,8 @@ package stainless import scala.concurrent.Await import scala.concurrent.duration._ +import org.scalatest.time.Span +import org.scalatest.FixedThreadPoolParallelExecution import stainless.utils.YesNoOnly @@ -11,15 +13,31 @@ import extraction.ExtractionSummary import extraction.xlang.{ TreeSanitizer, trees => xt } import extraction.utils.DebugSymbols -trait ComponentTestSuite extends inox.TestSuite with inox.ResourceUtils with InputUtils { self => +// Note: this class extends FixedThreadPoolParallelExecution which extends ParallelTestExecution. +// When extending ParallelTestExecution, scalatest will create a new instance for each parallel test, +// running the class init code many times. +// Therefore, to avoid duplicating work, one should instead load the program (with loadPrograms) +// in the companion object and save it to a lazy val, and then call testAll with the program symbols +// (or other relevant functions). See for instance ImperativeSuite. +// +// When a custom logic is necessary without involving testAll, care must be again taken to not +// run "heavy work" in the test class itself but rather in its companion object, saved in a lazy val. +// See for instance EvaluatorComponentTest. +trait ComponentTestSuite extends inox.TestSuite with inox.ResourceUtils with InputUtils with FixedThreadPoolParallelExecution { self => + + override def threadPoolSize: Int = + scala.sys.props.get("testcase-parallelism").flatMap(_.toIntOption).getOrElse(1) + + override protected def sortingTimeout: Span = Span.Max val component: Component override def configurations: Seq[Seq[inox.OptionValue[_]]] = Seq( Seq( - inox.optSelectedSolvers(Set("smt-z3:z3-4.8.12")), + inox.optSelectedSolvers(Set("smt-z3")), inox.optTimeout(300.seconds), verification.optStrictArithmetic(false), + frontend.optBatchedProgram(true), termination.optInferMeasures(false), termination.optCheckMeasures(YesNoOnly.No), ) @@ -35,84 +53,14 @@ trait ComponentTestSuite extends inox.TestSuite with inox.ResourceUtils with Inp protected def filter(ctx: inox.Context, name: String): FilterStatus = Test - // Note: Scala files that are not kept will not even be loaded and extracted. - def testAll(dir: String, recursive: Boolean = false, keepOnly: String => Boolean = _ => true, identifierFilter: Identifier => Boolean = _ => true)(block: (component.Analysis, inox.Reporter, xt.UnitDef) => Unit): Unit = { - require(dir != null, "Function testAll must be called with a non-null directory string") - val fs = resourceFiles(dir, f => f.endsWith(".scala") && keepOnly(f), recursive).toList - - // Toggle this variable if you need to debug one specific test. - // You might also want to run `it:testOnly ** -- -z ""`. - val DEBUG = false - - if (DEBUG) { - for { - file <- fs.sortBy(_.getPath) - path = file.getPath - name = file.getName stripSuffix ".scala" - } test(s"$dir/$name", ctx => filter(ctx, s"$dir/$name")) { ctx ?=> - val (structure, program) = loadFiles(Seq(path)) - assert(ctx.reporter.errorCount == 0, "There should be no error while loading the files") - assert((structure count { _.isMain }) == 1, "Expecting only one main unit") - - val userFiltering = new DebugSymbols { - val name = "UserFiltering" - val context = ctx - val s: xt.type = xt - val t: xt.type = xt - } - - val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 - programSymbols.ensureWellFormed - val errors = TreeSanitizer(xt).enforce(programSymbols) - if (!errors.isEmpty) { - ctx.reporter.fatalError("There were errors in TreeSanitizer") - } - - val run = component.run(extraction.pipeline) - - val exProgram = inox.Program(run.trees)(run.extract(programSymbols)._1) - exProgram.symbols.ensureWellFormed - assert(ctx.reporter.errorCount == 0, "There were errors during extraction") - - val unit = structure.find(_.isMain).get - assert(unit.id.name == name, "Expecting compilation unit to have same name as source file") - - val defs = inox.utils.fixpoint { (defs: Set[Identifier]) => - def derived(flags: Seq[run.trees.Flag]): Boolean = - (defs & flags.collect { case run.trees.Derived(Some(id)) => id }.toSet).nonEmpty || - flags.contains(run.trees.Derived(None)) - - defs ++ - exProgram.symbols.functions.values.filter(fd => derived(fd.flags)).map(_.id) ++ - exProgram.symbols.sorts.values.filter(sort => derived(sort.flags)).map(_.id) - } (unit.allFunctions(using program.symbols).toSet ++ unit.allClasses) - - val funs = defs.filter(i => exProgram.symbols.functions.contains(i) && identifierFilter(i)).toSeq - - val report = Await.result(run.execute(funs, exProgram.symbols, ExtractionSummary.NoSummary), Duration.Inf) - block(report, ctx.reporter, unit) - } - } else { - val ctx: inox.Context = inox.TestContext.empty - import ctx.given - val (structure, program) = loadFiles(fs.map(_.getPath)) - assert(ctx.reporter.errorCount == 0, "There should be no error while loading the files") - - val userFiltering = new DebugSymbols { - val name = "UserFiltering" - val context = ctx - val s: xt.type = xt - val t: xt.type = xt - } - - val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 - programSymbols.ensureWellFormed - - for { - unit <- structure - if unit.isMain - name = unit.id.name - } test(s"$dir/$name", ctx => filter(ctx, s"$dir/$name")) { ctx ?=> + def testAll(dir: String, structure: Seq[xt.UnitDef], programSymbols: xt.Symbols, identifierFilter: Identifier => Boolean = _ => true) + (block: (component.Analysis, inox.Reporter, xt.UnitDef) => Unit): Unit = { + for { + unit <- structure + if unit.isMain + name = unit.id.name + } { + test(s"$dir/$name", ctx => filter(ctx, s"$dir/$name")) { ctx ?=> val defs = (unit.allFunctions(using programSymbols).toSet ++ unit.allClasses).filter(identifierFilter) val deps = defs.flatMap(id => programSymbols.dependencies(id) + id) @@ -129,16 +77,38 @@ trait ComponentTestSuite extends inox.TestSuite with inox.ResourceUtils with Inp val funs = inox.utils.fixpoint { (defs: Set[Identifier]) => def derived(flags: Seq[run.trees.Flag]): Boolean = (defs & flags.collect { case run.trees.Derived(Some(id)) => id }.toSet).nonEmpty || - flags.contains(run.trees.Derived(None)) + flags.contains(run.trees.Derived(None)) defs ++ - exSymbols.functions.values.filter(fd => derived(fd.flags)).map(_.id) ++ - exSymbols.sorts.values.filter(sort => derived(sort.flags)).map(_.id) + exSymbols.functions.values.filter(fd => derived(fd.flags)).map(_.id) ++ + exSymbols.sorts.values.filter(sort => derived(sort.flags)).map(_.id) } (defs).toSeq.filter(exSymbols.functions contains _) - val report = Await.result(run.execute(funs, exSymbols, ExtractionSummary.NoSummary),Duration.Inf) + val report = Await.result(run.execute(funs, exSymbols, ExtractionSummary.NoSummary), Duration.Inf) block(report, ctx.reporter, unit) } } } +} +object ComponentTestSuite extends inox.ResourceUtils with InputUtils { + // Note: Scala files that are not kept will not even be loaded and extracted. + def loadPrograms(dir: String, recursive: Boolean = false, keepOnly: String => Boolean = _ => true): (Seq[xt.UnitDef], xt.Symbols) = { + val fs = resourceFiles(dir, f => f.endsWith(".scala") && keepOnly(f), recursive).toList + val ctx: inox.Context = inox.TestContext.empty + import ctx.given + + val (structure, program) = loadFiles(fs.map(_.getPath)) + assert(ctx.reporter.errorCount == 0, "There should be no error while loading the files") + + val userFiltering = new DebugSymbols { + val name = "UserFiltering" + val context = ctx + val s: xt.type = xt + val t: xt.type = xt + } + + val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 + programSymbols.ensureWellFormed + (structure, programSymbols) + } } \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/ConfigurableTests.scala b/frontends/common/src/it/scala/stainless/ConfigurableTests.scala new file mode 100644 index 0000000000..d82f790365 --- /dev/null +++ b/frontends/common/src/it/scala/stainless/ConfigurableTests.scala @@ -0,0 +1,104 @@ +package stainless + +import _root_.io.circe.{JsonObject, Json} +import inox.{OptionValue, TestSilentReporter} +import org.scalatest.funsuite.AnyFunSuite +import stainless.equivchk.EquivalenceCheckingReport.Status +import stainless.extraction.utils.DebugSymbols +import stainless.extraction.xlang.{TreeSanitizer, trees as xt} +import stainless.utils.{CheckFilter, JsonUtils, YesNoOnly} +import stainless.verification.* + +import java.io.File +import scala.concurrent.Await +import scala.concurrent.duration.* +import scala.util.matching.Regex +import scala.util.{Failure, Success, Try} + +trait ConfigurableTests extends inox.ResourceUtils with InputUtils { + type TestConf + + case class Benchmark(dir: String, runs: Seq[Run], syms: xt.Symbols) + case class Run(variant: Option[Int], testConf: TestConf) + + val baseFolder: String + + lazy val benchmarkData: Seq[Benchmark] = Utils.getFolders(baseFolder) + .flatMap(benchmark => loadBenchmark(s"$baseFolder/$benchmark").map { case (runs, syms) => Benchmark(benchmark, runs, syms) }) + + val testConfPattern: Regex = "test_conf(_(\\d+))?.json".r + val expectedOutcomePattern: Regex = "expected_outcome(_(\\d+))?.json".r + + def parseTestConf(f: File): TestConf + + def loadBenchmark(benchmarkDir: String): Option[(Seq[Run], xt.Symbols)] = { + val files = resourceFiles(benchmarkDir, f => f.endsWith(".scala") || f.endsWith(".conf") || f.endsWith(".json")).sorted + if (files.isEmpty) return None // Empty folder -- skip + + val scalaFiles = files.filter(_.getName.endsWith(".scala")) + + val confs = files.flatMap(f => f.getName match { + case testConfPattern(_, num) => + val conf = parseTestConf(f) + Some(Option(num).map(_.toInt) -> conf) + case _ => None + }).toMap + assert(confs.nonEmpty, s"No test_conf.json found in $benchmarkDir") + + val runs = confs.keySet.toSeq.sorted.map(num => Run(num, confs(num))) + + ///////////////////////////////////// + + val dummyCtx: inox.Context = inox.TestContext.empty + import dummyCtx.given + val (_, program) = loadFiles(scalaFiles.map(_.getPath)) + assert(dummyCtx.reporter.errorCount == 0, "There should be no error while loading the files") + + val userFiltering = new DebugSymbols { + val name = "UserFiltering" + val context = dummyCtx + val s: xt.type = xt + val t: xt.type = xt + } + + val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 + programSymbols.ensureWellFormed + + Some((runs, programSymbols)) + } + + extension[T] (o: Option[T]) { + def getOrCry(msg: String): T = o.getOrElse(throw AssertionError(msg)) + } + extension (id: Identifier) { + def fullName: String = CheckFilter.fixedFullName(id) + } + extension (jsonObj: JsonObject) { + def getStringOrCry(field: String): String = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asString.getOrCry(s"Expected '$field' to be a string") + + def getIntOrCry(field: String): Int = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asNumber.getOrCry(s"Expected '$field' to be a number") + .toInt.getOrCry(s"Expected '$field' to fit into an Int") + + def getObjectOrCry(field: String): JsonObject = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asObject.getOrCry(s"Expected '$field' to be an object") + + def getStringArrayOrCry(field: String): Seq[String] = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asArray.getOrCry(s"Expected '$field' to be an array of strings") + .map(_.asString.getOrCry(s"Expected '$field' array elements to be strings")) + + def getArrayOrCry(field: String): Seq[Json] = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asArray.getOrCry(s"Expected '$field' to be an array") + + def getObjectArrayOrCry(field: String): Seq[JsonObject] = + jsonObj(field).getOrCry(s"Expected a '$field' field") + .asArray.getOrCry(s"Expected '$field' to be an array of object") + .map(_.asObject.getOrCry(s"Expected '$field' array elements to be objects")) + } +} diff --git a/frontends/common/src/it/scala/stainless/FilteringTestSuite.scala b/frontends/common/src/it/scala/stainless/FilteringTestSuite.scala new file mode 100644 index 0000000000..996a4dc300 --- /dev/null +++ b/frontends/common/src/it/scala/stainless/FilteringTestSuite.scala @@ -0,0 +1,52 @@ +package stainless + +import _root_.io.circe.JsonObject +import org.scalatest.concurrent.* +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import org.scalatest.{CancelAfterFailure, Tag, exceptions} +import stainless.extraction.xlang.{TreeSanitizer, trees as xt} +import stainless.utils.{CheckFilter, JsonUtils} +import stainless.verification.VerificationComponent + +import java.io.File + +class FilteringTestSuite extends AnyFunSuite with Matchers with TimeLimits { + import FilteringTestSuite.* + for (Benchmark(benchmarkDir, testConfs, programSymbols) <- benchmarkData) { + given context: inox.Context = inox.TestContext.empty + for (Run(variant, testConf) <- testConfs) { + val testName = s"$benchmarkDir ${variant.map(v => s" (variant $v)").getOrElse("")}" + test(testName) { + val (exSymbols, exSummary) = stainless.extraction.pipeline.extract(programSymbols) + val extractionFilter = CheckFilter(exSymbols.trees, context) + val ids = exSymbols.functions.values.filter(id => testConf.toKeep(id.id.name)).map(_.id).toSeq + // Note: passing a dummy VerificationComponent + val toProcess = extractionFilter.filter(ids, exSymbols, VerificationComponent) + val got = toProcess.groupMapReduce(_.name)(_ => 1)(_ + _).map(Results.apply).toSet + got shouldEqual testConf.expected + } + } + } +} +object FilteringTestSuite extends ConfigurableTests { + case class TestConf(toKeep: Set[String], expected: Set[Results]) + case class Results(fn: String, occurrences: Int) + + override val baseFolder: String = "filtering" + + override def parseTestConf(f: File): TestConf = { + val json = JsonUtils.parseFile(f) + val jsonObj = json.asObject.getOrCry("Expected top-level json to be an object") + val toKeep = jsonObj.getStringArrayOrCry("toKeep").toSet + val expected = jsonObj.getArrayOrCry("expected") + .map { objOrStr => + objOrStr.asString.map(Results(_, 1)) + .getOrElse { + val obj = objOrStr.asObject.getOrCry("Expected 'expected' elements to be a string or an object") + Results(obj.getStringOrCry("fn"), obj.getIntOrCry("occurrences")) + } + }.toSet + TestConf(toKeep, expected) + } +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/Utils.scala b/frontends/common/src/it/scala/stainless/Utils.scala index b980340302..b95abc7d4c 100644 --- a/frontends/common/src/it/scala/stainless/Utils.scala +++ b/frontends/common/src/it/scala/stainless/Utils.scala @@ -1,6 +1,7 @@ package stainless -import sys.process._ +import java.io.File +import scala.sys.process.* object Utils { def runCommand(cmd: String): (List[String], Int) = { @@ -18,4 +19,12 @@ object Utils { compiler.join() (ctx, compiler.getReport) } + + def getFolders(dir: String): Seq[String] = { + Option(getClass.getResource(s"/$dir")).toSeq.flatMap { dirUrl => + val dirFile = new File(dirUrl.getPath) + Option(dirFile.listFiles().toSeq).getOrElse(Seq.empty).filter(_.isDirectory) + .map(_.getName) + }.sorted + } } diff --git a/frontends/common/src/it/scala/stainless/equivchk/EquivChkSuite.scala b/frontends/common/src/it/scala/stainless/equivchk/EquivChkSuite.scala index ce33c3a3f8..0ed6bf8218 100644 --- a/frontends/common/src/it/scala/stainless/equivchk/EquivChkSuite.scala +++ b/frontends/common/src/it/scala/stainless/equivchk/EquivChkSuite.scala @@ -26,73 +26,12 @@ class EquivChkSuite extends ComponentTestSuite { ) ++ seq } - private val testConfPattern = "test_conf(_(\\d+))?.json".r - private val expectedOutcomePattern = "expected_outcome(_(\\d+))?.json".r - override protected def optionsString(options: inox.Options): String = "" - /////////////////////////////////////////////// - - for (benchmark <- getFolders("equivalence")) { - testEquiv(s"equivalence/$benchmark") - } - - /////////////////////////////////////////////// - - private def getFolders(dir: String): Seq[String] = { - Option(getClass.getResource(s"/$dir")).toSeq.flatMap { dirUrl => - val dirFile = new File(dirUrl.getPath) - Option(dirFile.listFiles().toSeq).getOrElse(Seq.empty).filter(_.isDirectory) - .map(_.getName) - }.sorted - } - - private def testEquiv(benchmarkDir: String): Unit = { - val files = resourceFiles(benchmarkDir, f => f.endsWith(".scala") || f.endsWith(".conf") || f.endsWith(".json")).sorted - if (files.isEmpty) return // Empty folder -- skip - - val scalaFiles = files.filter(_.getName.endsWith(".scala")) - - val confs = files.flatMap(f => f.getName match { - case testConfPattern(_, num) => Some(Option(num).map(_.toInt) -> f) - case _ => None - }).toMap - assert(confs.nonEmpty, s"No test_conf.json found in $benchmarkDir") - - val expectedOutcomes = files.flatMap(f => f.getName match { - case expectedOutcomePattern(_, num) => Some(Option(num).map(_.toInt) -> f) - case _ => None - }).toMap - assert(expectedOutcomes.nonEmpty, s"No expected_outcome.json found in $benchmarkDir") - assert(confs.keySet == expectedOutcomes.keySet, "Test configuration and expected outcome files do not match") - - val runs = confs.keySet.toSeq.sorted.map { num => - (num, confs(num), expectedOutcomes(num)) - } - - ///////////////////////////////////// - - val dummyCtx: inox.Context = inox.TestContext.empty - import dummyCtx.given - val (_, program) = loadFiles(scalaFiles.map(_.getPath)) - assert(dummyCtx.reporter.errorCount == 0, "There should be no error while loading the files") - - val userFiltering = new DebugSymbols { - val name = "UserFiltering" - val context = dummyCtx - val s: xt.type = xt - val t: xt.type = xt - } - - val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 - programSymbols.ensureWellFormed - - ///////////////////////////////////// - - for ((num, confFile, expectedOutomeFile) <- runs) { - val conf = parseTestConf(confFile) - val expected = parseExpectedOutcome(expectedOutomeFile) + import EquivChkSuite._ + for (Benchmark(benchmarkDir, runs, programSymbols) <- benchmarkData) { + for (Run(num, conf) <- runs) { val testName = s"$benchmarkDir${num.map(n => s" (variant $n)").getOrElse("")}" test(testName, ctx => filter(ctx, benchmarkDir)) { ctx0 ?=> val opts = Seq( @@ -118,33 +57,14 @@ class EquivChkSuite extends ComponentTestSuite { given inox.Context = ctx val report = Await.result(component.run(extraction.pipeline).apply(ids, programSymbols2), Duration.Inf) val got = extractResults(conf.candidates, report) - got shouldEqual expected + got shouldEqual conf.results } } } - private case class EquivResults(equiv: Map[String, Set[String]], - unequivalent: Set[String], - unsafe: Set[String], - timeout: Set[String], - wrong: Set[String]) - private object EquivResults { - def empty: EquivResults = - EquivResults(Map.empty, Set.empty, Set.empty, Set.empty, Set.empty) - } - - private case class TestConf(models: Set[String], - candidates: Set[String], - tests: Set[String], - norm: Option[String], - n: Option[Int], - initScore: Option[Int], - maxPerm: Option[Int], - timeout: Option[Duration]) - - private def extractResults(candidates: Set[String], analysis: component.Analysis): EquivResults = { + private def extractResults(candidates: Set[String], analysis: component.Analysis): Results = { import EquivalenceCheckingReport._ - analysis.records.foldLeft(EquivResults.empty) { + analysis.records.foldLeft(Results.empty) { case (acc, record) => val fn = record.id.fullName if (candidates(fn)) { @@ -154,35 +74,41 @@ class EquivChkSuite extends ComponentTestSuite { acc.copy(equiv = acc.equiv + (mod.fullName -> (currCluster + fn))) case Status.Equivalence(EquivalenceStatus.Unequivalent) => acc.copy(unequivalent = acc.unequivalent + fn) case Status.Equivalence(EquivalenceStatus.Unsafe) => acc.copy(unsafe = acc.unsafe + fn) - case Status.Equivalence(EquivalenceStatus.Unknown) => acc.copy(timeout = acc.timeout + fn) + case Status.Equivalence(EquivalenceStatus.UnknownSafety) => acc.copy(unknownSafety = acc.unknownSafety + fn) + case Status.Equivalence(EquivalenceStatus.UnknownEquivalence) => acc.copy(unknownEquivalence = acc.unknownEquivalence + fn) case Status.Equivalence(EquivalenceStatus.Wrong) => acc.copy(wrong = acc.wrong + fn) case Status.Verification(_) => acc } } else acc } } - - private def parseExpectedOutcome(f: File): EquivResults = { - val json = JsonUtils.parseFile(f) - val jsonObj = json.asObject.getOrCry("Expected top-level json to be an object") - - val equivObj = jsonObj("equivalent").getOrCry("Expected 'equivalent' field") - .asArray.getOrCry("Expected 'equivalent' to be an array") - val equiv = equivObj.map { elem => - val elemObj = elem.asObject.getOrCry("Expected elements in 'equivalent' to be objects") - val model = elemObj("model").getOrCry("Expected a 'model' field in 'equivalent'") - .asString.getOrCry("Expected 'model' to be a string") - val fns = elemObj.getStringArrayOrCry("functions").toSet - model -> fns - }.toMap - val unequivalent = jsonObj.getStringArrayOrCry("unequivalent").toSet - val unsafe = jsonObj.getStringArrayOrCry("unsafe").toSet - val timeout = jsonObj.getStringArrayOrCry("timeout").toSet - val wrong = jsonObj.getStringArrayOrCry("wrong").toSet - EquivResults(equiv, unequivalent, unsafe, timeout, wrong) +} +object EquivChkSuite extends ConfigurableTests { + override val baseFolder: String = "equivalence" + + case class Results(equiv: Map[String, Set[String]], + unequivalent: Set[String], + unsafe: Set[String], + unknownSafety: Set[String], + unknownEquivalence: Set[String], + wrong: Set[String]) + + object Results { + def empty: Results = + Results(Map.empty, Set.empty, Set.empty, Set.empty, Set.empty, Set.empty) } - private def parseTestConf(f: File): TestConf = { + case class TestConf(models: Set[String], + candidates: Set[String], + tests: Set[String], + norm: Option[String], + n: Option[Int], + initScore: Option[Int], + maxPerm: Option[Int], + timeout: Option[Duration], + results: Results) + + override def parseTestConf(f: File): TestConf = { val json = JsonUtils.parseFile(f) val jsonObj = json.asObject.getOrCry("Expected top-level json to be an object") val models = jsonObj.getStringArrayOrCry("models") @@ -195,19 +121,25 @@ class EquivChkSuite extends ComponentTestSuite { val timeout = jsonObj("timeout").map(_.asNumber.getOrCry("Expected 'timeout' to be an double").toDouble) assert(models.nonEmpty, "At least one model must be specified") assert(candidates.nonEmpty, "At least one candidate must be specified") - TestConf(models.toSet, candidates.toSet, tests.toSet, norm, n, initScore, maxPerm, timeout.map(_.seconds)) + val res = parseExpectedOutcome(jsonObj.getObjectOrCry("outcome")) + TestConf(models.toSet, candidates.toSet, tests.toSet, norm, n, initScore, maxPerm, timeout.map(_.seconds), res) } - extension[T] (o: Option[T]) { - private def getOrCry(msg: String): T = o.getOrElse(throw AssertionError(msg)) - } - extension (id: Identifier) { - private def fullName: String = CheckFilter.fixedFullName(id) - } - extension (jsonObj: JsonObject) { - private def getStringArrayOrCry(field: String): Seq[String] = - jsonObj(field).getOrCry(s"Expected a '$field' field") - .asArray.getOrCry(s"Expected '$field' to be an array of strings") - .map(_.asString.getOrCry(s"Expected '$field' array elements to be strings")) + private def parseExpectedOutcome(jsonObj: JsonObject): Results = { + val equivObj = jsonObj("equivalent").getOrCry("Expected 'equivalent' field") + .asArray.getOrCry("Expected 'equivalent' to be an array") + val equiv = equivObj.map { elem => + val elemObj = elem.asObject.getOrCry("Expected elements in 'equivalent' to be objects") + val model = elemObj("model").getOrCry("Expected a 'model' field in 'equivalent'") + .asString.getOrCry("Expected 'model' to be a string") + val fns = elemObj.getStringArrayOrCry("functions").toSet + model -> fns + }.toMap + val unequivalent = jsonObj.getStringArrayOrCry("unequivalent").toSet + val unsafe = jsonObj.getStringArrayOrCry("unsafe").toSet + val unknownSafety = jsonObj.getStringArrayOrCry("unknownSafety").toSet + val unknownEquiv = jsonObj.getStringArrayOrCry("unknownEquivalence").toSet + val wrong = jsonObj.getStringArrayOrCry("wrong").toSet + Results(equiv, unequivalent, unsafe, unknownSafety, unknownEquiv, wrong) } } diff --git a/frontends/common/src/it/scala/stainless/evaluators/EvaluatorComponentTest.scala b/frontends/common/src/it/scala/stainless/evaluators/EvaluatorComponentTest.scala index 2608f78cca..09085c100e 100644 --- a/frontends/common/src/it/scala/stainless/evaluators/EvaluatorComponentTest.scala +++ b/frontends/common/src/it/scala/stainless/evaluators/EvaluatorComponentTest.scala @@ -18,110 +18,79 @@ import scala.concurrent.duration.* class EvaluatorComponentTest extends ComponentTestSuite { override val component: EvaluatorComponent.type = EvaluatorComponent - private def testConfName = "test_conf.json" - override protected def optionsString(options: inox.Options): String = "" - /////////////////////////////////////////////// - - for (benchmark <- getFolders("evaluators")) { - testEval(s"evaluators/$benchmark") - } - - /////////////////////////////////////////////// - - private def getFolders(dir: String): Seq[String] = { - Option(getClass.getResource(s"/$dir")).toSeq.flatMap { dirUrl => - val dirFile = new File(dirUrl.getPath) - Option(dirFile.listFiles().toSeq).getOrElse(Seq.empty).filter(_.isDirectory) - .map(_.getName) - }.sorted - } - - private def testEval(benchmarkDir: String): Unit = { - val files = resourceFiles(benchmarkDir, f => f.endsWith(".scala") || f.endsWith(".json")).sorted - if (files.isEmpty) return // Empty folder -- skip + import EvaluatorComponentTest.* - val scalaFiles = files.filter(_.getName.endsWith(".scala")) - val testConfFile = files.find(_.getName == testConfName) - .getOrElse(fail(s"Test configuration file $testConfName not found in $benchmarkDir")) - val testConf = parseTestConf(testConfFile) - - ///////////////////////////////////// - - val dummyCtx: inox.Context = inox.TestContext.empty - import dummyCtx.given - val (_, program) = loadFiles(scalaFiles.map(_.getPath)) - assert(dummyCtx.reporter.errorCount == 0, "There should be no error while loading the files") - - val userFiltering = new DebugSymbols { - val name = "UserFiltering" - val context = dummyCtx - val s: xt.type = xt - val t: xt.type = xt - } - - val programSymbols = userFiltering.debugWithoutSummary(frontend.UserFiltering().transform)(program.symbols)._1 - programSymbols.ensureWellFormed - - ///////////////////////////////////// + for (Benchmark(benchmarkDir, testConfs, programSymbols) <- benchmarkData) { + for (Run(variant, testConf) <- testConfs) { + for (evaluator <- testConf.evaluators) { + val testName = s"$benchmarkDir ($evaluator)${variant.map(v => s" (variant $v)").getOrElse("")}" + test(testName) { ctx0 ?=> + val opt = evaluator match { + case Evaluator.Recursive => optCodeGen(false) + case Evaluator.Codegen => optCodeGen(true) + } - for (evaluator <- testConf.evaluators) { - test(s"$benchmarkDir ($evaluator)") { ctx0 ?=> - val opt = evaluator match { - case Evaluator.Recursive => optCodeGen(false) - case Evaluator.Codegen => optCodeGen(true) - } - given ctx: inox.Context = ctx0.withOpts(opt) - - // Note that `run` must be defined here to pick the `ctx` with updated evaluator option. - val run = component.run(extraction.pipeline) - val exSymbols = run.extract(programSymbols)._1 - exSymbols.ensureWellFormed - assert(ctx.reporter.errorCount == 0, "There were errors during pipeline extraction") - - val fnsToEval = exSymbols.functions.keys.filter(testConf.needsEvaluation).toSeq - - val groundValues = testConf.expected.collect { - case EvalResult.SuccessfulEval(tested, ground) => - val groundFd = exSymbols.functions.find(_._1.name == ground) - .getOrElse(fail(s"Function for ground result $ground not found")) - ._2.fullBody - tested -> groundFd - }.toMap - - val report = Await.result(run.execute(fnsToEval, exSymbols, ExtractionSummary.NoSummary), Duration.Inf) - for { - res <- report.results - exp <- testConf.expected.find(_.testedFn == res.fd.id.name) - } { - exp match { - case EvalResult.SuccessfulEval(tested, _) => - res.status match { - case EvaluatorRun.NoPost(bodyValue) => - bodyValue shouldEqual groundValues(tested) - case EvaluatorRun.PostHeld(bodyValue) => - bodyValue shouldEqual groundValues(tested) - case _ => fail(s"Evaluation of ${res.fd.id} did not succeed: got ${res.status}") - } - case EvalResult.FailedEval(_) => - res.status match { - case EvaluatorRun.NoPost(_) | EvaluatorRun.PostHeld(_) => - fail(s"Evaluation of ${res.fd.id} should have failed, but has succeeded") - case _ => () - } + given ctx: inox.Context = ctx0.withOpts(opt) + + // Note that `run` must be defined here to pick the `ctx` with updated evaluator option. + val run = component.run(extraction.pipeline) + val exSymbols = run.extract(programSymbols)._1 + exSymbols.ensureWellFormed + assert(ctx.reporter.errorCount == 0, "There were errors during pipeline extraction") + + val fnsToEval = exSymbols.functions.keys.filter(testConf.needsEvaluation).toSeq + + val groundValues = testConf.expected.collect { + case EvalResult.SuccessfulEval(tested, ground) => + val groundFd = exSymbols.functions.find(_._1.name == ground) + .getOrElse(fail(s"Function for ground result $ground not found")) + ._2.fullBody + tested -> groundFd + }.toMap + + val report = Await.result(run.execute(fnsToEval, exSymbols, ExtractionSummary.NoSummary), Duration.Inf) + for { + res <- report.results + exp <- testConf.expected.find(_.testedFn == res.fd.id.name) + } { + exp match { + case EvalResult.SuccessfulEval(tested, _) => + res.status match { + case EvaluatorRun.NoPost(bodyValue) => + bodyValue shouldEqual groundValues(tested) + case EvaluatorRun.PostHeld(bodyValue) => + bodyValue shouldEqual groundValues(tested) + case _ => fail(s"Evaluation of ${res.fd.id} did not succeed: got ${res.status}") + } + case EvalResult.FailedEval(_) => + res.status match { + case EvaluatorRun.NoPost(_) | EvaluatorRun.PostHeld(_) => + fail(s"Evaluation of ${res.fd.id} should have failed, but has succeeded") + case _ => () + } + } } } } } } +} +object EvaluatorComponentTest extends ConfigurableTests { + override val baseFolder: String = "evaluators" - private enum Evaluator { + case class TestConf(evaluators: Seq[Evaluator], + expected: Seq[EvalResult]) { + def needsEvaluation(fn: Identifier): Boolean = expected.exists(_.testedFn == fn.name) + } + + enum Evaluator { case Recursive case Codegen } - private enum EvalResult { + enum EvalResult { case SuccessfulEval(fn: String, ground: String) case FailedEval(fn: String) @@ -131,18 +100,13 @@ class EvaluatorComponentTest extends ComponentTestSuite { } } - private case class TestConf(evaluators: Seq[Evaluator], - expected: Seq[EvalResult]) { - def needsEvaluation(fn: Identifier): Boolean = expected.exists(_.testedFn == fn.name) - } - - private def parseTestConf(f: File): TestConf = { + override def parseTestConf(f: File): TestConf = { val json = JsonUtils.parseFile(f) val jsonObj = json.asObject.getOrCry("Expected top-level json to be an object") val recEval = jsonObj("recursive").exists(_.asBoolean.getOrCry("Expected 'recursive' to be a boolean")) val codegenEval = jsonObj("codegen").exists(_.asBoolean.getOrCry("Expected 'codegen' to be a boolean")) if (!recEval && !codegenEval) { - fail("At least one of 'recursive' or 'codegen' evaluator option must be set to true") + sys.error("At least one of 'recursive' or 'codegen' evaluator option must be set to true") } val evaluators = (if (recEval) Seq(Evaluator.Recursive) else Seq.empty) ++ (if (codegenEval) Seq(Evaluator.Codegen) else Seq.empty) @@ -163,14 +127,4 @@ class EvaluatorComponentTest extends ComponentTestSuite { }.getOrElse(Vector.empty) TestConf(evaluators, successful ++ failures) } - - extension[T] (o: Option[T]) { - private def getOrCry(msg: String): T = o.getOrElse(fail(msg)) - } - - extension (jsonObj: JsonObject) { - private def getStringOrCry(field: String): String = - jsonObj(field).getOrCry(s"Expected a '$field' field") - .asString.getOrCry(s"Expected '$field' to a string") - } -} +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/termination/TerminationSuite.scala b/frontends/common/src/it/scala/stainless/termination/TerminationSuite.scala index d23cb8bf0f..bf23d16a31 100644 --- a/frontends/common/src/it/scala/stainless/termination/TerminationSuite.scala +++ b/frontends/common/src/it/scala/stainless/termination/TerminationSuite.scala @@ -6,6 +6,7 @@ package termination import org.scalatest.funsuite.AnyFunSuite import stainless.utils.YesNoOnly import stainless.verification._ +import extraction.xlang.{trees => xt} import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} @@ -29,6 +30,8 @@ class TerminationSuite extends VerificationComponentTestSuite { case "verification/valid/BitsTricksSlow" => Skip // Flaky case "verification/valid/PackedFloat8" => Ignore + // Succeeds most of the time, but unsuitable for CI due to its flakiness + case "imperative/valid/i1306b" => Ignore case _ => super.filter(ctx, name) } @@ -39,41 +42,21 @@ class TerminationSuite extends VerificationComponentTestSuite { analysis.sources .toSeq .sortBy(_.name) - .map(symbols.getFunction(_)) + .map(symbols.getFunction) .map { fd => fd -> fd.flags.collectFirst { case TerminationStatus(status) => status } } } - testAll("termination/valid") { (analysis, reporter, _) => - val failures = getResults(analysis).collect { - case (fd, Some(status)) if !status.isTerminating => fd - } + import TerminationSuite._ - assert(failures.isEmpty, "Functions " + failures.map(_.id) + " should be annotated as terminating") + testAllTerminating("termination/valid", terminationValid._1, terminationValid._2) - for ((vc, vr) <- analysis.vrs) { - if (vr.isInvalid) fail(s"The following verification condition was invalid: $vc @${vc.getPos}") - if (vr.isInconclusive) fail(s"The following verification condition was inconclusive: $vc @${vc.getPos}") - } - reporter.terminateIfError() - } + testAllTerminating("verification/valid", verificationValid._1, verificationValid._2) - testAll("verification/valid") { (analysis, reporter, _) => - val failures = getResults(analysis).collect { - case (fd, Some(status)) if !status.isTerminating => fd - } - - assert(failures.isEmpty, "Functions " + failures.map(_.id) + " should be annotated as terminating") + testAllTerminating("imperative/valid", imperativeValid._1, imperativeValid._2) - for ((vc, vr) <- analysis.vrs) { - if (vr.isInvalid) fail(s"The following verification condition was invalid: $vc @${vc.getPos}") - if (vr.isInconclusive) fail(s"The following verification condition was inconclusive: $vc @${vc.getPos}") - } - reporter.terminateIfError() - } - - testAll("termination/looping") { (analysis, reporter, _) => + testAll("termination/looping", terminationLooping._1, terminationLooping._2) { (analysis, reporter, _) => import analysis.program.symbols import analysis.program.trees._ @@ -98,10 +81,10 @@ class TerminationSuite extends VerificationComponentTestSuite { reporter.terminateIfError() } - testUncheckedAll("termination/unchecked-invalid") + testUncheckedAll("termination/unchecked-invalid", terminationUncheckedInvalid._1, terminationUncheckedInvalid._2) // Tests that should be verified, but aren't (not compatible with System FR type-checker, or needs more inference) - testNegAll("termination/false-invalid") + testNegAll("termination/false-invalid", terminationFalseInvalid._1, terminationFalseInvalid._2) // Looping programs that are already correctly rejected by the type-checker // Since these are rejected at extraction (and not due to invalid VCs), we need @@ -129,7 +112,31 @@ class TerminationSuite extends VerificationComponentTestSuite { }} } + private def testAllTerminating(dir: String, structure: Seq[xt.UnitDef], programSymbols: xt.Symbols): Unit = { + testAll(dir, structure, programSymbols) { (analysis, reporter, _) => + val failures = getResults(analysis).collect { + case (fd, Some(status)) if !status.isTerminating => fd + } + + assert(failures.isEmpty, "Functions " + failures.map(_.id) + " should be annotated as terminating") + + for ((vc, vr) <- analysis.vrs) { + if (vr.isInvalid) fail(s"The following verification condition was invalid: $vc @${vc.getPos}") + if (vr.isInconclusive) fail(s"The following verification condition was inconclusive: $vc @${vc.getPos}") + } + reporter.terminateIfError() + } + } + // Workaround for a compiler crash caused by calling super.test def superTest(self: AnyFunSuite, testName: String)(body: => Unit): Unit = self.test(testName)(body) } +object TerminationSuite { + private lazy val terminationValid = ComponentTestSuite.loadPrograms("termination/valid") + private lazy val verificationValid = ComponentTestSuite.loadPrograms("verification/valid") + private lazy val imperativeValid = ComponentTestSuite.loadPrograms("imperative/valid") + private lazy val terminationLooping = ComponentTestSuite.loadPrograms("termination/looping") + private lazy val terminationUncheckedInvalid = ComponentTestSuite.loadPrograms("termination/unchecked-invalid") + private lazy val terminationFalseInvalid = ComponentTestSuite.loadPrograms("termination/false-invalid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/FullImperativeSuite.scala b/frontends/common/src/it/scala/stainless/verification/FullImperativeSuite.scala index eeb172fe64..566f5627bb 100644 --- a/frontends/common/src/it/scala/stainless/verification/FullImperativeSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/FullImperativeSuite.scala @@ -29,14 +29,22 @@ class FullImperativeSuite extends VerificationComponentTestSuite with inox.MainH // FIXME(gsps): Works, but flaky on CI? case "full-imperative/valid/TreeImmutMapGeneric" => Skip + // Timeout on z3-4.12.2 + case "full-imperative/valid/StackSimple" => Skip + case _ => super.filter(ctx, name) } // We filter out the 'copy' method from the extracted symbols, // since it involves allocations, and that isn't supported yet. // TODO: when allocation is supported, remove the identifierFilter + import FullImperativeSuite._ - testPosAll("full-imperative/valid", identifierFilter = i => !i.name.startsWith("copy")) + testPosAll("full-imperative/valid", valid._1, valid._2, identifierFilter = i => !i.name.startsWith("copy")) - testNegAll("full-imperative/invalid", identifierFilter = i => !i.name.startsWith("copy")) + testNegAll("full-imperative/invalid", invalid._1, invalid._2, identifierFilter = i => !i.name.startsWith("copy")) } +object FullImperativeSuite { + private lazy val valid = ComponentTestSuite.loadPrograms("full-imperative/valid") + private lazy val invalid = ComponentTestSuite.loadPrograms("full-imperative/invalid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/ImperativeSuite.scala b/frontends/common/src/it/scala/stainless/verification/ImperativeSuite.scala index 8df739e954..8bedc78d33 100644 --- a/frontends/common/src/it/scala/stainless/verification/ImperativeSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/ImperativeSuite.scala @@ -12,11 +12,19 @@ class ImperativeSuite extends VerificationComponentTestSuite { override def filter(ctx: inox.Context, name: String): FilterStatus = name match { // Unstable on 4.8.12, but works in non-incremental mode case "imperative/valid/WhileAsFun2" => Ignore + // Succeeds most of the time, but unsuitable for CI due to its flakiness + case "imperative/valid/i1306b" => Ignore case _ => super.filter(ctx, name) } - testPosAll("imperative/valid") + import ImperativeSuite._ - testNegAll("imperative/invalid") + testPosAll("imperative/valid", valid._1, valid._2) + + testNegAll("imperative/invalid", invalid._1, invalid._2) } +object ImperativeSuite { + private lazy val valid = ComponentTestSuite.loadPrograms("imperative/valid") + private lazy val invalid = ComponentTestSuite.loadPrograms("imperative/invalid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/StrictArithmeticSuite.scala b/frontends/common/src/it/scala/stainless/verification/StrictArithmeticSuite.scala index 724c40ca6d..f53a17d722 100644 --- a/frontends/common/src/it/scala/stainless/verification/StrictArithmeticSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/StrictArithmeticSuite.scala @@ -13,7 +13,13 @@ class StrictArithmeticSuite extends VerificationComponentTestSuite { override protected def optionsString(options: inox.Options): String = "" - testPosAll("strictarithmetic/valid") + import StrictArithmeticSuite._ - testNegAll("strictarithmetic/invalid") + testPosAll("strictarithmetic/valid", valid._1, valid._2) + + testNegAll("strictarithmetic/invalid", invalid._1, invalid._2) } +object StrictArithmeticSuite { + private lazy val valid = ComponentTestSuite.loadPrograms("strictarithmetic/valid") + private lazy val invalid = ComponentTestSuite.loadPrograms("strictarithmetic/invalid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/TerminationVerificationSuite.scala b/frontends/common/src/it/scala/stainless/verification/TerminationVerificationSuite.scala index c327f28399..b000584dcf 100644 --- a/frontends/common/src/it/scala/stainless/verification/TerminationVerificationSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/TerminationVerificationSuite.scala @@ -17,5 +17,10 @@ class TerminationVerificationSuite extends VerificationComponentTestSuite { override protected def optionsString(options: inox.Options): String = "" - testPosAll("termination/valid") + import TerminationVerificationSuite._ + + testPosAll("termination/valid", valid._1, valid._2) } +object TerminationVerificationSuite { + private lazy val valid = ComponentTestSuite.loadPrograms("termination/valid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/UncheckedSuite.scala b/frontends/common/src/it/scala/stainless/verification/UncheckedSuite.scala index 40851d9a8e..d4b97266af 100644 --- a/frontends/common/src/it/scala/stainless/verification/UncheckedSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/UncheckedSuite.scala @@ -5,26 +5,25 @@ package verification import org.scalatest._ -trait UncheckedSuite extends VerificationComponentTestSuite { - - override def configurations = super.configurations.map { - seq => Seq(optFailEarly(true), inox.solvers.optCheckModels(false)) ++ seq - } - - override val component: VerificationComponent.type = VerificationComponent - - testUncheckedAll("verification/unchecked-invalid") - testUncheckedAll("verification/unchecked-valid") -} - -class SMTZ3UncheckedSuite extends UncheckedSuite { - override def configurations = super.configurations.map { - seq => inox.optSelectedSolvers(Set("smt-z3:z3-4.8.12")) +: seq +class UncheckedSuite extends VerificationComponentTestSuite { + private val solvers = Seq("smt-z3", "smt-cvc4", "smt-cvc5") + + override def configurations: Seq[Seq[inox.OptionValue[_]]] = { + solvers.flatMap { solver => + super.configurations.map { + seq => Seq( + inox.optSelectedSolvers(Set(solver)), + optFailEarly(true), + inox.solvers.optCheckModels(false)) ++ seq + } + } } -} -class SMTCVC4UncheckedSuite extends UncheckedSuite { - override def configurations = super.configurations.map { - seq => inox.optSelectedSolvers(Set("smt-cvc4")) +: seq - } + import UncheckedSuite._ + testUncheckedAll("verification/unchecked-invalid", uncheckedInvalid._1, uncheckedInvalid._2) + testUncheckedAll("verification/unchecked-valid", uncheckedValid._1, uncheckedValid._2) } +object UncheckedSuite { + private lazy val uncheckedInvalid = ComponentTestSuite.loadPrograms("verification/unchecked-invalid") + private lazy val uncheckedValid = ComponentTestSuite.loadPrograms("verification/unchecked-valid") +} \ No newline at end of file diff --git a/frontends/common/src/it/scala/stainless/verification/VerificationComponentTestSuite.scala b/frontends/common/src/it/scala/stainless/verification/VerificationComponentTestSuite.scala index 314c3f281b..212a50889d 100644 --- a/frontends/common/src/it/scala/stainless/verification/VerificationComponentTestSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/VerificationComponentTestSuite.scala @@ -2,6 +2,7 @@ package stainless package verification import scala.util.Try +import extraction.xlang.{trees => xt} trait VerificationComponentTestSuite extends ComponentTestSuite { self => @@ -14,8 +15,8 @@ trait VerificationComponentTestSuite extends ComponentTestSuite { self => ) ++ seq } - def testPosAll(dir: String, recursive: Boolean = false, keepOnly: String => Boolean = _ => true, identifierFilter: Identifier => Boolean = _ => true): Unit = - testAll(dir, recursive, keepOnly, identifierFilter) { (analysis, reporter, _) => + def testPosAll(dir: String, structure: Seq[xt.UnitDef], programSymbols: xt.Symbols, identifierFilter: Identifier => Boolean = _ => true): Unit = + testAll(dir, structure, programSymbols, identifierFilter) { (analysis, reporter, _) => assert(analysis.toReport.stats.validFromCache == 0, "no cache should be used for these tests") for ((vc, vr) <- analysis.vrs) { if (vr.isInvalid) fail(s"The following verification condition was invalid: $vc @${vc.getPos}") @@ -24,14 +25,14 @@ trait VerificationComponentTestSuite extends ComponentTestSuite { self => reporter.terminateIfError() } - def testNegAll(dir: String, recursive: Boolean = false, keepOnly: String => Boolean = _ => true, identifierFilter: Identifier => Boolean = _ => true): Unit = - testAll(dir, recursive, keepOnly, identifierFilter) { (analysis, reporter, _) => + def testNegAll(dir: String, structure: Seq[xt.UnitDef], programSymbols: xt.Symbols, identifierFilter: Identifier => Boolean = _ => true): Unit = + testAll(dir, structure, programSymbols, identifierFilter) { (analysis, reporter, _) => val report = analysis.toReport assert(report.totalInvalid > 0, "There should be at least one invalid verification condition. " + report.stats) } - def testUncheckedAll(dir: String, recursive: Boolean = false, keepOnly: String => Boolean = _ => true, identifierFilter: Identifier => Boolean = _ => true): Unit = - testAll(dir, recursive, keepOnly, identifierFilter) { (analysis, reporter, _) => + def testUncheckedAll(dir: String, structure: Seq[xt.UnitDef], programSymbols: xt.Symbols, identifierFilter: Identifier => Boolean = _ => true): Unit = + testAll(dir, structure, programSymbols, identifierFilter) { (analysis, reporter, _) => val report = analysis.toReport assert(report.totalInvalid > 0 || report.totalUnknown > 0, "There should be at least one invalid/unknown verification condition.") diff --git a/frontends/common/src/it/scala/stainless/verification/VerificationSuite.scala b/frontends/common/src/it/scala/stainless/verification/VerificationSuite.scala index bd84ea7b87..095bd2f2e4 100644 --- a/frontends/common/src/it/scala/stainless/verification/VerificationSuite.scala +++ b/frontends/common/src/it/scala/stainless/verification/VerificationSuite.scala @@ -7,260 +7,254 @@ import scala.concurrent.duration._ import org.scalatest._ -trait VerificationSuite extends VerificationComponentTestSuite { - - override def filter(ctx: inox.Context, name: String): FilterStatus = name match { - case "verification/invalid/ForallAssoc" => Ignore // Hangs +class VerificationSuite extends VerificationComponentTestSuite { + private val solvers = Seq("smt-z3", "smt-cvc5", "princess") + private val ignoreCommon = Set( + // Hangs + "verification/invalid/ForallAssoc", // unknown/timeout VC but counter-example not found - case "verification/invalid/BadConcRope" => Ignore - + "verification/invalid/BadConcRope", // Lemmas used in one equation can leak in other equations due to https://github.com/epfl-lara/inox/issues/139 - case "verification/invalid/Equations1" => Ignore - case "verification/invalid/Equations2" => Ignore - case "verification/invalid/Equations3" => Ignore + "verification/invalid/Equations1", + "verification/invalid/Equations2", + "verification/invalid/Equations3", + ) - case _ => super.filter(ctx, name) - } + private val ignoreZ3 = ignoreCommon ++ + Set( + // Flaky + "verification/valid/PackedFloat8", + ) - // Scala 2 BitVectors tests leverages the fact that `==` can be used to compare two unrelated types. - // For instance, if we have x: Int42, then x == 42 is legal. - // In Scala 3, however, this expression is ill-formed because Int42 and Int (the widened type of 42) are unrelated. - // Therefore, all BitVectors tests for Scala 3 must perform a conversion for literals - // (e.g. the above expression is rewritten to x == (42: Int42)) - def bitVectorsTestDiscarding(file: String): Boolean = { - val scala2BitVectors = Set("MicroTests/scalac/BitVectors1.scala", "MicroTests/scalac/BitVectors2.scala", "MicroTests/scalac/BitVectors3.scala") - val scala3BitVectors = Set("MicroTests/dotty/BitVectors1.scala", "MicroTests/dotty/BitVectors2.scala", "MicroTests/dotty/BitVectors3.scala") + private val ignoreCVC = ignoreCommon ++ + Set( + // Unknown / cannot encode map with non-default values + "verification/valid/ArraySlice", + "verification/valid/BigIntMonoidLaws", + "verification/valid/BigIntRing", + "verification/valid/InnerClasses4", + "verification/valid/Iterables", + "verification/valid/PartialCompiler", + "verification/valid/PartialKVTrace", + ) - (isScala3 || !scala3BitVectors.exists(t => file.endsWith(t))) && - (isScala2 || !scala2BitVectors.exists(t => file.endsWith(t))) - } + private val ignorePrincess = ignoreCommon ++ + Set( + // valid/MicroTests + "verification/valid/ADTWithArray1", + "verification/valid/ADTWithArray2", + "verification/valid/ADTWithArray4", + "verification/valid/ADTWithArray5", + "verification/valid/ADTWithArray6", + "verification/valid/Array1", + "verification/valid/Array2", + "verification/valid/Array3", + "verification/valid/Array4", + "verification/valid/ArrayUpdated", + "verification/valid/BitVectors2", + "verification/valid/BitVectors3", + "verification/valid/Issue803", + "verification/valid/Monads3", + "verification/valid/Nested13", + "verification/valid/SetIsEmpty", + "verification/valid/Sets1", + "verification/valid/Sets2", + "verification/valid/Subtyping1", - testPosAll("verification/valid", recursive = true, bitVectorsTestDiscarding) + // valid/ + "verification/valid/ArraySlice", + "verification/valid/AnonymousClasses1", + "verification/valid/AssociativeList", + "verification/valid/AmortizedQueue", + "verification/valid/BalancedParentheses", + "verification/valid/BasicReal", + "verification/valid/BinarySearch", + "verification/valid/BinarySearchTreeQuant", + "verification/valid/BinarySearchTreeQuant2", + "verification/valid/BitsTricks", + "verification/valid/BitsTricksSlow", + "verification/valid/BooleanOps", + "verification/valid/BVMaxInterpret", + "verification/valid/Composition", + "verification/valid/ConcRope", + "verification/valid/ConcTree", + "verification/valid/ContMonad", + "verification/valid/Count", + "verification/valid/CovCollection", + "verification/valid/Filter", + "verification/valid/FiniteSort", + "verification/valid/FlatMap", + "verification/valid/FoolProofAdder", + "verification/valid/FunctionMaps", + "verification/valid/FunctionMapsObj", + "verification/valid/GodelNumbering", + "verification/valid/Heaps", + "verification/valid/HOInvocations2", + "verification/valid/i1379a", + "verification/valid/i1379e", + "verification/valid/i1379f", + "verification/valid/i1379g", + "verification/valid/InnerClasses4", + "verification/valid/InnerClassesInvariants", + "verification/valid/InsertionSort", + "verification/valid/IntSetInv", + "verification/valid/Iterables", + "verification/valid/LawTypeArgsElim", + "verification/valid/ListMonad", + "verification/valid/ListOperations", + "verification/valid/LiteralMaps", + "verification/valid/Longs", + "verification/valid/MapDiff", + "verification/valid/MapGetOrElse2", + "verification/valid/MapGetPlus", + "verification/valid/MergeSort", + "verification/valid/MergeSort2", + "verification/valid/Monotonicity", + "verification/valid/NNF", + "verification/valid/PackedFloat8", + "verification/valid/ParBalance", + "verification/valid/PartialCompiler", + "verification/valid/PartialKVTrace", + "verification/valid/PositiveMap", + "verification/valid/PropositionalLogic", + "verification/valid/QuickSort", + "verification/valid/QuickSortFilter", + "verification/valid/RedBlackTree", + "verification/valid/Reverse", + "verification/valid/StableSorter", + "verification/valid/TransitiveQuantification", + "verification/valid/Trees1", + "verification/valid/ValueClasses", - testNegAll("verification/invalid") + // invalid/ + "verification/invalid/AbstractRefinementMap", + "verification/invalid/Acc", + "verification/invalid/AddNaturals1", + "verification/invalid/AddNaturals2", + "verification/invalid/ADTWithArray1", + "verification/invalid/ADTWithArray2", + "verification/invalid/Array1", + "verification/invalid/Array2", + "verification/invalid/Array3", + "verification/invalid/Array4", + "verification/invalid/Array5", + "verification/invalid/Array6", + "verification/invalid/Array7", + "verification/invalid/Asserts1", + "verification/invalid/AssociativityProperties", + "verification/invalid/BadConcRope", + "verification/invalid/BigArray", + "verification/invalid/BinarySearch1", + "verification/invalid/BinarySearch2", + "verification/invalid/BinarySearch3", + "verification/invalid/BraunTree", + "verification/invalid/Choose1", + "verification/invalid/Choose2", + "verification/invalid/Existentials", + "verification/invalid/ExternFallbackMut", + "verification/invalid/FiniteSort", + "verification/invalid/Float8", + "verification/invalid/ForallAssoc", + "verification/invalid/HiddenOverride", + "verification/invalid/HOInvocations", + "verification/invalid/i497", + "verification/invalid/i1295", + "verification/invalid/InductTacticTest", + "verification/invalid/InsertionSort", + "verification/invalid/IntSet", + "verification/invalid/IntSetUnit", + "verification/invalid/InvisibleOpaque", + "verification/invalid/Issue1167", + "verification/invalid/Issue1167b", + "verification/invalid/LawsExampleInvalid", + "verification/invalid/ListOperations", + "verification/invalid/Lists", + "verification/invalid/Nested15", + "verification/invalid/PackedFloat8", + "verification/invalid/PartialSplit", + "verification/invalid/PositiveMap", + "verification/invalid/PositiveMap2", + "verification/invalid/PropositionalLogic", + "verification/invalid/RedBlackTree", + "verification/invalid/RedBlackTree2", + "verification/invalid/SimpleQuantification2", + "verification/invalid/SpecWithExtern", - // Tests that should be rejected, but aren't - testPosAll("verification/false-valid") -} - -class SMTZ3VerificationSuite extends VerificationSuite { - override def configurations = super.configurations.map { - seq => Seq( - inox.optSelectedSolvers(Set("smt-z3:z3-4.8.12")), - inox.solvers.optCheckModels(true) - ) ++ seq - } + // false-valid/ (for greater good...) + "verification/false-valid/ForestNothing2", + ) - override def filter(ctx: inox.Context, name: String): FilterStatus = name match { - // Flaky - case "verification/valid/PackedFloat8" => Ignore - case _ => super.filter(ctx, name) - } -} - -class CodeGenVerificationSuite extends SMTZ3VerificationSuite { - override def configurations = super.configurations.map { - seq => Seq( - inox.solvers.unrolling.optFeelingLucky(true), - inox.solvers.optCheckModels(true), - evaluators.optCodeGen(true) - ) ++ seq - } - - override def filter(ctx: inox.Context, name: String): FilterStatus = name match { + private val ignoreCodegen = Set( // Does not work with --feeling-lucky. See #490 - case "verification/valid/MsgQueue" => Skip - + "verification/valid/MsgQueue", // assertion failed in `compileLambda` - case "verification/valid/GodelNumbering" => Ignore + "verification/valid/GodelNumbering" + ) - case _ => super.filter(ctx, name) + override protected def optionsString(options: inox.Options): String = { + "solvr=" + options.findOptionOrDefault(inox.optSelectedSolvers).head + " " + + "lucky=" + options.findOptionOrDefault(inox.solvers.unrolling.optFeelingLucky) + " " + + "check=" + options.findOptionOrDefault(inox.solvers.optCheckModels) + " " + + "codegen=" + options.findOptionOrDefault(evaluators.optCodeGen) } -} -class SMTCVC4VerificationSuite extends VerificationSuite { - override def configurations = super.configurations.map { - seq => Seq( - inox.optSelectedSolvers(Set("smt-cvc4")), - inox.solvers.optCheckModels(true) - ) ++ seq + override def configurations: Seq[Seq[inox.OptionValue[_]]] = { + // All configurations for all possible solvers and codegen / recursive evaluators + // Note 1: For codegen, we only use Z3 + // Note 2: We opt-in for early counter-example discovery for codegen with the "feeling lucky" option + for { + solver <- solvers + codeGen <- Seq(false, true) + if !codeGen || solver == "smt-z3" + conf <- super.configurations.map { + seq => + Seq( + inox.optSelectedSolvers(Set(solver)), + inox.solvers.optCheckModels(true), + evaluators.optCodeGen(codeGen), + inox.solvers.unrolling.optFeelingLucky(codeGen)) ++ seq + } + } yield conf } - override def filter(ctx: inox.Context, name: String): FilterStatus = name match { - case "verification/valid/ArraySlice" => Ignore - case "verification/valid/BigIntMonoidLaws" => Ignore - case "verification/valid/BigIntRing" => Ignore - case "verification/valid/ConcRope" => Ignore - case "verification/valid/Huffman" => Ignore - case "verification/valid/InnerClasses4" => Ignore - case "verification/valid/Iterables" => Ignore - case "verification/valid/List" => Ignore - case "verification/valid/MoreExtendedEuclidGCD" => Ignore - case "verification/valid/MoreExtendedEuclidReachability" => Ignore - case "verification/valid/Overrides" => Ignore - case "verification/valid/PartialCompiler" => Ignore - case "verification/valid/PartialKVTrace" => Ignore - case "verification/valid/ReachabilityChecker" => Ignore - case "verification/valid/TestPartialFunction" => Ignore - case "verification/valid/TestPartialFunction3" => Ignore - - case _ => super.filter(ctx, name) + override def filter(ctx: inox.Context, name: String): FilterStatus = { + val solvers = ctx.options.findOptionOrDefault(inox.optSelectedSolvers) + assert(solvers.size == 1) + val ignoredSolver = solvers.head match { + case "smt-z3" => ignoreZ3(name) + case "smt-cvc4" | "smt-cvc5" => ignoreCVC(name) + case "princess" => ignorePrincess(name) + case other => fail(s"An unknown solver: $other") + } + val ignoredCodegen = ctx.options.findOptionOrDefault(evaluators.optCodeGen) && ignoreCodegen(name) + if (ignoredSolver || ignoredCodegen) Ignore else super.filter(ctx, name) } -} -class PrincessVerificationSuite extends VerificationSuite { - override def configurations = super.configurations.map { - seq => Seq( - inox.optSelectedSolvers(Set("princess")), - inox.solvers.optCheckModels(true) - ) ++ seq - } - - override def filter(ctx: inox.Context, name: String): FilterStatus = name match { - // valid/MicroTests - case "verification/valid/ADTWithArray1" => Ignore - case "verification/valid/ADTWithArray2" => Ignore - case "verification/valid/ADTWithArray4" => Ignore - case "verification/valid/ADTWithArray5" => Ignore - case "verification/valid/ADTWithArray6" => Ignore - case "verification/valid/Array1" => Ignore - case "verification/valid/Array2" => Ignore - case "verification/valid/Array3" => Ignore - case "verification/valid/Array4" => Ignore - case "verification/valid/ArrayUpdated" => Ignore - case "verification/valid/BitVectors2" => Ignore - case "verification/valid/BitVectors3" => Ignore - case "verification/valid/Issue803" => Ignore - case "verification/valid/Monads3" => Ignore - case "verification/valid/Nested13" => Ignore - case "verification/valid/SetIsEmpty" => Ignore - case "verification/valid/Sets1" => Ignore - case "verification/valid/Sets2" => Ignore - case "verification/valid/Subtyping1" => Ignore + import VerificationSuite._ - // valid/ - case "verification/valid/ArraySlice" => Ignore - case "verification/valid/AnonymousClasses1" => Ignore - case "verification/valid/AssociativeList" => Ignore - case "verification/valid/AmortizedQueue" => Ignore - case "verification/valid/BalancedParentheses" => Ignore - case "verification/valid/BasicReal" => Ignore - case "verification/valid/BinarySearch" => Ignore - case "verification/valid/BinarySearchTreeQuant" => Ignore - case "verification/valid/BinarySearchTreeQuant2" => Ignore - case "verification/valid/BitsTricks" => Ignore - case "verification/valid/BitsTricksSlow" => Ignore - case "verification/valid/BooleanOps" => Ignore - case "verification/valid/BVMaxInterpret" => Ignore - case "verification/valid/Composition" => Ignore - case "verification/valid/ConcRope" => Ignore - case "verification/valid/ConcTree" => Ignore - case "verification/valid/ContMonad" => Ignore - case "verification/valid/Count" => Ignore - case "verification/valid/CovCollection" => Ignore - case "verification/valid/Filter" => Ignore - case "verification/valid/FiniteSort" => Ignore - case "verification/valid/FlatMap" => Ignore - case "verification/valid/FoolProofAdder" => Ignore - case "verification/valid/FunctionMaps" => Ignore - case "verification/valid/FunctionMapsObj" => Ignore - case "verification/valid/GodelNumbering" => Ignore - case "verification/valid/Heaps" => Ignore - case "verification/valid/HOInvocations2" => Ignore - case "verification/valid/i1379a" => Ignore - case "verification/valid/i1379e" => Ignore - case "verification/valid/i1379f" => Ignore - case "verification/valid/i1379g" => Ignore - case "verification/valid/InnerClasses4" => Ignore - case "verification/valid/InnerClassesInvariants" => Ignore - case "verification/valid/InsertionSort" => Ignore - case "verification/valid/IntSetInv" => Ignore - case "verification/valid/Iterables" => Ignore - case "verification/valid/LawTypeArgsElim" => Ignore - case "verification/valid/ListMonad" => Ignore - case "verification/valid/ListOperations" => Ignore - case "verification/valid/LiteralMaps" => Ignore - case "verification/valid/Longs" => Ignore - case "verification/valid/MapDiff" => Ignore - case "verification/valid/MapGetOrElse2" => Ignore - case "verification/valid/MapGetPlus" => Ignore - case "verification/valid/MergeSort" => Ignore - case "verification/valid/MergeSort2" => Ignore - case "verification/valid/Monotonicity" => Ignore - case "verification/valid/NNF" => Ignore - case "verification/valid/PackedFloat8" => Ignore - case "verification/valid/ParBalance" => Ignore - case "verification/valid/PartialCompiler" => Ignore - case "verification/valid/PartialKVTrace" => Ignore - case "verification/valid/PositiveMap" => Ignore - case "verification/valid/PropositionalLogic" => Ignore - case "verification/valid/QuickSort" => Ignore - case "verification/valid/QuickSortFilter" => Ignore - case "verification/valid/RedBlackTree" => Ignore - case "verification/valid/Reverse" => Ignore - case "verification/valid/StableSorter" => Ignore - case "verification/valid/TransitiveQuantification" => Ignore - case "verification/valid/Trees1" => Ignore - case "verification/valid/ValueClasses" => Ignore + testPosAll("verification/valid", valid._1, valid._2) - // invalid/ - case "verification/invalid/AbstractRefinementMap" => Ignore - case "verification/invalid/Acc" => Ignore - case "verification/invalid/AddNaturals1" => Ignore - case "verification/invalid/AddNaturals2" => Ignore - case "verification/invalid/ADTWithArray1" => Ignore - case "verification/invalid/ADTWithArray2" => Ignore - case "verification/invalid/Array1" => Ignore - case "verification/invalid/Array2" => Ignore - case "verification/invalid/Array3" => Ignore - case "verification/invalid/Array4" => Ignore - case "verification/invalid/Array5" => Ignore - case "verification/invalid/Array6" => Ignore - case "verification/invalid/Array7" => Ignore - case "verification/invalid/Asserts1" => Ignore - case "verification/invalid/AssociativityProperties" => Ignore - case "verification/invalid/BadConcRope" => Ignore - case "verification/invalid/BigArray" => Ignore - case "verification/invalid/BinarySearch1" => Ignore - case "verification/invalid/BinarySearch2" => Ignore - case "verification/invalid/BinarySearch3" => Ignore - case "verification/invalid/BraunTree" => Ignore - case "verification/invalid/Choose1" => Ignore - case "verification/invalid/Choose2" => Ignore - case "verification/invalid/Existentials" => Ignore - case "verification/invalid/ExternFallbackMut" => Ignore - case "verification/invalid/FiniteSort" => Ignore - case "verification/invalid/Float8" => Ignore - case "verification/invalid/ForallAssoc" => Ignore - case "verification/invalid/HiddenOverride" => Ignore - case "verification/invalid/HOInvocations" => Ignore - case "verification/invalid/i497" => Ignore - case "verification/invalid/i1295" => Ignore - case "verification/invalid/InductTacticTest" => Ignore - case "verification/invalid/InsertionSort" => Ignore - case "verification/invalid/IntSet" => Ignore - case "verification/invalid/IntSetUnit" => Ignore - case "verification/invalid/InvisibleOpaque" => Ignore - case "verification/invalid/Issue1167" => Ignore - case "verification/invalid/Issue1167b" => Ignore - case "verification/invalid/LawsExampleInvalid" => Ignore - case "verification/invalid/ListOperations" => Ignore - case "verification/invalid/Lists" => Ignore - case "verification/invalid/Nested15" => Ignore - case "verification/invalid/PackedFloat8" => Ignore - case "verification/invalid/PartialSplit" => Ignore - case "verification/invalid/PositiveMap" => Ignore - case "verification/invalid/PositiveMap2" => Ignore - case "verification/invalid/PropositionalLogic" => Ignore - case "verification/invalid/RedBlackTree" => Ignore - case "verification/invalid/RedBlackTree2" => Ignore - case "verification/invalid/SimpleQuantification2" => Ignore - case "verification/invalid/SpecWithExtern" => Ignore + testNegAll("verification/invalid", invalid._1, invalid._2) - // false-valid/ (for greater good...) - case "verification/false-valid/ForestNothing2" => Ignore + // Tests that should be rejected, but aren't + testPosAll("verification/false-valid", falseValid._1, falseValid._2) +} +object VerificationSuite { + // Scala 2 BitVectors tests leverages the fact that `==` can be used to compare two unrelated types. + // For instance, if we have x: Int42, then x == 42 is legal. + // In Scala 3, however, this expression is ill-formed because Int42 and Int (the widened type of 42) are unrelated. + // Therefore, all BitVectors tests for Scala 3 must perform a conversion for literals + // (e.g. the above expression is rewritten to x == (42: Int42)) + private def bitVectorsTestDiscarding(file: String): Boolean = { + val scala2BitVectors = Set("MicroTests/scalac/BitVectors1.scala", "MicroTests/scalac/BitVectors2.scala", "MicroTests/scalac/BitVectors3.scala") + val scala3BitVectors = Set("MicroTests/dotty/BitVectors1.scala", "MicroTests/dotty/BitVectors2.scala", "MicroTests/dotty/BitVectors3.scala") - case _ => super.filter(ctx, name) + (ComponentTestSuite.isScala3 || !scala3BitVectors.exists(t => file.endsWith(t))) && + (ComponentTestSuite.isScala2 || !scala2BitVectors.exists(t => file.endsWith(t))) } -} + + private lazy val valid = ComponentTestSuite.loadPrograms("verification/valid", recursive = true, keepOnly = bitVectorsTestDiscarding) + private lazy val invalid = ComponentTestSuite.loadPrograms("verification/invalid") + private lazy val falseValid = ComponentTestSuite.loadPrograms("verification/false-valid") +} \ No newline at end of file diff --git a/frontends/dotty/src/it/scala/stainless/verification/DottyVerificationSuite.scala b/frontends/dotty/src/it/scala/stainless/verification/DottyVerificationSuite.scala index 69e62cc86a..98656eb5d5 100644 --- a/frontends/dotty/src/it/scala/stainless/verification/DottyVerificationSuite.scala +++ b/frontends/dotty/src/it/scala/stainless/verification/DottyVerificationSuite.scala @@ -5,18 +5,18 @@ package verification import org.scalatest._ -trait DottyVerificationSuite extends VerificationComponentTestSuite { +class DottyVerificationSuite extends VerificationComponentTestSuite { - override def configurations = super.configurations.map { - seq => optFailInvalid(true) +: seq - } + override protected def optionsString(options: inox.Options): String = "" - override protected def optionsString(options: inox.Options): String = { - super.optionsString(options) + - (if (options.findOptionOrDefault(evaluators.optCodeGen)) " codegen" else "") - } + import DottyVerificationSuite._ + + testPosAll("dotty-specific/valid", valid._1, valid._2) - def keepOnly(f: String): Boolean = { + testNegAll("dotty-specific/invalid", invalid._1, invalid._2) +} +object DottyVerificationSuite { + private def keepOnly(f: String): Boolean = { val noLongerCompiles = Set( "ConstructorRefinement.scala", "IdentityRefinement.scala", @@ -28,17 +28,6 @@ trait DottyVerificationSuite extends VerificationComponentTestSuite { ) noLongerCompiles.forall(s => !f.endsWith(s)) } - - testPosAll("dotty-specific/valid", keepOnly = keepOnly) - - testNegAll("dotty-specific/invalid") -} - -class SMTZ3DottyVerificationSuite extends DottyVerificationSuite { - override def configurations = super.configurations.map { - seq => Seq( - inox.optSelectedSolvers(Set("smt-z3:z3-4.8.12")), - inox.solvers.optCheckModels(true) - ) ++ seq - } -} + private lazy val valid = ComponentTestSuite.loadPrograms("dotty-specific/valid", keepOnly = keepOnly) + private lazy val invalid = ComponentTestSuite.loadPrograms("dotty-specific/invalid") +} \ No newline at end of file diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/ASTExtractors.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/ASTExtractors.scala index d57f8b8881..a0b5872b5e 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/ASTExtractors.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/ASTExtractors.scala @@ -3,7 +3,6 @@ package frontends.dotc import scala.language.implicitConversions import dotty.tools.dotc._ -import typer.Inliner import ast.tpd import ast.Trees._ import core.Contexts.{NoContext, Context => DottyContext} @@ -22,7 +21,7 @@ import scala.collection.mutable.{Map => MutableMap} trait ASTExtractors { val dottyCtx: DottyContext - import dottyCtx.given + given DottyContext = dottyCtx def classFromName(nameStr: String): ClassSymbol = requiredClass(typeName(nameStr)) def moduleFromName(nameStr: String): TermSymbol = requiredModule(typeName(nameStr)) @@ -41,20 +40,42 @@ trait ASTExtractors { defn.AnyValType, ) + // Annotations that are propagated to symbols owned by an owner containing these. + // Note: we do not necessarily want @opaque/@inlineOnce function to have their inner functions + // automatically annotated with @opaque/@inlineOnce, we therefore leave them out + private val propagatedAnnotations: Set[String] = Set( + "stainless.annotation.ignore", + "stainless.annotation.library", + "stainless.annotation.extern", + "stainless.annotation.dropVCs", + "stainless.annotation.pure", + "stainless.annotation.wrapping", + "stainless.annotation.keep", + "stainless.annotation.keepFor", + "stainless.annotation.cCode.drop" + ) + private val ghostAnnot: String = "stainless.annotation.ghost" + def isIgnored(tp: Type): Boolean = ignoredClasses.exists(_ frozen_=:= tp) def getAnnotations(sym: Symbol, ignoreOwner: Boolean = false): Seq[(String, Seq[tpd.Tree])] = { if (sym eq NoSymbol) return Seq.empty - val erased = if (sym.isEffectivelyErased) Seq(("ghost", Seq.empty[tpd.Tree])) else Seq() + val erased = if (sym.isEffectivelyErased && !(sym is Inline)) Seq(("ghost", Seq.empty[tpd.Tree])) else Seq() val selfs = sym.annotations val owners = if (ignoreOwner) List.empty[Annotation] - else sym.owner.annotations.filter(annot => - annot.toString != "stainless.annotation.export" && - !annot.toString.startsWith("stainless.annotation.cCode.global") - ) + else sym.ownersIterator.drop(1) // drop(1) to skip `sym` itself + .zipWithIndex + .flatMap { case (owner, ix) => + owner.annotations.filter { annot => + val annotNme = annot.symbol.fullName.toString + // Keep this annotation if is either an annotation to propagate (`propagatedAnnotations`) + // or if it's a @ghost that applies to a method whose direct owner is a class (ix == 0 and owner.isClass). + propagatedAnnotations(annotNme) || (annotNme == ghostAnnot && ix == 0 && (sym is Method) && owner.isClass) + } + }.toList val companions = List(sym.denot.companionModule).filter(_ ne NoSymbol).flatMap(_.annotations) erased ++ (for { a <- selfs ++ owners ++ companions @@ -103,6 +124,7 @@ trait ASTExtractors { protected lazy val mutableMapSym = classFromName("stainless.lang.MutableMap") protected lazy val bagSym = classFromName("stainless.lang.Bag") protected lazy val realSym = classFromName("stainless.lang.Real") + protected lazy val cellSym = classFromName("stainless.lang.Cell") protected lazy val bvSym = classFromName("stainless.math.BitVectors.BV") @@ -199,6 +221,10 @@ trait ASTExtractors { getResolvedTypeSym(sym) == realSym } + def isCellSym(sym: Symbol): Boolean = { + getResolvedTypeSym(sym) == cellSym + } + def isScalaSetSym(sym: Symbol) : Boolean = { getResolvedTypeSym(sym) == scalaSetSym } @@ -775,10 +801,34 @@ trait ASTExtractors { canExtractSynthetic(dd.symbol) && !(getAnnotations(tpt.symbol) exists (_._1 == "ignore")) )) => - Some((dd.symbol, dd.leadingTypeParams, dd.termParamss.flatten, tpt.tpe, dd.rhs)) + Some((dd.symbol, allTypeParams(dd), dd.termParamss.flatten, tpt.tpe, dd.rhs)) case _ => None } + + // Get all type parameters of a DefDef. Note that dd.leadingTypeParams will only retrieve the leading ones + // which is insufficient for parametric extension methods since these have type parameters in the "middle" of `paramss`. + // For instance, for the following extension method: + // extension[T](m: Option[T]) + // def map[U](f: U => T): Option[U] = ... + // `paramss` will be as follows: + // List( + // List(TypeDef(T)), + // List(ValDef(m)), + // List(TypeDef(U)), + // List(ValDef(f)), + // ) + // and `d.leadingTypeParams` will only get `T` and miss `U`. + private def allTypeParams(dd: tpd.DefDef): Seq[tpd.TypeDef] = { + def go(paramss: List[tpd.ParamClause], acc: List[tpd.TypeDef]): List[tpd.TypeDef] = { + paramss match { + case Nil => acc + case (tparams@(tparam: tpd.TypeDef) :: _) :: rest => go(rest, acc ++ tparams.asInstanceOf[List[tpd.TypeDef]]) + case _ :: rest => go(rest, acc) + } + } + go(dd.paramss, Nil) + } } /** @@ -1402,6 +1452,23 @@ trait ASTExtractors { } } + object ExCellSwapExpression { + def unapply( + tree: tpd.Apply + ): Option[(tpd.Tree, tpd.Tree)] = + tree match { + case Apply( + TypeApply( + ExSymbol("stainless", "lang", "package$", "swap"), + _ + ), + cell1 :: cell2 :: Nil + ) => + Some((cell1, cell2)) + case _ => None + } + } + object ExForallExpression { def unapply(tree: tpd.Apply) : Option[tpd.Tree] = tree match { case Apply(TypeApply(ExSymbol("stainless", "lang", "package$", "forall"), _), List(fun)) => @@ -1628,4 +1695,4 @@ trait ASTExtractors { case _ => None } } -} +} \ No newline at end of file diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/CodeExtraction.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/CodeExtraction.scala index afc34a7cc6..420c19f1c0 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/CodeExtraction.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/CodeExtraction.scala @@ -15,16 +15,22 @@ import core.StdNames._ import core.Symbols._ import core.Types._ import core.Flags._ +import core.Constants._ import core.NameKinds +import core.NameOps._ import util.{NoSourcePosition, SourcePosition} import stainless.ast.SymbolIdentifier import extraction.xlang.{trees => xt} +import Utils._ import scala.collection.mutable.{Map => MutableMap} import scala.collection.immutable.ListMap import scala.language.implicitConversions -class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using override val dottyCtx: DottyContext) +class CodeExtraction(inoxCtx: inox.Context, + symbolMapping: SymbolMapping, + exportedSymsMapping: ExportedSymbolsMapping) + (using override val dottyCtx: DottyContext) extends ASTExtractors { import AuxiliaryExtractors._ @@ -90,7 +96,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using def isVariable(s: Symbol) = (vars contains s) || (mutableVars contains s) - def withNewTypeParams(ntparams: Traversable[(Symbol, xt.TypeParameter)]) = { + def withNewTypeParams(ntparams: Iterable[(Symbol, xt.TypeParameter)]) = { copy(tparams = tparams ++ ntparams) } @@ -98,7 +104,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using copy(tparams = tparams + tparam) } - def withNewVars(nvars: Traversable[(Symbol, () => xt.Expr)]) = { + def withNewVars(nvars: Iterable[(Symbol, () => xt.Expr)]) = { copy(vars = vars ++ nvars) } @@ -110,7 +116,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using copy(mutableVars = mutableVars + nvar) } - def withNewMutableVars(nvars: Traversable[(Symbol, () => xt.Variable)]) = { + def withNewMutableVars(nvars: Iterable[(Symbol, () => xt.Variable)]) = { copy(mutableVars = mutableVars ++ nvars) } @@ -122,7 +128,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using copy(localClasses = this.localClasses + (lcd.id -> lcd)) } - def withDepParams(dps: Traversable[(TermName, xt.ValDef)]) = { + def withDepParams(dps: Iterable[(TermName, xt.ValDef)]) = { copy(depParams = depParams ++ dps) } @@ -373,6 +379,9 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case t if (t.symbol is Synthetic) && !canExtractSynthetic(t.symbol) => // ignore + case ExFunctionDef(fsym, _, _, _, _) if fsym is Exported => + // ignore + // Normal function case dd @ ExFunctionDef(fsym, tparams, vparams, tpt, rhs) => val fd0 = extractFunction(fsym, dd, tparams, vparams, rhs) @@ -394,6 +403,9 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case t @ ExNonCtorMutableFieldDef(_, _, _) => outOfSubsetError(t, "Mutable fields in static containers such as objects are not supported") + case Export(_, _) => + // ignore + case other => reporter.warning(other.sourcePos, s"Stainless does not support the following tree in static containers:\n$other") } @@ -428,11 +440,8 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using (Seq.empty, extractType(tpt), false) } - // Opaque types are referenced from their opaque right-hand side for some reason. - val realId = if (td.rhs.symbol is Opaque) getIdentifier(td.rhs.symbol) else id - new xt.TypeDef( - realId, + id, tparams.map(xt.TypeParameterDef(_)), body, flags ++ (if (isAbstract) Seq(xt.IsAbstract) else Seq.empty) @@ -555,6 +564,9 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using if hasExternFields && (isCopyMethod(fsym) || isDefaultGetter(fsym)) => () // ignore + case ExFunctionDef(fsym, _, _, _, _) if fsym is Exported => + // ignore + // Normal methods case dd @ ExFunctionDef(fsym, tparams, vparams, _, rhs) => methods :+= extractFunction(fsym, dd, tparams, vparams, rhs)(using tpCtx) @@ -573,6 +585,9 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case d if d.symbol is Synthetic => // ignore + case Export(_, _) => + // ignore + case other => reporter.warning(other.sourcePos, s"In class $id, Stainless does not support:\n$other") } @@ -723,7 +738,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using (if ((sym is Implicit) && (sym is Synthetic)) Seq(xt.Inline, xt.Synthetic) else Seq()) ++ (if (sym is Inline) Seq(xt.Inline) else Seq()) ++ (if (sym is Private) Seq(xt.Private) else Seq()) ++ - (if (sym is Final) Seq(xt.Final) else Seq()) ++ + (if (sym.isEffectivelyFinal) Seq(xt.Final) else Seq()) ++ (if (isDefaultGetter(sym) || isCopyMethod(sym)) Seq(xt.Synthetic, xt.Inline) else Seq()) if (sym.name == nme.unapply) { @@ -791,7 +806,6 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using .copy(isExtern = dctx.isExtern || (flags contains xt.Extern)) lazy val retType = extractType(tree.tpt)(using nctx) - val (finalBody, returnType) = if (isAbstract) { (xt.NoTree(retType).setPos(sym.sourcePos), retType) } else { @@ -896,24 +910,32 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using }._2 } - private def extractPattern(p: tpd.Tree, binder: Option[xt.ValDef] = None)(using dctx: DefContext): (xt.Pattern, DefContext) = p match { + // Note: `expectedTpe` is used to check for redundant type checks that can appear in some patterns. + // For instance, the following expression (assuming a: A and b: B) is a valid pattern: + // val (aa: A, bb: B) = (a, b) + // When we recursively traverse the pattern aa: A and bb: B, we set `expectedTpe` to be `A` and `B` respectively + // since these type tests are redundant. If we do not do so, we would be falling into an "Unsupported pattern" error. + // Note that this pattern will be correctly rejected as "Unsupported pattern" (in fact, it cannot be even tested at runtime): + // val (aa: B, bb: B) = (a, b) + private def extractPattern(p: tpd.Tree, expectedTpe: Option[xt.Type], binder: Option[xt.ValDef] = None)(using dctx: DefContext): (xt.Pattern, DefContext) = p match { case b @ Bind(name, t @ Typed(pat, tpt)) => val vd = xt.ValDef(FreshIdentifier(name.toString), extractType(tpt), annotationsOf(b.symbol, ignoreOwner = true)).setPos(b.sourcePos) val pctx = dctx.withNewVar(b.symbol -> (() => vd.toVariable)) - extractPattern(t, Some(vd))(using pctx) + extractPattern(t, expectedTpe, Some(vd))(using pctx) case b @ Bind(name, pat) => val vd = xt.ValDef(FreshIdentifier(name.toString), extractType(b), annotationsOf(b.symbol, ignoreOwner = true)).setPos(b.sourcePos) val pctx = dctx.withNewVar(b.symbol -> (() => vd.toVariable)) - extractPattern(pat, Some(vd))(using pctx) + extractPattern(pat, expectedTpe, Some(vd))(using pctx) case t @ Typed(Ident(nme.WILDCARD), tpt) => extractType(tpt)(using dctx.setResolveTypes(true)) match { case ct: xt.ClassType => (xt.InstanceOfPattern(binder, ct).setPos(p.sourcePos), dctx) - + case lt if expectedTpe.contains(lt) => + (xt.WildcardPattern(binder), dctx) case lt => - outOfSubsetError(tpt, "Invalid type "+tpt.tpe+" for .isInstanceOf") + outOfSubsetError(p, s"Unsupported pattern: ${p.show}") } case Ident(nme.WILDCARD) => @@ -924,7 +946,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case ct: xt.ClassType => (xt.ClassPattern(binder, ct, Seq()).setPos(p.sourcePos), dctx) case _ => - outOfSubsetError(s, "Invalid instance pattern: "+s) + outOfSubsetError(p, s"Unsupported pattern: ${p.show}") } case id @ Ident(_) if id.symbol isOneOf (Case | Module) => @@ -932,23 +954,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case ct: xt.ClassType => (xt.ClassPattern(binder, ct, Seq()).setPos(p.sourcePos), dctx) case _ => - outOfSubsetError(id, "Invalid instance pattern: "+id) - } - - case a @ Apply(fn, args) => - extractType(a)(using dctx.setResolveTypes(true)) match { - case ct: xt.ClassType => - val (subPatterns, subDctx) = args.map(extractPattern(_)).unzip - val nctx = subDctx.foldLeft(dctx)(_ union _) - (xt.ClassPattern(binder, ct, subPatterns).setPos(p.sourcePos), nctx) - - case xt.TupleType(argsTpes) => - val (subPatterns, subDctx) = args.map(extractPattern(_)).unzip - val nctx = subDctx.foldLeft(dctx)(_ union _) - (xt.TuplePattern(binder, subPatterns).setPos(p.sourcePos), nctx) - - case _ => - outOfSubsetError(a, "Invalid type "+a.tpe+" for .isInstanceOf") + outOfSubsetError(p, s"Unsupported pattern: ${p.show}") } case ExBigIntPattern(l) => @@ -963,8 +969,10 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case ExUnitLiteral() => (xt.LiteralPattern(binder, xt.UnitLiteral()), dctx) case ExStringLiteral(s) => (xt.LiteralPattern(binder, xt.StringLiteral(s)), dctx) - case t @ Typed(UnApply(f, _, pats), tp) => - val (subPatterns, subDctx) = pats.map(extractPattern(_)).unzip + case t @ Typed(un@UnApply(f, _, pats), tp) => + val subPatTps = resolveUnapplySubPatternsTps(un) + assert(subPatTps.size == pats.size) + val (subPatterns, subDctx) = pats.zip(subPatTps).map { case (pat, tp) => extractPattern(pat, Some(tp)) }.unzip val nctx = subDctx.foldLeft(dctx)(_ union _) val sym = f.symbol @@ -978,8 +986,10 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using (xt.UnapplyPattern(binder, Seq(), id, tps, subPatterns).setPos(t.sourcePos), nctx) } - case UnApply(f, _, pats) => - val (subPatterns, subDctx) = pats.map(extractPattern(_)).unzip + case un@UnApply(f, _, pats) => + val subPatTps = resolveUnapplySubPatternsTps(un) + assert(subPatTps.size == pats.size) + val (subPatterns, subDctx) = pats.zip(subPatTps).map { case (pat, tp) => extractPattern(pat, Some(tp)) }.unzip val nctx = subDctx.foldLeft(dctx)(_ union _) val sym = f.symbol @@ -997,11 +1007,58 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using } case _ => - outOfSubsetError(p, "Unsupported pattern: "+p) + outOfSubsetError(p, s"Unsupported pattern: ${p.show}") + } + + private def resolveUnapplySubPatternsTps(un: tpd.UnApply)(using dctx: DefContext): Seq[xt.Type] = { + def classFieldsAccessors(tpe: Type): Seq[Type] = { + // We only keep the fields of the constructor (and disregard the inherited ones) + val fields = tpe.fields.filter { denot => + val sym = denot.symbol + !sym.is(Accessor) && (sym.is(ParamAccessor) || sym.is(CaseAccessor)) + } + fields.map(_.info) + } + def resolve(resTpe: Type): Seq[xt.Type] = { + val subPatTps = resTpe match { + // The return type is Option[T] where T may be a tuple - in which case we flatten it + case AppliedType(opt, List(underlying)) if opt.typeSymbol == optionClassSym || opt.typeSymbol == optionSymbol => + underlying match { + case at@AppliedType(tr: TypeRef, tps) if TupleSymbol.unapply(tr.classSymbol).isDefined => + val AppliedType(_, theTps) = at.dealias: @unchecked + theTps + case _ => Seq(underlying) + } + // The following two cases are for patterns that do not "return" any value such as None + case ConstantType(Constant(true)) => Seq.empty + case _ if resTpe.typeSymbol == defn.BooleanClass => Seq.empty + // The following two cases are for ADT patterns such as Left/Right .unapply which are typically compiler-generated + case at@AppliedType(tt@TypeRef(_, _), args) if tt.symbol.isClass => + classFieldsAccessors(at) + case tt@TypeRef(_, _) if tt.symbol.isClass => + classFieldsAccessors(tt) + case _ => + outOfSubsetError(un, s"Unsupported pattern: ${un.show}") + } + subPatTps.map(extractType(_)(using dctx, un.sourcePos)) + } + un.fun.tpe match { + case mt: MethodType => resolve(mt.resultType) + case tr: TermRef => + // If we have a TermRef, this `unapply` method does not take type parameter. + // We can unveil its underlying type with `info` (and cry if we get something else...) + tr.info match { + case mt: MethodType => resolve(mt.resultType) + case _ => + outOfSubsetError(un, s"Unsupported pattern: ${un.show}") + } + case _ => + outOfSubsetError(un, s"Unsupported pattern: ${un.show}") + } } private def extractMatchCase(cd: tpd.CaseDef)(using dctx: DefContext): xt.MatchCase = { - val (recPattern, ndctx) = extractPattern(cd.pat) + val (recPattern, ndctx) = extractPattern(cd.pat, None) val recBody = extractTree(cd.body)(using ndctx) if (cd.guard == tpd.EmptyTree) { @@ -1205,6 +1262,61 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using private def extractTree(tr: tpd.Tree)(using dctx: DefContext): xt.Expr = (tr match { case SingletonTypeTree(tree) => extractTree(tree) + case ExExportedSymbol(path, recv0, tps, args) => + def ownerType(sym: Symbol): xt.ClassType | xt.LocalClassType = { + stripAnnotationsExceptStrictBV(extractType(sym.owner.typeRef)(using dctx.setResolveTypes(true), tr.sourcePos)) match { + case ct: (xt.ClassType | xt.LocalClassType) => ct + case _ => outOfSubsetError(tr, s"Stainless does not support use of exported symbol in this context:\n${tr.show}") + } + } + def mkSelection(recv: xt.Expr, sym: Symbol): xt.Expr = { + // Selection across exported symbol that works whether the class + // is abstract (method invocation) or concrete (field selection). + // Inspired by `extractCall` + val ct = ownerType(sym) + val isCtorField = (sym is CaseAccessor) || (sym is ParamAccessor) + val isNonCtorField = sym.isField && !isCtorField + assert(isCtorField || isNonCtorField) + if (isCtorField) { + // Class is concrete, so this is a simple field selection + classSelector(ct, recv, getIdentifier(sym)).setPos(tr.sourcePos) + } else { + // Class is abstract, so we must issue a method call *using* `getFieldAccessorIdentifier` + methodInvocation(ct, recv, getFieldAccessorIdentifier(sym), Seq.empty, Seq.empty).setPos(tr.sourcePos) + } + } + + val last = path.last + val isCtorField = (last is CaseAccessor) || (last is ParamAccessor) + val isNonCtorField = last.isField && !isCtorField + val recRecv0 = extractTree(recv0) + if (isCtorField) { + // Class is concrete, however assignment of fields is done through the setter (e.g. `myField_=`), + // therefore, we must use the underlying symbol in such case. + assert(tps.isEmpty && args.size <= 1) + val isSetter = last.name.isSetterName + val newPath = { + if (isSetter) path.init + else path.init :+ last.underlyingSymbol + } + val recv = newPath.foldLeft(recRecv0)(mkSelection) + if (isSetter) xt.FieldAssignment(recv, getIdentifier(last.underlyingSymbol), extractTree(args.head)).setPos(tr.sourcePos) + else recv + } else { + // Either a normal method, an abstract class field selection or an abstract class field assignment. + // The latter two are distinguished by `isNonCtorField` being true. + // If so, we use `getFieldAccessorIdentifier` to get the right Stainless symbol. + val ct = ownerType(last) + val recv = path.init.foldLeft(recRecv0)(mkSelection) + val iden = { + if (isNonCtorField) getFieldAccessorIdentifier(last) + else getIdentifier(last) + } + val recArgs = extractArgs(last, args) + val recTps = tps.map(extractType) + methodInvocation(ct, recv, iden, recTps, recArgs).setPos(tr.sourcePos) + } + case ExLambda(vparams, rhs) => val vds = vparams map (vd => xt.ValDef( FreshIdentifier(vd.symbol.name.toString), @@ -1530,6 +1642,8 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case ExSwapExpression(array1, index1, array2, index2) => xt.Swap(extractTree(array1), extractTree(index1), extractTree(array2), extractTree(index2)) + case ExCellSwapExpression(cell1, cell2) => xt.CellSwap(extractTree(cell1), extractTree(cell2)) + case ExForallExpression(fun) => extractTree(fun) match { case l: xt.Lambda => xt.Forall(l.params, l.body).setPos(l) @@ -1576,10 +1690,19 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case e => (xt.TupleSelect(e, 1).setPos(e), xt.TupleSelect(e, 2).setPos(e)) }, extractType(tpt)) - case ExClassConstruction(tpe, args) => extractType(tpe)(using dctx, tr.sourcePos) match { + case ExClassConstruction(tpe, args) => + extractType(tpe)(using dctx, tr.sourcePos) match { case lct: xt.LocalClassType => xt.LocalClassConstructor(lct, args map extractTree) case ct: xt.ClassType => xt.ClassConstructor(ct, args map extractTree) case tt: xt.TupleType => xt.Tuple(args map extractTree) + case at: xt.ArrayType if args.size == 1 && extractType(args.head.tpe)(using dctx, tr.sourcePos) == xt.Int32Type() => + mkZeroForPrimitive(at.base) match { + case Some(zero) => + val recArg = extractTree(args.head) + xt.LargeArray(Map.empty, zero, recArg, at.base) + case None => + outOfSubsetError(tr, s"Cannot use array constructor for non-primitive type ${at.base}\nHint: you may use `Array.fill` instead") + } case _ => outOfSubsetError(tr, "Unexpected constructor " + tr.show + " " + tpe.show) } @@ -2268,7 +2391,7 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using case AppliedType(tr: TypeRef, Seq(tp)) if isArrayClassSym(tr.symbol) => xt.ArrayType(extractType(tp)) - case fo @ defn.FunctionOf(from, to, _, _) => + case fo @ defn.FunctionOf(from, to, _) => xt.FunctionType(from map extractType, extractType(to)) case tr @ TypeRef(_, _) if dctx.tparams contains tr.symbol => @@ -2427,6 +2550,13 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using } } + private def mkZeroForPrimitive(tp: xt.Type): Option[xt.Expr] = tp match { + case xt.BooleanType() => Some(xt.BooleanLiteral(false)) + case xt.BVType(signed, size) => Some(xt.BVLiteral(signed, 0, size)) + case xt.CharType() => Some(xt.CharLiteral(0.toChar)) + case _ => None + } + // @extern function may contain constructs that are not supported by Stainless. // However, we must be sure that we have captured all contracts. // For instance, the following function uses the `.toString` method that we do not support: @@ -2471,4 +2601,18 @@ class CodeExtraction(inoxCtx: inox.Context, symbolMapping: SymbolMapping)(using } traverser.traverse(tree) } + + object ExExportedSymbol { + def unapply(tr: tpd.Tree): Option[(Seq[Symbol], tpd.Tree, Seq[tpd.Tree], Seq[tpd.Tree])] = { + val sym = tr.symbol + exportedSymsMapping.get(sym) match { + case Some(path) => + tr match { + case ExCall(Some(recv), _, tps, args) => Some((path, recv, tps, args)) + case _ => outOfSubsetError(tr, s"Stainless does not support use of exported symbol in this context:\n${tr.show}") + } + case None => None + } + } + } } diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/DottyCompiler.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/DottyCompiler.scala index 31a12b80a5..0bf52ed603 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/DottyCompiler.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/DottyCompiler.scala @@ -14,6 +14,7 @@ import core.Phases._ import transform._ import typer._ import frontend.{CallBack, Frontend, FrontendFactory, ThreadedFrontend} +import Utils._ import java.io.File import java.net.URL @@ -40,11 +41,17 @@ class DottyCompiler(ctx: inox.Context, callback: CallBack) extends Compiler { // Note: this must not be instantiated within `run`, because we need the underlying `symbolMapping` in `StainlessExtraction` // to be shared across multiple compilation unit. private val extraction = new StainlessExtraction(ctx) + private var exportedSymsMapping: ExportedSymbolsMapping = ExportedSymbolsMapping.empty // This method id called for every compilation unit, and in the same thread. override def run(using dottyCtx: DottyContext): Unit = - extraction.extractUnit.foreach(extracted => + extraction.extractUnit(exportedSymsMapping).foreach(extracted => callback(extracted.file, extracted.unit, extracted.classes, extracted.functions, extracted.typeDefs)) + + override def runOn(units: List[CompilationUnit])(using dottyCtx: DottyContext): List[CompilationUnit] = { + exportedSymsMapping = exportedSymbolsMapping(ctx, this.start, units) + super.runOn(units) + } } // Pick all phases until `including` (with its group included) @@ -110,7 +117,8 @@ private class SimpleReporter(val reporter: inox.Reporter) extends DottyReporter "on a value with an unknown initialization", "may cause initialization errors", "Promoting the value to fully-initialized is unsafe", - "Cannot prove the argument is fully initialized") + "Cannot prove the argument is fully initialized", + "Cannot prove the method argument is hot") dia.level == WARNING && msgs.exists(dia.message.contains) } diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/FragmentChecker.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/FragmentChecker.scala index bd8500f303..149f11a764 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/FragmentChecker.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/FragmentChecker.scala @@ -272,7 +272,7 @@ class FragmentChecker(inoxCtx: inox.Context)(using override val dottyCtx: DottyC class Checker extends tpd.TreeTraverser { private val ScalaEnsuringMethod = requiredMethod("scala.Predef.Ensuring") - private val StainlessLangPackage = getPackageIfDefinedOrNone("stainless.lang") + private val StainlessLangPackage = getClassIfDefinedOrNone("stainless.lang.package$") private val ExternAnnotation = getClassIfDefinedOrNone("stainless.annotation.extern") private val IgnoreAnnotation = getClassIfDefinedOrNone("stainless.annotation.ignore") private val StainlessOld = StainlessLangPackage.map(_.info.decl(Names.termName("old")).symbol) @@ -348,8 +348,22 @@ class FragmentChecker(inoxCtx: inox.Context)(using override val dottyCtx: DottyC // We do not traverse SuperType as it contains all hierarchy, // including traits such as Product, etc. we do not support. return acc + + case ot: OrType => + // The extraction replace OrType with their join, so we do the same here to catch potential "Matchable" types + return apply(acc, ot.join) + + // The extraction explicitly ignore Product and Serializable when they appear in AndType, we do the same here + case AndType(tp, prod) if prod.typeSymbol == defn.ProductClass => return apply(acc, tp) + case AndType(prod, tp) if prod.typeSymbol == defn.ProductClass => return apply(acc, tp) + case AndType(tp, prod) if defn.isProductClass(prod.typeSymbol) => return apply(acc, tp) + case AndType(prod, tp) if defn.isProductClass(prod.typeSymbol) => return apply(acc, tp) + case AndType(tp, ser) if ser.typeSymbol == defn.SerializableClass => return apply(acc, tp) + case AndType(ser, tp) if ser.typeSymbol == defn.SerializableClass => return apply(acc, tp) + case _ => () } + val newAcc = { val tpSym = tp.typeSymbol if (stainlessReplacement.contains(tpSym)) @@ -367,33 +381,53 @@ class FragmentChecker(inoxCtx: inox.Context)(using override val dottyCtx: DottyC reportError(tree.sourcePos, s"Scala API `${sym.name.show}` is not directly supported, please use `$replacement` instead.") } - def contains(hay: Type, needle: Type): Boolean = { + // Returns true if `needle` is in `hay` where the OrType in `needle` are replaced with their join, and return the transformed `needle` as well + def contains(hay: Type, needle: Type): (Boolean, Type) = { var found = false - val tr = new TypeTraverser() { - override def traverse(tp: Type): Unit = { - if (tp eq needle) found = true - else traverseChildren(tp) + val tm = new TypeMap() { + override def apply(tp: Type): Type = { + if (tp eq needle) { + found = true + tp + } else { + tp match { + case ot: OrType => apply(ot.join) + case AndType(tp, prod) if prod.typeSymbol == defn.ProductClass => apply(tp) + case AndType(prod, tp) if prod.typeSymbol == defn.ProductClass => apply(tp) + case AndType(tp, prod) if defn.isProductClass(prod.typeSymbol) => apply(tp) + case AndType(prod, tp) if defn.isProductClass(prod.typeSymbol) => apply(tp) + case AndType(tp, ser) if ser.typeSymbol == defn.SerializableClass => apply(tp) + case AndType(ser, tp) if ser.typeSymbol == defn.SerializableClass => apply(tp) + case _ => mapOver(tp) + } + } } } - tr.traverse(hay) - found + val widened = tm(hay) + (found, widened) } def sourceOfType(tree: tpd.Tree, tp: Type): Unit = { tree match { case dd: tpd.DefDef => dd.tpt match { - case inf: tpd.InferredTypeTree if contains(inf.tpe, tp) => - reportError(inf.sourcePos, s"Hint: the inferred return type of ${dd.name.show} is `${inf.tpe.show}`") - sourceOfType(dd.rhs, tp) + case inf: tpd.InferredTypeTree => + val (contained, widened) = contains(inf.tpe, tp) + if (contained) { + reportError(inf.sourcePos, s"Hint: the inferred return type of ${dd.name.show} is `${inf.tpe.show}`, and is widened to ${widened.show}") + sourceOfType(dd.rhs, tp) + } case _ => () } case bl: tpd.Block => sourceOfType(bl.expr, tp) case vd: tpd.ValDef => vd.tpt match { - case inf: tpd.InferredTypeTree if contains(inf.tpe, tp) => - reportError(inf.sourcePos, s"Hint: the inferred type of ${vd.name.show} is `${inf.tpe.show}`") - sourceOfType(vd.rhs, tp) + case inf: tpd.InferredTypeTree => + val (contained, widened) = contains(inf.tpe, tp) + if (contained) { + reportError(inf.sourcePos, s"Hint: the inferred type of ${vd.name.show} is `${inf.tpe.show}`, and is widened to ${widened.show}") + sourceOfType(vd.rhs, tp) + } case _ => () } case ite: tpd.If => @@ -403,18 +437,18 @@ class FragmentChecker(inoxCtx: inox.Context)(using override val dottyCtx: DottyC case t => Seq(t) }) } - val iteTpWiden = ite.tpe.widenUnion - if (contains(iteTpWiden, tp)) { - reportError(ite.sourcePos, s"Hint: the widened type of this if expression is `${iteTpWiden.show}`") + val (contained, widened) = contains(ite.tpe, tp) + if (contained) { + reportError(ite.sourcePos, s"Hint: the type of this if expression is ${ite.tpe.show} and is widened to `${widened.show}`") val brchs = branches(ite) for (branch <- brchs) { reportError(branch.sourcePos, s"Hint: this branch type is `${branch.tpe.widenTermRefExpr.widenSingleton.show}`") } } case mtch: tpd.Match => - val mtchTpWiden = mtch.tpe.widenUnion - if (contains(mtchTpWiden, tp)) { - reportError(mtch.sourcePos, s"Hint: the widened type of this match expression is `${mtchTpWiden.show}`") + val (contained, widened) = contains(mtch.tpe, tp) + if (contained) { + reportError(mtch.sourcePos, s"Hint: the type of this match expression is ${mtch.tpe.show} and is widened to `${widened.show}`") for (branch <- mtch.cases) { reportError(branch.sourcePos, s"Hint: this case type is `${branch.tpe.widenTermRefExpr.widenSingleton.show}`") } diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/GhostAccessRewriter.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/GhostAccessRewriter.scala index 7d106d40ee..16ec6ca877 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/GhostAccessRewriter.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/GhostAccessRewriter.scala @@ -13,9 +13,9 @@ import Contexts.{Context => DottyContext} import plugins._ import transform._ -class GhostAccessRewriter extends PluginPhase { self => +class GhostAccessRewriter(afterPhase: String) extends PluginPhase { self => override val phaseName = "ghost-removal" - override val runsAfter = Set("stainless") + override val runsAfter = Set(afterPhase) override val runsBefore = Set(FirstTransform.name) override def run(using dottyCtx: DottyContext): Unit = (new GhostAccessMacroTransform).run @@ -24,15 +24,19 @@ class GhostAccessRewriter extends PluginPhase { self => // However, the MacroTransform class is better suited for our needs. // Because PluginPhase extends MiniPhase (which is a class) and that MacroTransform is a class, we can't extend // both. So we use composition instead of inheritance to achieve our goal. + private class GhostAccessMacroTransform(using override val dottyCtx: DottyContext) extends MacroTransform with ASTExtractors { + import StructuralExtractors._ + import AuxiliaryExtractors._ + + private val ghostAnnotation = Symbols.requiredClass("stainless.annotation.ghost") + private val ghostFun = Symbols.requiredMethod("stainless.lang.ghost") - private class GhostAccessMacroTransform extends MacroTransform { override val phaseName = self.phaseName override val runsAfter = self.runsAfter override protected def newTransformer(using DottyContext): Transformer = new GhostRewriteTransformer private class GhostRewriteTransformer(using DottyContext) extends Transformer { - val ghostAnnotation = Symbols.requiredClass("stainless.annotation.ghost") /** * Is this symbol @ghost, or enclosed inside a ghost definition? @@ -78,9 +82,23 @@ class GhostAccessRewriter extends PluginPhase { self => case vd@ValDef(name, tpt, _) if effectivelyGhost(tree.symbol) => cpy.ValDef(tree)(name, tpt, mkZero(vd.rhs.tpe)) - case Apply(fun, args) if effectivelyGhost(fun.symbol) => + case Apply(fun, args) if effectivelyGhost(fun.symbol) || fun.symbol == ghostFun => mkZero(tree.tpe) + case ExRequiredExpression(_, true) => tpd.Literal(Constant(())) + case ExDecreasesExpression(_) => tpd.Literal(Constant(())) + case ExAssertExpression(_, _, true) => tpd.Literal(Constant(())) + case ExEnsuredExpression(body, _, true) => + transform(body) match { + case Apply(ExSymbol("stainless", "lang", "StaticChecks$", "Ensuring"), Seq(unwrapped)) => unwrapped + case body => body + } + + case ExWhile.WithInvariant(_, body) => transform(body) + case ExWhile.WithWeakInvariant(_, body) => transform(body) + case ExWhile.WithInline(body) => transform(body) + case ExWhile.WithOpaque(body) => transform(body) + case f@Apply(fun, args) => val fun1 = super.transform(fun) @@ -101,6 +119,19 @@ class GhostAccessRewriter extends PluginPhase { self => case Assign(lhs, rhs) if effectivelyGhost(lhs.symbol) => cpy.Assign(tree)(lhs, mkZero(rhs.tpe)) + case Block(stats, last) => + val recStats = transform(stats).filter { + case tpd.Literal(_) => false + case _ => true + } + val recLast = transform(last) + // Transform `val v = e; v` into `e` to allow for tail recursion elimination + (recStats.lastOption, recLast) match { + case (Some(vd @ ValDef(_, _, _)), iden @ (Ident(_) | Typed(Ident(_), _))) if iden.symbol == vd.symbol => + cpy.Block(tree)(recStats.init, vd.rhs) + case _ => cpy.Block(tree)(recStats, recLast) + } + case _ => super.transform(tree) } } diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessExtraction.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessExtraction.scala index 359861872e..9c1984384d 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessExtraction.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessExtraction.scala @@ -14,17 +14,18 @@ import typer._ import extraction.xlang.{trees => xt} import frontend.{CallBack, Frontend, FrontendFactory, ThreadedFrontend, UnsupportedCodeException} +import Utils._ case class ExtractedUnit(file: String, unit: xt.UnitDef, classes: Seq[xt.ClassDef], functions: Seq[xt.FunDef], typeDefs: Seq[xt.TypeDef]) class StainlessExtraction(val inoxCtx: inox.Context) { private val symbolMapping = new SymbolMapping - def extractUnit(using ctx: DottyContext): Option[ExtractedUnit] = { + def extractUnit(exportedSymsMapping: ExportedSymbolsMapping)(using ctx: DottyContext): Option[ExtractedUnit] = { // Remark: the method `extractUnit` is called for each compilation unit (which corresponds more or less to a Scala file) // Therefore, the symbolMapping instances needs to be shared accross compilation unit. // Since `extractUnit` is called within the same thread, we do not need to synchronize accesses to symbolMapping. - val extraction = new CodeExtraction(inoxCtx, symbolMapping) + val extraction = new CodeExtraction(inoxCtx, symbolMapping, exportedSymsMapping) import extraction._ val unit = ctx.compilationUnit diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessPlugin.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessPlugin.scala index c09da7ccd7..1bc86fe39a 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessPlugin.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/StainlessPlugin.scala @@ -4,15 +4,18 @@ package frontends.dotc import dotty.tools.dotc import dotc._ import core._ +import Decorators.toMessage import dotc.util._ import Contexts.{Context => DottyContext} import plugins._ import Phases._ +import Symbols._ import transform._ import reporting._ import inox.{Context, DebugSection, utils => InoxPosition} import stainless.frontend import stainless.frontend.{CallBack, Frontend} +import Utils._ object StainlessPlugin { val PluginName = "stainless" @@ -36,7 +39,7 @@ class StainlessPlugin extends StandardPlugin { Some(new ExtractionAndVerification) else None, if (pluginOpts.enableGhostElimination) - Some(new GhostAccessRewriter) + Some(new GhostAccessRewriter(if (pluginOpts.enableVerification) "stainless" else Pickler.name)) else None ).flatten } @@ -75,11 +78,12 @@ class StainlessPlugin extends StandardPlugin { private var extraction: Option[StainlessExtraction] = None private var callback: Option[CallBack] = None + private var exportedSymsMapping: ExportedSymbolsMapping = ExportedSymbolsMapping.empty // This method id called for every compilation unit, and in the same thread. // It is called within super.runOn. override def run(using DottyContext): Unit = - extraction.get.extractUnit.foreach(extracted => + extraction.get.extractUnit(exportedSymsMapping).foreach(extracted => callback.get(extracted.file, extracted.unit, extracted.classes, extracted.functions, extracted.typeDefs)) override def runOn(units: List[CompilationUnit])(using dottyCtx: DottyContext): List[CompilationUnit] = { @@ -104,6 +108,7 @@ class StainlessPlugin extends StandardPlugin { // Not pretty at all... Oh well... callback = Some(cb) extraction = Some(new StainlessExtraction(inoxCtx)) + exportedSymsMapping = Utils.exportedSymbolsMapping(inoxCtx, this.start, units) cb.beginExtractions() val unitRes = super.runOn(units) @@ -150,7 +155,7 @@ class StainlessPlugin extends StandardPlugin { case msg: String => message.severity match { case INFO => dottyCtx.reporter.report(Info(msg, pos)) - case WARNING => dottyCtx.reporter.report(Warning(msg, pos)) + case WARNING => dottyCtx.reporter.report(Warning(msg.toMessage, pos)) case ERROR | FATAL | INTERNAL => dottyCtx.reporter.report(Diagnostic.Error(msg, pos)) case _ => dottyCtx.reporter.report(Info(msg, pos)) // DEBUG messages are at reported at INFO level } diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/SymbolMapping.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/SymbolMapping.scala index fbe482b3e3..4e3e626336 100644 --- a/frontends/dotty/src/main/scala/stainless/frontends/dotc/SymbolMapping.scala +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/SymbolMapping.scala @@ -84,7 +84,14 @@ object SymbolMapping { sym.fullName.toString.trim.split("\\.") .filter(_ != "package$") .map(name => if (name.endsWith("$")) name.init else name) - .map(name => if (name.startsWith("_$")) name.drop(2) else name) + .map { name => + // Strip the _$ introduced for each scope of inner function + var nme = name + while (nme.startsWith("_$")) { + nme = nme.drop(2) + } + nme + } .mkString(".") } } \ No newline at end of file diff --git a/frontends/dotty/src/main/scala/stainless/frontends/dotc/Utils.scala b/frontends/dotty/src/main/scala/stainless/frontends/dotc/Utils.scala new file mode 100644 index 0000000000..c44f4d3a64 --- /dev/null +++ b/frontends/dotty/src/main/scala/stainless/frontends/dotc/Utils.scala @@ -0,0 +1,65 @@ +package stainless +package frontends.dotc + +import dotty.tools.dotc +import dotc._ +import core._ +import Symbols._ +import dotc.util._ +import Contexts.{Context => DottyContext} +import ast.tpd +import Flags._ +import transform._ + +object Utils { + + case class ExportedSymbolsMapping private(private val mapping: Map[Symbol, (Option[Symbol], Symbol)]) { + def add(from: Symbol, recv: Option[Symbol], to: Symbol): ExportedSymbolsMapping = { + val newMapping = mapping + (from -> (recv, to)) + ExportedSymbolsMapping(newMapping) + } + + def get(sym: Symbol): Option[Seq[Symbol]] = { + def loop(sym: Symbol, acc: Seq[Symbol]): Seq[Symbol] = { + mapping.get(sym) match { + case Some((recv, fwd)) => + val newPath = acc ++ recv.toSeq + loop(fwd, newPath) + case None => acc :+ sym + } + } + + if (mapping.contains(sym)) Some(loop(sym, Seq.empty)) + else None + } + } + + object ExportedSymbolsMapping { + def empty: ExportedSymbolsMapping = ExportedSymbolsMapping(Map.empty) + } + + def exportedSymbolsMapping(ctx: inox.Context, start: Int, units: List[CompilationUnit])(using dottyCtx: DottyContext): ExportedSymbolsMapping = { + var mapping = ExportedSymbolsMapping.empty + + class Traverser(override val dottyCtx: DottyContext) extends tpd.TreeTraverser with ASTExtractors { + import StructuralExtractors._ + import ExpressionExtractors._ + + override def traverse(tree: tpd.Tree)(using DottyContext): Unit = { + tree match { + case ExFunctionDef(sym, _, _, _, ExCall(recv, fwd, _, _)) if sym is Exported => + mapping = mapping.add(sym, recv.map(_.symbol), fwd) + case _ => traverseChildren(tree) + } + } + } + + import dotty.tools.dotc.typer.ImportInfo.withRootImports + for (unit <- units) { + val newCtx = dottyCtx.fresh.setPhase(start).setCompilationUnit(unit).withRootImports + val traverser = new Traverser(newCtx) + traverser.traverse(unit.tpdTree) + } + mapping + } +} diff --git a/frontends/library/stainless/lang/Cell.scala b/frontends/library/stainless/lang/Cell.scala new file mode 100644 index 0000000000..b8765fe09b --- /dev/null +++ b/frontends/library/stainless/lang/Cell.scala @@ -0,0 +1,14 @@ +/** Author: Samuel Chassot + * + * Cell class + * + * Used to provide better handling of mutable state + **/ + +package stainless.lang + +import stainless.annotation._ + + +@library +case class Cell[@mutable T](var v: T) \ No newline at end of file diff --git a/frontends/library/stainless/lang/package.scala b/frontends/library/stainless/lang/package.scala index e78edb03f6..8accfdbe59 100644 --- a/frontends/library/stainless/lang/package.scala +++ b/frontends/library/stainless/lang/package.scala @@ -4,6 +4,7 @@ package stainless import stainless.annotation._ import stainless.lang.StaticChecks._ +import stainless.lang.Cell package object lang { @@ -40,6 +41,25 @@ package object lang { @library abstract class Exception extends Throwable + @library + @extern + def Exception(msg: String): Exception = new Exception{} + + @library + sealed abstract class Try[T]{ + def map[U](f: T => U): Try[U] = this match { + case Success(t) => Success(f(t)) + case Failure(exc: Exception) => Failure(exc) + } + + def flatMap[U](f: T => Try[U]): Try[U] = this match { + case Success(t) => f(t) + case Failure(exc: Exception) => Failure(exc) + } + } + @library case class Success[T](t: T) extends Try[T] + @library case class Failure[T](exc: Exception) extends Try[T] + @ignore implicit class Throwing[T](underlying: => T) { def throwing(pred: Exception => Boolean): T = try { @@ -181,6 +201,13 @@ package object lang { a2(i2) = t } + @ignore @library + def swap[@mutable T](c1: Cell[T], c2: Cell[T]): Unit = { + val t = c2.v + c2.v = c1.v + c1.v = t + } + @extern @library @mutable @anyHeapRef trait AnyHeapRef { @refEq diff --git a/frontends/scalac/src/it/scala/stainless/GhostRewriteSuite.scala b/frontends/scalac/src/it/scala/stainless/GhostRewriteSuite.scala deleted file mode 100644 index 04f6057bca..0000000000 --- a/frontends/scalac/src/it/scala/stainless/GhostRewriteSuite.scala +++ /dev/null @@ -1,86 +0,0 @@ -package stainless - -import scala.tools.nsc.Global -import scala.tools.nsc.Settings -import scala.tools.nsc.reporters.StoreReporter -import scala.tools.nsc.reporters.ConsoleReporter - -import org.scalatest.funspec.AnyFunSpec -import stainless.frontends.scalac.StainlessPlugin - -class GhostRewriteSuite extends AnyFunSpec { - val settings = new Settings() - settings.stopAfter.value = List("refchecks") - settings.usejavacp.value = false - - val reporter = new StoreReporter(settings) - - def newCompiler: CheckingGlobal = { - new CheckingGlobal(settings) - } - - class CheckingGlobal(settings: Settings) extends Global(settings, reporter) { glob => - - private val ctx: inox.Context = stainless.TestContext.empty - - override def loadPlugins() = { - class Impl(override val stainlessContext: inox.Context) extends StainlessPlugin(this) - List(new Impl(ctx)) - } - - class GhostChecks extends Traverser { - val ghostAnnotation = rootMirror.getRequiredClass("stainless.annotation.ghost") - override def traverse(tree: Tree): Unit = { - val sym = tree.symbol - tree match { - case Ident(_) if sym.hasAnnotation(ghostAnnotation) && !currentOwner.isSynthetic => - glob.reporter.error(tree.pos, s"Access to ghost symbol leftover after rewrite.") - - case Select(qual, _) if sym.hasAnnotation(ghostAnnotation) && !currentOwner.isSynthetic => - if (!currentOwner.isAccessor) - glob.reporter.error(tree.pos, s"Access to ghost symbol leftover after rewrite.") - super.traverse(tree) - - case Assign(Ident(_), rhs) => - traverse(rhs) // don't check assignments to locals - - case _ => - super.traverse(tree) - } - } - } - } - - def ignoreError(i: StoreReporter.Info) = { - val s = i.toString - - s.contains("The Z3 native interface is not available") || - s.endsWith("VALID WARNING") - } - - def compileFile(file: String) = { - reporter.reset() - val compiler = newCompiler - - val run = new compiler.Run() - run.compile(Main.libraryFiles.toList :+ file) - val unitToCheck = run.units.toList.last - - (new compiler.GhostChecks).traverse(unitToCheck.body) - - val errors = reporter.infos.filterNot(ignoreError) - assert(errors.isEmpty, errors.mkString("\n\n")) - } - - - describe("Rewrite suite should remove all ghosts") { - ignore("should not leave ghost code around in GhostMethods.scala") { - compileFile("frontends/benchmarks/extraction/valid/GhostMethods.scala") - } - - ignore("should not leave ghost code around in GhostCaseClass.scala") { - compileFile("frontends/benchmarks/extraction/valid/GhostCaseClass.scala") - } - } - -} diff --git a/frontends/scalac/src/it/scala/stainless/ScalacExtractionSuite.scala b/frontends/scalac/src/it/scala/stainless/ScalacExtractionSuite.scala deleted file mode 100644 index 5e42300c20..0000000000 --- a/frontends/scalac/src/it/scala/stainless/ScalacExtractionSuite.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless - -class ScalacExtractionSuite extends ExtractionSuite { - - testExtractAll("extraction/valid") - testRejectAll("extraction/invalid", - "FunnyDottyInference.scala", - // This file is extracted because there is no -Ysafe-init check equivalent in Scala 2 (and not caught by Stainless either) - "Initialization6.scala") - - testExtractAll("verification/valid") - testExtractAll("verification/invalid") - testExtractAll("verification/unchecked-valid") - testExtractAll("verification/unchecked-invalid") - testExtractAll("verification/false-valid") - - testExtractAll("imperative/valid") - testExtractAll("imperative/invalid") - - testExtractAll("termination/valid") - testExtractAll("termination/looping") - testExtractAll("termination/unchecked-invalid") - testExtractAll("termination/false-invalid") - -} - diff --git a/frontends/scalac/src/main/resources/scalac-plugin.xml b/frontends/scalac/src/main/resources/scalac-plugin.xml deleted file mode 100644 index e62bc9a08f..0000000000 --- a/frontends/scalac/src/main/resources/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - stainless-plugin - stainless.frontends.scalac.StainlessPlugin - \ No newline at end of file diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/ASTExtractors.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/ASTExtractors.scala deleted file mode 100644 index 725f93fdee..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/ASTExtractors.scala +++ /dev/null @@ -1,1521 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import scala.tools.nsc._ -import scala.collection.mutable.{Map => MutableMap} - -/** Contains extractors to pull-out interesting parts of the Scala ASTs. */ -trait ASTExtractors(val global: Global) { - import global._ - import global.definitions._ - - def classFromName(str: String) = { - rootMirror.getClassByName(str) - } - - def moduleFromName(str: String) = { - rootMirror.getModuleByName(str) - } - - /** - * Extract the annotations for [[sym]], combined with its owner (unless - * [[ignoreOwner]] is true). - * - * When [[sym]] is synthetically created by the compiler, also extract the - * companion symbol's annotations. But behold the dark arcane magic: - * [[Symbol.companionSymbol]] is deprecated in favor of [[Symbol.companion]], - * yet for implicit class the synthetic function of the same name doesn't - * have a [[Symbol.companion]]. Additionally, the documentation doesn't - * clearly guarantees that functions can have a companion symbol. In - * practice, however, it seems to work. - */ - def getAnnotations(sym: Symbol, ignoreOwner: Boolean = false): Seq[(String, Seq[Tree])] = { - val actualSymbol = sym.accessedOrSelf.orElse(sym) - val selfs = actualSymbol.annotations - val owners = - if (ignoreOwner) Set.empty - else actualSymbol.owner.annotations.filter(annot => - annot.toString != "stainless.annotation.export" && - !annot.toString.startsWith("stainless.annotation.cCode.global") - ) - val companions = if (actualSymbol.isSynthetic) actualSymbol.companionSymbol.annotations else Set.empty - (for { - a <- (selfs ++ owners ++ companions) - name = a.atp.safeToString - .replace(".package.", ".") - .replace(" @scala.annotation.meta.field", "") - } yield { - if (name startsWith "stainless.annotation.") { - val shortName = name drop "stainless.annotation.".length - Some(shortName, a.args) - } else if (name == "inline") { - Some(name, a.args) - } else { - None - } - }).flatten.foldLeft[(Set[String], Seq[(String, Seq[Tree])])]((Set(), Seq())) { - case (acc @ (keys, _), (key, _)) if keys contains key => acc - case ((keys, seq), (key, args)) => (keys + key, seq :+ (key -> args)) - }._2 - } - - protected lazy val scalaMapSym = classFromName("scala.collection.immutable.Map") - protected lazy val scalaSetSym = classFromName("scala.collection.immutable.Set") - protected lazy val scalaListSym = classFromName("scala.collection.immutable.List") - - protected lazy val exceptionSym = classFromName("stainless.lang.Exception") - - protected lazy val setSym = classFromName("stainless.lang.Set") - protected lazy val mapSym = classFromName("stainless.lang.Map") - protected lazy val mutableMapSym = classFromName("stainless.lang.MutableMap") - protected lazy val bagSym = classFromName("stainless.lang.Bag") - protected lazy val realSym = classFromName("stainless.lang.Real") - - protected lazy val bvSym = classFromName("stainless.math.BitVectors.BV") - - protected lazy val optionSymbol = classFromName("stainless.lang.Option") - protected lazy val someSymbol = classFromName("stainless.lang.Some") - protected lazy val noneSymbol = classFromName("stainless.lang.None") - - protected lazy val listSymbol = classFromName("stainless.collection.List") - protected lazy val consSymbol = classFromName("stainless.collection.Cons") - protected lazy val nilSymbol = classFromName("stainless.collection.Nil") - - protected lazy val covListSymbol = classFromName("stainless.covcollection.List") - protected lazy val covConsSymbol = classFromName("stainless.covcollection.$colon$colon") - protected lazy val covNilSymbol = moduleFromName("stainless.covcollection.Nil").moduleClass - - protected lazy val arraySym = classFromName("scala.Array") - protected lazy val someClassSym = classFromName("scala.Some") - protected lazy val byNameSym = classFromName("scala.") - protected lazy val bigIntSym = classFromName("scala.math.BigInt") - protected lazy val stringSym = classFromName("java.lang.String") - - protected def functionTraitSym(i:Int) = { - require(0 <= i && i <= 22, s"$i must be between 0 and 22") - classFromName("scala.Function" + i) - } - - def isTuple(sym: Symbol, size: Int): Boolean = (size > 0 && size <= 22) && (sym == classFromName(s"scala.Tuple$size")) - - def isBigIntSym(sym: Symbol) : Boolean = getResolvedTypeSym(sym) == bigIntSym - - def isStringSym(sym: Symbol) : Boolean = getResolvedTypeSym(sym) match { case `stringSym` => true case _ => false } - - def isByNameSym(sym: Symbol) : Boolean = getResolvedTypeSym(sym) == byNameSym - - // Resolve type aliases - def getResolvedTypeSym(sym: Symbol): Symbol = { - if (sym.isAliasType) { - getResolvedTypeSym(sym.tpe.resultType.typeSymbol) - } else { - sym - } - } - - def isBVSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == bvSym - } - - def isSetSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == setSym - } - - def isBagSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == bagSym - } - - def isRealSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == realSym - } - - def isMapSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == mapSym - } - - def isMutableMapSym(sym: Symbol) : Boolean = { - getResolvedTypeSym(sym) == mutableMapSym - } - - def isFunction(sym: Symbol, i: Int) : Boolean = - 0 <= i && i <= 22 && sym == functionTraitSym(i) - - def isArrayClassSym(sym: Symbol): Boolean = sym == arraySym - - private lazy val bvtypes = Set(ByteTpe, ShortTpe, IntTpe, LongTpe) - - def hasBVType(t: Tree) = bvtypes contains t.tpe.widen - - def hasNumericType(t: Tree): Boolean = hasBigIntType(t) || hasBVType(t) || hasRealType(t) - - def hasBigIntType(t: Tree) = isBigIntSym(t.tpe.typeSymbol) - - def hasStringType(t: Tree) = isStringSym(t.tpe.typeSymbol) - - def hasRealType(t: Tree) = isRealSym(t.tpe.typeSymbol) - - def hasBooleanType(t: Tree) = t.tpe.widen =:= BooleanClass.tpe - - def isDefaultGetter(sym: Symbol) = sym.isSynthetic && (sym.name containsName nme.DEFAULT_GETTER_STRING) - - def isCopyMethod(sym: Symbol) = sym.isSynthetic && sym.name == nme.copy - - def canExtractSynthetic(sym: Symbol) = { - sym.isImplicit || - isDefaultGetter(sym) || - isCopyMethod(sym) - } - - object TupleSymbol { - // It is particularly time expensive so we cache this. - private val cache = MutableMap[Symbol, Option[Int]]() - private val cardinality = """Tuple(\d{1,2})""".r - def unapply(sym: Symbol): Option[Int] = cache.getOrElseUpdate(sym, { - // First, extract a gess about the cardinality of the Tuple. - // Then, confirm that this is indeed a regular Tuple. - val name = sym.unexpandedName.toString - name match { - case cardinality(i) if isTuple(sym, i.toInt) => Some(i.toInt) - case _ => None - } - }) - - def unapply(tpe: Type): Option[Int] = tpe.typeSymbol match { - case TupleSymbol(i) => Some(i) - case _ => None - } - - def unapply(tree: Tree): Option[Int] = unapply(tree.tpe) - } - - /** A set of helpers for extracting trees.*/ - object ExtractorHelpers { - /** Extracts the identifier as `"Ident(name)"` (who needs this?!) */ - object ExIdNamed { - def unapply(id: Ident): Option[String] = Some(id.toString) - } - - /** Extracts the tree and its type (who needs this?!) */ - object ExHasType { - def unapply(tr: Tree): Option[(Tree, Symbol)] = Some((tr, tr.tpe.typeSymbol)) - } - - /** Extracts the string representation of a name of something having the `Name` trait */ - object ExNamed { - def unapply(name: Name): Option[String] = Some(name.toString) - } - - /** Returns the full dot-separated names of the symbol as a list of strings */ - object ExSymbol { - def unapplySeq(t: Tree): Option[Seq[String]] = { - if (t.symbol == null) None - else Some(t.symbol.fullName.toString.split('.').toSeq) - } - } - - /** Matches nested `Select(Select(...Select(a, b) ...y) , z)` and returns the list `a,b, ... y,z` */ - object ExSelected { - def unapplySeq(select: Select): Option[Seq[String]] = select match { - case Select(This(scalaName), name) => - Some(Seq(scalaName.toString, symName(select, name))) - - case Select(from: Select, name) => - unapplySeq(from).map(prefix => prefix :+ symName(select, name)) - - case Select(from: Ident, name) => - val full = symName(select, name) :: from.symbol.ownerChain.init.map(_.name.toString) - Some(full.reverse) - - case _ => - None - } - } - } - - object StructuralExtractors { - import ExtractorHelpers._ - - /** Extracts the 'ensuring' contract from an expression. */ - object ExEnsuredExpression { - def unapply(tree: Apply): Option[(Tree,Tree,Boolean)] = tree match { - // An optional message may comes after `contract`, but we do not make use of it. - case Apply(Select(Apply(TypeApply( - ExSelected("scala", "Predef", "Ensuring"), - _ :: Nil), body :: Nil), ExNamed("ensuring")), contract :: _) - => Some((body, contract, false)) - - // Ditto - case Apply(Select(Apply(TypeApply( - ExSelected("stainless", "lang", "StaticChecks", "Ensuring"), - _ :: Nil), body :: Nil), ExNamed("ensuring")), contract :: _) - => Some((body, contract, true)) - - case _ => None - } - } - - object ExThrowingExpression { - def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { - case Apply(Select(Apply( - TypeApply(ExSelected("stainless", "lang", "`package`", "Throwing"), _ :: Nil), body :: Nil), ExNamed("throwing")), - contract :: Nil - ) => Some((body, contract)) - - case _ =>None - } - } - - /** Matches the `holds` expression at the end of any boolean expression, and returns the boolean expression.*/ - object ExHoldsExpression { - def unapply(tree: Select) : Option[Tree] = tree match { - case Select( - Apply(ExSelected("stainless", "lang", "`package`", "BooleanDecorations"), realExpr :: Nil), - ExNamed("holds") - ) => Some(realExpr) - case _ => None - } - } - - /** Matches the `holds` expression at the end of any boolean expression with a proof as argument, and returns both of themn.*/ - object ExHoldsWithProofExpression { - def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { - case Apply(Select(Apply(ExSelected("stainless", "lang", "`package`", "BooleanDecorations"), body :: Nil), ExNamed("holds")), proof :: Nil) => - Some((body, proof)) - case _ => None - } - } - - /** Matches the `because` method at the end of any boolean expression, and return the assertion and the cause. If no "because" method, still returns the expression */ - object ExMaybeBecauseExpressionWrapper { - def unapply(tree: Tree) : Some[Tree] = tree match { - case Apply(ExSelected("stainless", "lang", "`package`", "because"), body :: Nil) => - unapply(body) - case body => Some(body) - } - } - - /** Matches the `because` method at the end of any boolean expression, and return the assertion and the cause.*/ - object ExBecauseExpression { - def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { - case Apply(Select( - Apply(ExSelected("stainless", "proof" | "equations", "`package`", "boolean2ProofOps"), body :: Nil), - ExNamed("because")), proof :: Nil) => Some((body, proof)) - case _ => None - } - } - - /** Matches the `bigLength` expression at the end of any string expression, and returns the expression.*/ - object ExBigLengthExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case Apply(Select( - Apply(ExSelected("stainless", "lang", "`package`", "StringDecorations"), stringExpr :: Nil), - ExNamed("bigLength")), Nil) - => Some(stringExpr) - case _ => None - } - } - - /** Matches the `bigSubstring` method at the end of any string expression, and returns the expression and the start index expression.*/ - object ExBigSubstringExpression { - def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { - case Apply(Select( - Apply(ExSelected("stainless", "lang", "`package`", "StringDecorations"), stringExpr :: Nil), - ExNamed("bigSubstring")), startExpr :: Nil) - => Some(stringExpr, startExpr) - case _ => None - } - } - - /** Matches the `bigSubstring` expression at the end of any string expression, and returns the expression, the start and end index expressions.*/ - object ExBigSubstring2Expression { - def unapply(tree: Apply) : Option[(Tree, Tree, Tree)] = tree match { - case Apply(Select( - Apply(ExSelected("stainless", "lang", "`package`", "StringDecorations"), stringExpr :: Nil), - ExNamed("bigSubstring")), startExpr :: endExpr :: Nil) - => Some(stringExpr, startExpr, endExpr) - case _ => None - } - } - - /** Matches an implication `lhs ==> rhs` and returns (lhs, rhs)*/ - object ExImplies { - def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { - case - Apply( - Select( - Apply( - ExSymbol("stainless", "lang", "BooleanDecorations"), - lhs :: Nil - ), - ExNamed("$eq$eq$greater") - ), - rhs :: Nil - ) => Some((lhs, rhs)) - case _ => None - } - } - - /** Matches `lhs &&& rhs` and returns (lhs, rhs)*/ - object ExSplitAnd { - def unapply(tree: Apply) : Option[(Tree, Tree)] = tree match { - case - Apply( - Select( - Apply( - ExSymbol("stainless", "lang", "BooleanDecorations"), - lhs :: Nil - ), - ExNamed("$amp$amp$amp") - ), - rhs :: Nil - ) => Some((lhs, rhs)) - case _ => None - } - } - - object ExGhost { - def unapply(tree: Apply): Option[Tree] = tree match { - case a@Apply(TypeApply(s@ExSymbol("stainless", "lang", "ghost"), _), body :: Nil) => - Some(body) - case _ => None - } - } - - /** Extracts the 'require' contract from an expression (only if it's the - * first call in the block). */ - object ExRequiredExpression { - def unapply(tree: Apply): Option[(Tree, Boolean)] = tree match { - // An optional message may comes after `contractBody`, but we do not make use of it. - case Apply(ExSelected("scala", "Predef", "require"), contractBody :: _) => - Some((contractBody, false)) - - // Ditto - case Apply(ExSelected("stainless", "lang", "StaticChecks", "require"), contractBody :: _) => - Some((contractBody, true)) - - case _ => None - } - } - - /** Extracts the 'reads' contract from an expression */ - object ExReadsExpression { - def unapply(tree: Apply): Option[Tree] = tree match { - case Apply(ExSelected("stainless", "lang", "`package`", "reads"), objs :: Nil) => - Some(objs) - case _ => None - } - } - - /** Extracts the 'modifies' contract from an expression */ - object ExModifiesExpression { - def unapply(tree: Apply): Option[Tree] = tree match { - case Apply(ExSelected("stainless", "lang", "`package`", "modifies"), objs :: Nil) => - Some(objs) - case _ => None - } - } - - /** Extracts the 'decreases' contract for an expression (should be right after 'require') */ - object ExDecreasesExpression { - def unapply(tree: Apply): Option[Seq[Tree]] = tree match { - case Apply(ExSelected("stainless", "lang", "`package`", "decreases"), args) => - Some(args) - case _ => None - } - } - - /** Extracts the `(input, output) passes { case In => Out ...}` - * and returns (input, output, list of case classes) */ - object ExPasses { - import ExpressionExtractors._ - - def unapply(tree: Apply): Option[(Tree, Tree, List[CaseDef])] = tree match { - case Apply( - Select( - Apply( - TypeApply( - ExSelected("stainless", "lang", "`package`", "Passes"), - Seq(_, _) - ), - Seq(ExTuple(_, Seq(in, out))) - ), - ExNamed("passes") - ), - Seq(ExLambdaExpression( - Seq(ValDef(_, _, _, EmptyTree)), - ExPatternMatching(_, tests) - )) - ) => Some((in, out, tests)) - case _ => None - } - } - - /** Returns a string literal from a constant string literal. */ - object ExStringLiteral { - def unapply(tree: Tree): Option[String] = tree match { - case Literal(c @ Constant(i)) if c.tpe == StringClass.tpe => - Some(c.stringValue) - case _ => - None - } - } - - /** Returns the arguments of an unapply pattern */ - object ExUnapplyPattern { - def unapply(tree: Tree): Option[(Tree, Seq[Tree])] = tree match { - case UnApply(Apply(s, _), args) => - Some((s, args)) - case _ => None - } - } - - /** Returns the argument of a bigint literal, either from scala or stainless */ - object ExBigIntLiteral { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply(ExSelected("scala", "`package`", "BigInt", "apply"), n :: Nil) => - Some(n) - case Apply(ExSelected("stainless", "lang", "`package`", "BigInt", "apply"), n :: Nil) => - Some(n) - case _ => - None - } - } - - object FrontendBVType { - val R = """type (UInt|Int)(\d+)""".r - - def unapply(tpe: Type): Option[(Boolean, Int)] = tpe match { - case TypeRef(_, sym, FrontendBVKind(signed, size) :: Nil) if isBVSym(sym) => - Some((signed, size)) - case TypeRef(_, sym, Nil) if isBVSym(sym) => - sym.toString match { - case R(signed, size) => Some((signed == "Int", size.toInt)) - case _ => None - } - case _ => FrontendBVKind.unapply(tpe) - } - - def unapply(tr: Tree): Option[(Boolean, Int)] = unapply(tr.tpe) - } - - object FrontendBVKind { - val R = """object ([ui])(\d+)""".r - - def unapply(tpe: Type): Option[(Boolean, Int)] = tpe match { - case SingleType(_, sym) => - sym.toString match { - case R(signed, size) => Some((signed == "i", size.toInt)) - case _ => None - } - case _ => - None - } - - def unapply(tr: Tree): Option[(Boolean, Int)] = unapply(tr.tpe) - } - - /** `max` extraction for bitvectors */ - object ExMaxBV { - def unapply(tree: Tree): Option[(Boolean, Int)] = tree match { - case TypeApply( - ExSelected("stainless", "math", "BitVectors", "max"), - FrontendBVType(signed, size) :: Nil - ) => - Some((signed, size)) - case _ => - None - } - } - - /** `min` extraction for bitvectors */ - object ExMinBV { - def unapply(tree: Tree): Option[(Boolean, Int)] = tree match { - case TypeApply( - ExSelected("stainless", "math", "BitVectors", "min"), - FrontendBVType(signed, size) :: Nil - ) => - Some((signed, size)) - case _ => - None - } - } - - /** `fromByte` extraction (Byte to Int8 identity conversion) */ - object ExFromByte { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply( - ExSelected("stainless", "math", "BitVectors", "fromByte"), - expr :: Nil - ) => - Some(expr) - case _ => - None - } - } - - /** `fromShort` extraction (Short to Int16 identity conversion) */ - object ExFromShort { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply( - ExSelected("stainless", "math", "BitVectors", "fromShort"), - expr :: Nil - ) => - Some(expr) - case _ => - None - } - } - - /** `fromInt` extraction (Int to Int32 identity conversion) */ - object ExFromInt { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply( - ExSelected("stainless", "math", "BitVectors", "fromInt"), - expr :: Nil - ) => - Some(expr) - case _ => - None - } - } - - /** `fromLong` extraction (Long to Int64 identity conversion) */ - object ExFromLong { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply( - ExSelected("stainless", "math", "BitVectors", "fromLong"), - expr :: Nil - ) => - Some(expr) - case _ => - None - } - } - - /** `intToBV` extraction */ - object ExIntToBV { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { - case Apply( - TypeApply( - ExSelected("stainless", "math", "BitVectors", "intToBV"), - typ :: Nil - ), n :: Nil - ) => - Some((typ, n)) - case _ => - None - } - } - - /** `bigIntToBV` extraction */ - object ExBigIntToBV { - def unapply(tree: Tree): Option[(Boolean, Int, Tree)] = tree match { - case Apply( - TypeApply( - ExSelected("stainless", "math", "BitVectors", "bigIntToBV"), - FrontendBVKind(signed, size) :: Nil - ), n :: Nil - ) => - Some((signed, size, n)) - case _ => - None - } - } - - /** Returns the two components (n, d) of a real n/d literal */ - object ExRealLiteral { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { - case Apply(ExSelected("stainless", "lang", "Real", "apply"), n :: d :: Nil) => - Some((n, d)) - case _ => - None - } - } - - /** Matches Real(x) when n is an integer and returns x */ - object ExRealIntLiteral { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply(ExSelected("stainless", "lang", "Real", "apply"), n :: Nil) => - Some(n) - case _ => - None - } - } - - /** Matches the construct int2bigInt(a) and returns a */ - object ExIntToBigInt { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply(ExSelected("math", "BigInt", "int2bigInt"), tree :: Nil) => Some(tree) - case _ => None - } - } - - /** Matches the construct stainless.math.wrapping[A](a) and returns a */ - object ExWrapping { - def unapply(tree: Tree): Option[Tree] = tree match { - case Apply(TypeApply(ExSelected("stainless", "math", "`package`", "wrapping"), Seq(_)), tree :: Nil) => - Some(tree) - case _ => - None - } - } - - /** Matches the construct List[tpe](a, b, ...) and returns tpe and arguments */ - object ExListLiteral { - def unapply(tree: Apply): Option[(Tree, List[Tree])] = tree match { - case Apply( - TypeApply(ExSelected("stainless", "collection", "List", "apply"), tpe :: Nil), - args) => - Some((tpe, args)) - case _ => - None - } - } - - /** Matches the construct covcollection.List[tpe](a, b, ...) and returns tpe and arguments */ - object ExCovListLiteral { - def unapply(tree: Apply): Option[(Tree, List[Tree])] = tree match { - case Apply( - TypeApply(ExSelected("stainless", "covcollection", "List", "apply"), tpe :: Nil), - args) => - Some((tpe, args)) - case _ => - None - } - } - - /** Extracts the 'assert' contract from an expression (only if it's the - * first call in the block). */ - object ExAssertExpression { - def unapply(tree: Apply): Option[(Tree, Option[String], Boolean)] = tree match { - case Apply(ExSymbol("scala", "Predef", "assert"), contractBody :: Nil) => - Some((contractBody, None, false)) - - case Apply(ExSymbol("stainless", "lang", "StaticChecks", "assert"), contractBody :: Nil) => - Some((contractBody, None, true)) - - case Apply( - ExSymbol("scala", "Predef", "assert"), contractBody :: (error: Literal) :: Nil) => - Some((contractBody, Some(error.value.stringValue), false)) - - case Apply(ExSymbol("stainless", "lang", "StaticChecks", "assert"), contractBody :: (error: Literal) :: Nil) => - Some((contractBody, Some(error.value.stringValue), true)) - - case _ => - None - } - } - - /** Matches an object with no type parameters, regardless of its visibility. */ - object ExObjectDef { - def unapply(md: ModuleDef): Option[(String, Template)] = md match { - case ModuleDef(_, name, impl) if !md.symbol.isCase => Some((name.toString, impl)) - case _ => None - } - } - - object ExCaseObject { - def unapply(s: Select): Option[Symbol] = { - if (s.tpe.typeSymbol.isModuleClass) { - Some(s.tpe.typeSymbol) - } else { - None - } - } - } - - object ExCaseClassSyntheticJunk { - def unapply(cd: Tree): Boolean = cd match { - case ClassDef(_, _, _, _) if cd.symbol.isSynthetic => true - case DefDef(_, _, _, _, _, _) if cd.symbol.isSynthetic && (cd.symbol.isCase || cd.symbol.isPrivate) => true - case _ => false - } - } - - object ExConstructorDef { - def unapply(dd: DefDef): Boolean = dd match { - case DefDef(_, name, tparams, vparamss, tpt, rhs) if name == nme.CONSTRUCTOR && tparams.isEmpty => true - case _ => false - } - } - - object ExMainFunctionDef { - def unapply(dd: DefDef): Boolean = dd match { - case DefDef(_, name, tparams, vparamss, tpt, rhs) if name.toString == "main" && tparams.isEmpty && vparamss.size == 1 && vparamss.head.size == 1 => - true - case _ => false - } - } - - object ExFunctionDef { - /** Matches a function with a single list of arguments, and regardless of its visibility. */ - def unapply(dd: DefDef): Option[(Symbol, Seq[Symbol], Seq[ValDef], Type, Tree)] = dd match { - case DefDef(_, name, tparams, vparamss, tpt, rhs) if name != nme.CONSTRUCTOR && !dd.symbol.isAccessor => - if (( - // extract implicit class construction functions - dd.symbol.isSynthetic && - dd.symbol.isImplicit && - dd.symbol.isMethod && - !(getAnnotations(tpt.symbol) exists (_._1 == "ignore")) - ) || - !dd.symbol.isSynthetic || - canExtractSynthetic(dd.symbol) - ) { - Some((dd.symbol, tparams.map(_.symbol), vparamss.flatten, tpt.tpe, rhs)) - } else { - None - } - case _ => None - } - } - - object ExMutableFieldDef { - - /** Matches a definition of a strict var field inside a class constructor */ - def unapply(vd: ValDef) : Option[(Symbol, Type, Tree)] = { - val sym = vd.symbol - vd match { - // Implemented fields - case ValDef(mods, name, tpt, rhs) if ( - !sym.isCaseAccessor && !sym.isParamAccessor && - !sym.isLazy && !sym.isSynthetic && sym.isVar - ) => - // Since scalac uses the accessor symbol all over the place, we pass that instead: - Some((sym, tpt.tpe, rhs)) - case _ => None - } - } - } - - object ExFieldDef { - /** Matches a definition of a strict field */ - def unapply(vd: ValDef): Option[(Symbol, Type, Tree)] = { - val sym = vd.symbol - vd match { - // Implemented fields - case ValDef(mods, name, tpt, rhs) if ( - !sym.isCaseAccessor && !sym.isParamAccessor && - !sym.isLazy && !sym.isVar - ) => - Some((sym, tpt.tpe, rhs)) - case _ => None - } - } - } - - object ExLazyFieldDef { - /** Matches a definition of a lazy field */ - def unapply(vd: ValDef): Option[(Symbol, Type, Tree)] = { - val sym = vd.symbol - vd match { - case ValDef(mods, name, tpt, rhs) if ( - sym.isLazy && !sym.isCaseAccessor && !sym.isParamAccessor && - !sym.isSynthetic - ) => - Some((sym, tpt.tpe, rhs)) - case _ => None - } - } - } - - object ExFieldAccessorFunction { - /** Matches the accessor function of a field */ - def unapply(dd: DefDef): Option[(Symbol, Type, Seq[ValDef], Tree)] = dd match { - case DefDef(_, name, tparams, vparamss, tpt, rhs) if( - vparamss.size <= 1 && name != nme.CONSTRUCTOR && - dd.symbol.isAccessor && !dd.symbol.isLazy - ) => - Some((dd.symbol, tpt.tpe, vparamss.flatten, rhs)) - case _ => None - } - } - - object ExLazyFieldAccessorFunction { - def unapply(dd: DefDef): Option[(Symbol, Type, Tree)] = dd match { - case DefDef(_, name, tparams, vparamss, tpt, rhs) if( - vparamss.size <= 1 && name != nme.CONSTRUCTOR && - !dd.symbol.isSynthetic && dd.symbol.isAccessor && dd.symbol.isLazy - ) => - Some((dd.symbol, tpt.tpe, rhs)) - case _ => None - } - } - - object ExIndexedAt { - def unapply(annot: Annotation): Option[Tree] = annot match { - case AnnotationInfo(TypeRef(_, sym, _), Seq(arg), _) if - getResolvedTypeSym(sym) == classFromName("stainless.annotation.indexedAt") => - Some(arg) - case _ => None - } - } - } - - object ExpressionExtractors { - import ExtractorHelpers._ - - object ExErrorExpression { - def unapply(tree: Apply) : Option[(String, Tree)] = tree match { - case a @ Apply(TypeApply(ExSymbol("stainless", "lang", "error"), List(tpe)), List(lit : Literal)) => - Some((lit.value.stringValue, tpe)) - case _ => - None - } - } - - object ExOldExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case a @ Apply(TypeApply(ExSymbol("stainless", "lang", "old"), List(tpe)), List(arg)) => - Some(arg) - case _ => - None - } - } - - object ExSnapshotExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case Apply(TypeApply(ExSymbol("stainless", "lang", "snapshot"), List(_)), List(arg)) => - Some(arg) - case _ => - None - } - } - - object ExFreshCopyExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case Apply(TypeApply(ExSymbol("stainless", "lang", "freshCopy"), List(_)), List(arg)) => - Some(arg) - case _ => - None - } - } - - object ExChooseExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case a @ Apply( - TypeApply(s @ ExSymbol("stainless", "lang", "choose"), types), - predicate :: Nil) => - Some(predicate) - case _ => None - } - } - - object ExSwapExpression { - def unapply(tree: Apply) : Option[(Tree, Tree, Tree, Tree)] = tree match { - case a @ Apply( - TypeApply(ExSymbol("stainless", "lang", "swap"), _), - array1 :: index1 :: array2 :: index2 :: Nil) => - Some((array1, index1, array2, index2)) - case _ => None - } - } - - object ExLambdaExpression { - def unapply(tree: Function) : Some[(Seq[ValDef], Tree)] = tree match { - case Function(vds, body) => Some((tree.vparams, tree.body)) - } - } - - object ExForallExpression { - def unapply(tree: Apply) : Option[Tree] = tree match { - case a @ Apply( - TypeApply(s @ ExSymbol("stainless", "lang", "forall"), _), - predicate :: Nil) => - Some(predicate) - case _ => None - } - } - - private object ExArraySelect { - def unapply(tree: Select): Option[(Tree, String)] = tree match { - case Select(array, select) if isArrayClassSym(array.tpe.typeSymbol) => Some((array, select.toString)) - case _ => None - } - } - - /** - * Extract both Array.length and Array.size as they are equivalent. - * - * Note that Array.size is provided thought implicit conversion to - * scala.collection.mutable.ArrayOps via scala.Predef.*ArrayOps. - * As such, `arrayOps` can be `intArrayOps`, `genericArrayOps`, etc... - */ - object ExArrayLength { - def unapply(tree: Select): Option[Tree] = tree match { - case Select(Apply(ExSymbol("scala", "Predef", arrayOps), Seq(array)), size) - if (arrayOps.toString endsWith "ArrayOps") && (size.toString == "size") - => Some(array) - - case ExArraySelect(array, "length") => Some(array) - - case _ => None - } - } - - object ExArrayApplyBV { - def unapply(tree: Apply): Option[(Tree, Tree, Tree)] = tree match { - case Apply(TypeApply( - Select( - Apply( - TypeApply(ExSelected("stainless", "math", "BitVectors", "ArrayIndexing"), tpe :: Nil), - array :: Nil - ), - ExNamed("apply") - ), - bvType :: Nil), - index :: Nil) => - - Some((array, bvType, index)) - - case _ => None - } - } - - object ExArrayUpdated { - def unapply(tree: Apply): Option[(Tree, Tree, Tree)] = tree match { - case Apply( - Apply( - TypeApply(Select(Apply(ExSymbol("scala", "Predef", arrayOps), Seq(array)), update), _), - Seq(index, value)), - List(Typed(_, _)) - ) - if (arrayOps endsWith "ArrayOps") && (update.toString == "updated") - => Some((array, index, value)) - - case Apply( - Select( - Apply( - TypeApply(ExSelected("stainless", "lang", "`package`", "ArrayUpdating"), tpe :: Nil), - array :: Nil - ), - ExNamed("updated") - ), - index :: value :: Nil) => - - Some((array, index, value)) - - // There's no `updated` method in the Array class itself, only though implicit conversion. - case _ => None - } - } - - object ExArrayUpdate { - def unapply(tree: Apply): Option[(Tree, Tree, Tree)] = tree match { - // This implicit conversion to ArrayOps is shadowed. We therefore skip it here. - case Apply(ExArraySelect(array, "update"), Seq(index, newValue)) => Some((array, index, newValue)) - case _ => None - } - } - - object ExArrayApply { - def unapply(tree: Apply): Option[(Tree, Tree)] = tree match { - // This implicit conversion to ArrayOps is shadowed. We therefore skip it here. - case Apply(ExArraySelect(array, "apply"), Seq(index)) => Some((array, index)) - case _ => None - } - } - - object ExArrayFill { - def unapply(tree: Apply): Option[(Tree, Tree, Tree)] = tree match { - case Apply( - Apply( - Apply( - TypeApply(ExSelected("scala", "Array", "fill"), baseType :: Nil), - length :: Nil - ), - defaultValue :: Nil - ), - manifest - ) => - Some((baseType, length, defaultValue)) - - case _ => None - } - } - - object ExArrayLiteral { - def unapply(tree: Apply): Option[(Type, Seq[Tree])] = tree match { - case Apply(ExSelected("scala", "Array", "apply"), args) => - tree.tpe match { - case TypeRef(_, _, List(t1)) => - Some((t1, args)) - case _ => - None - } - - case Apply(Apply(TypeApply(ExSelected("scala", "Array", "apply"), List(tpt)), args), ctags) => - Some((tpt.tpe, args)) - - case _ => - None - } - } - - - object ExValDef { - /** Extracts val's in the head of blocks. */ - def unapply(tree: ValDef): Option[(Symbol,Tree,Tree)] = tree match { - case vd @ ValDef(mods, _, tpt, rhs) if !mods.isMutable => Some((vd.symbol, tpt, rhs)) - case _ => None - } - } - object ExVarDef { - /** Extracts var's in the head of blocks. */ - def unapply(tree: ValDef): Option[(Symbol,Tree,Tree)] = tree match { - case vd @ ValDef(mods, _, tpt, rhs) if mods.isMutable => Some((vd.symbol, tpt, rhs)) - case _ => None - } - } - - object ExAssign { - def unapply(tree: Assign): Option[(Symbol,Tree)] = tree match { - case Assign(id@Ident(_), rhs) => Some((id.symbol, rhs)) - //case Assign(sym@Select(This(_), v), rhs) => Some((sym.symbol, rhs)) - case _ => None - } - } - - object ExFieldAssign { - def unapply(tree: Assign): Option[(Symbol,Tree,Tree)] = tree match { - case Assign(sel@Select(This(_), v), rhs) => Some((sel.symbol, sel, rhs)) - case _ => None - } - } - - object ExBareWhile { - def unapply(tree: LabelDef): Option[(Tree,Tree)] = tree match { - case (label@LabelDef( - _, _, If(cond, Block(body, jump@Apply(_, _)), unit@ExUnitLiteral()))) - if label.symbol == jump.symbol && unit.symbol == null => Some((cond, Block(body, unit))) - case _ => None - } - } - - object ExWhile { - object WithInvariant { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { - case Apply( - Select( - Apply(while2invariant, List(rest)), - invariantSym), - List(invariant)) if invariantSym.toString == "invariant" => Some((invariant, rest)) - case _ => None - } - } - - object WithWeakInvariant { - def unapply(tree: Tree): Option[(Tree, Tree)] = tree match { - case Apply( - Select( - Apply(while2invariant, List(rest)), - invariantSym), - List(invariant)) if invariantSym.toString == "noReturnInvariant" => Some((invariant, rest)) - case _ => None - } - } - - object WithInline { - def unapply(tree: Tree): Option[Tree] = tree match { - case Select( - Apply(_, List(rest)), - inlineSym - ) if inlineSym.toString == "inline" => Some(rest) - case _ => None - } - } - - object WithOpaque { - def unapply(tree: Tree): Option[Tree] = tree match { - case Select( - Apply(_, List(rest)), - opaqueSym - ) if opaqueSym.toString == "opaque" => Some(rest) - case _ => None - } - } - - - def parseWhile(tree: Tree, optInv: Option[Tree], optWeakInv: Option[Tree], inline: Boolean, opaque: Boolean): - Option[(Tree, Tree, Option[Tree], Option[Tree], Boolean, Boolean)] = { - - tree match { - case WithOpaque(rest) => parseWhile(rest, optInv, optWeakInv, inline, true) - case WithInline(rest) => parseWhile(rest, optInv, optWeakInv, true, opaque) - case WithInvariant(invariant, rest) => parseWhile(rest, Some(invariant), optWeakInv, inline, opaque) - case WithWeakInvariant(invariant, rest) => parseWhile(rest, optInv, Some(invariant), inline, opaque) - case ExBareWhile(cond, body) => Some((cond, body, optInv, optWeakInv, inline, opaque)) - case _ => None - } - } - - // returns condition, body, optional invariant and weak invariant, inline boolean, opaque boolean - def unapply(tree: Tree): Option[(Tree, Tree, Option[Tree], Option[Tree], Boolean, Boolean)] = - parseWhile(tree, None, None, false, false) - } - - object ExTuple { - def unapply(tree: Apply): Option[(Seq[Type], Seq[Tree])] = tree match { - case Apply(Select(New(tupleType), _), args) if isTuple(tupleType.symbol, args.size) => - tupleType.tpe match { - case TypeRef(_, _, tps) => Some(tps, args) - case _ => None - } - - // Match e1 -> e2 - case Apply(TypeApply(Select(Apply(TypeApply(ExSelected("scala", "Predef", "ArrowAssoc"), List(tpeFrom)), List(from)), ExNamed("$minus$greater")), List(tpeTo)), List(to)) => - Some((Seq(tpeFrom.tpe, tpeTo.tpe), Seq(from, to))) - - case Apply(TypeApply(e, tps), args) if - isTuple(e.symbol.owner.companionClass, args.size) && - e.symbol.name.toString == "apply" => Some((tps.map(_.tpe), args)) - - case _ => None - } - } - - object ExIdentity { - def unapply(tree: Apply) : Option[Tree] = tree match { - case Apply(TypeApply(ExSelected("scala", "Predef", "identity"), _), List(body)) => - Some(body) - case Apply(TypeApply(ExSelected("scala", "Predef", "locally"), _), List(body)) => - Some(body) - case _ => - None - } - } - - object ExTupleExtract { - def unapply(tree: Select) : Option[(Tree,Int)] = tree match { - case Select(lhs @ TupleSymbol(i), n) => - val methodName = n.toString - if(methodName.head == '_') { - val indexString = methodName.tail - try { - val index = indexString.toInt - if(index > 0 && index <= i) { - Some((lhs, index)) - } else None - } catch { - case t: Throwable => - None - } - } else None - case _ => None - } - } - - object ExIfThenElse { - def unapply(tree: If): Option[(Tree,Tree,Tree)] = tree match { - case If(t1,t2,t3) => Some((t1,t2,t3)) - } - } - - object ExBooleanLiteral { - def unapply(tree: Literal): Option[Boolean] = tree match { - case Literal(Constant(true)) => Some(true) - case Literal(Constant(false)) => Some(false) - case _ => None - } - } - - object ExCharLiteral { - def unapply(tree: Literal): Option[Char] = tree match { - case Literal(c @ Constant(i)) if c.tpe == CharTpe => Some(c.charValue) - case _ => None - } - } - - object ExInt8Literal { - def unapply(tree: Literal): Option[Byte] = tree match { - case Literal(c @ Constant(i)) if c.tpe == ByteTpe => Some(c.byteValue) - case _ => None - } - } - - object ExInt16Literal { - def unapply(tree: Literal): Option[Short] = tree match { - case Literal(c @ Constant(i)) if c.tpe == ShortTpe => Some(c.shortValue) - case _ => None - } - } - - object ExInt32Literal { - def unapply(tree: Literal): Option[Int] = tree match { - case Literal(c @ Constant(i)) if c.tpe == IntTpe => Some(c.intValue) - case _ => None - } - } - - object ExInt64Literal { - def unapply(tree: Literal): Option[Long] = tree match { - case Literal(c @ Constant(i)) if c.tpe == LongTpe => Some(c.longValue) - case _ => None - } - } - - object ExUnitLiteral { - def unapply(tree: Literal): Boolean = tree match { - case Literal(c @ Constant(_)) if c.tpe == UnitTpe => true - case _ => false - } - } - - object ExClassConstruction { - def unapply(tree: Tree): Option[(Type, Seq[Tree])] = tree match { - case Apply(s @ Select(New(tpt), n), args) if n == nme.CONSTRUCTOR => - Some((tpt.tpe, args)) - - case Apply(e, args) if ( - (e.symbol.owner.isModuleClass) && - (e.symbol.isSynthetic) && - (e.symbol.name.toString == "apply") - ) => Some((tree.tpe, args)) - - case _ => None - } - } - - object ExIdentifier { - def unapply(tree: Ident): Option[(Symbol,Tree)] = tree match { - case i: Ident => Some((i.symbol, i)) - } - } - - object ExTyped { - def unapply(tree : Typed): Option[(Tree,Tree)] = tree match { - case Typed(e,t) => Some((e,t)) - } - } - - object ExIntIdentifier { - def unapply(tree: Ident): Option[String] = tree match { - case i: Ident if i.symbol.tpe == IntClass.tpe => Some(i.symbol.name.toString) - case _ => None - } - } - - object ExAnd { - def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { - case Apply(s @ Select(lhs, _), List(rhs)) if s.symbol == Boolean_and => - Some((lhs,rhs)) - case _ => None - } - } - - object ExOr { - def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { - case Apply(s @ Select(lhs, _), List(rhs)) if s.symbol == Boolean_or => - Some((lhs,rhs)) - case _ => None - } - } - - object ExNot { - def unapply(tree: Select): Option[Tree] = tree match { - case Select(t, n) if n == nme.UNARY_! && hasBooleanType(t) => Some(t) - case _ => None - } - } - - object ExEquals { - def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { - case Apply(Select(lhs, n), List(rhs)) if n == nme.EQ => Some((lhs,rhs)) - case _ => None - } - } - - object ExNotEquals { - def unapply(tree: Apply): Option[(Tree,Tree)] = tree match { - case Apply(Select(lhs, n), List(rhs)) if n == nme.NE => Some((lhs,rhs)) - case _ => None - } - } - - object ExUMinus { - def unapply(tree: Select): Option[Tree] = tree match { - case Select(t, n) if n == nme.UNARY_- && hasNumericType(t) => Some(t) - case _ => None - } - } - - object ExBVNot { - def unapply(tree: Select): Option[Tree] = tree match { - case Select(t, n) if n == nme.UNARY_~ && hasBVType(t) => Some(t) - case _ => None - } - } - - object ExPatternMatching { - def unapply(tree: Match): Option[(Tree,List[CaseDef])] = - if(tree != null) Some((tree.selector, tree.cases)) else None - } - - object ExBigIntPattern { - def unapply(tree: UnApply): Option[Tree] = tree match { - case ua @ UnApply(Apply(ExSelected("stainless", "lang", "`package`", "BigInt", "unapply"), _), List(l)) => - Some(l) - case _ => - None - } - } - - object ExAsInstanceOf { - def unapply(tree: TypeApply) : Option[(Tree, Tree)] = tree match { - case TypeApply(Select(t, isInstanceOfName), typeTree :: Nil) if isInstanceOfName.toString == "asInstanceOf" => Some((t, typeTree)) - case _ => None - } - } - - object ExIsInstanceOf { - def unapply(tree: TypeApply) : Option[(Tree, Tree)] = tree match { - case TypeApply(Select(t, isInstanceOfName), typeTree :: Nil) if isInstanceOfName.toString == "isInstanceOf" => Some((t, typeTree)) - case _ => None - } - } - - object ExLiteralMap { - def unapply(tree: Apply): Option[(Tree, Tree, Seq[Tree])] = tree match { - case Apply(TypeApply(ExSelected("scala", "Predef", "Map", "apply"), fromTypeTree :: toTypeTree :: Nil), args) => - Some((fromTypeTree, toTypeTree, args)) - case _ => - None - } - } - object ExEmptyMap { - def unapply(tree: TypeApply): Option[(Tree, Tree)] = tree match { - case TypeApply(ExSelected("scala", "collection", "immutable", "Map", "empty"), fromTypeTree :: toTypeTree :: Nil) => - Some((fromTypeTree, toTypeTree)) - case TypeApply(ExSelected("scala", "Predef", "Map", "empty"), fromTypeTree :: toTypeTree :: Nil) => - Some((fromTypeTree, toTypeTree)) - case _ => - None - } - } - - object ExMutableMapWithDefault { - def unapply(tree: Apply): Option[(Tree,Tree,Tree)] = tree match { - case Apply(TypeApply(ExSelected("MutableMap", "withDefaultValue"), Seq(tptFrom, tptTo)), Seq(default)) => - Some(tptFrom, tptTo, default) - case Apply(TypeApply(ExSelected("stainless", "lang", "MutableMap", "withDefaultValue"), Seq(tptFrom, tptTo)), Seq(default)) => - Some(tptFrom, tptTo, default) - case _ => None - } - } - - object ExFiniteSet { - def unapply(tree: Apply): Option[(Tree,List[Tree])] = tree match { - case Apply(TypeApply(ExSelected("Set", "apply"), Seq(tpt)), args) => - Some(tpt, args) - case Apply(TypeApply(ExSelected("stainless", "lang", "Set", "apply"), Seq(tpt)), args) => - Some(tpt, args) - case _ => None - } - } - - object ExFiniteBag { - def unapply(tree: Apply): Option[(Tree, List[Tree])] = tree match { - case Apply(TypeApply(ExSelected("Bag", "apply"), Seq(tpt)), args) => - Some(tpt, args) - case Apply(TypeApply(ExSelected("stainless", "lang", "Bag", "apply"), Seq(tpt)), args) => - Some(tpt, args) - case _ => None - } - } - - object ExFiniteMap { - def unapply(tree: Apply): Option[(Tree, Tree, List[Tree])] = tree match { - case Apply(TypeApply(ExSelected("Map", "apply"), Seq(tptFrom, tptTo)), args) => - Some((tptFrom, tptTo, args)) - case Apply(TypeApply(ExSelected("stainless", "lang", "Map", "apply"), Seq(tptFrom, tptTo)), args) => - Some((tptFrom, tptTo, args)) - case _ => None - } - } - - object ExParameterLessCall { - def unapply(tree: Tree): Option[(Tree, Symbol, Seq[Tree])] = tree match { - case s @ Select(t, _) => - Some((t, s.symbol, Nil)) - - case TypeApply(s @ Select(t, _), tps) => - Some((t, s.symbol, tps)) - - case TypeApply(i: Ident, tps) => - Some((i, i.symbol, tps)) - - case _ => - None - } - } - - object ExAndThen { - def unapply(tree: Apply): Option[(Tree, Tree)] = tree match { - case Apply(TypeApply(sel@Select(lhs, _), _), List(rhs)) => - sel match { - case ExSymbol("scala", "Function1", "andThen") => Some((lhs, rhs)) - case _ => None - } - case _ => - None - } - } - - object ExCompose { - def unapply(tree: Apply): Option[(Tree, Tree)] = tree match { - case Apply(TypeApply(sel@Select(lhs, _), _), List(rhs)) => - sel match { - case ExSymbol("scala", "Function1", "compose") => Some((lhs, rhs)) - case _ => None - } - case _ => - None - } - } - - object ExCall { - def unapply(tree: Tree): Option[(Option[Tree], Symbol, Seq[Tree], Seq[Tree])] = { - val res = tree match { - // a.foo - case Select(qualifier, _) => - Some((Some(qualifier), tree.symbol, Nil, Nil)) - - // foo(args) - case Apply(id: Ident, args) => - Some((None, id.symbol, Nil, args)) - - // a.foo(args) - case Apply(s @ Select(qualifier, _), args) => - Some((Some(qualifier), s.symbol, Nil, args)) - - // foo[T] - case TypeApply(id: Ident, tps) => - Some((None, id.symbol, tps, Nil)) - - // a.foo[T] - case TypeApply(s @ Select(t, _), tps) => - Some((Some(t), s.symbol, tps, Nil)) - - case Apply(ExCall(caller, sym, tps, args), newArgs) => - Some((caller, sym, tps, args ++ newArgs)) - - case TypeApply(ExCall(caller, sym, tps, args), newTps) => - Some((caller, sym, tps ++ newTps, args)) - - case _ => None - } - - res.map { case (rec, sym, tps, args) => - val newRec = rec.filter { - case r if r.symbol == null => true - case r if (r.symbol.isModule || r.symbol.isModuleClass) && !r.symbol.isCase => false - case r => true - } - - (newRec, sym, tps, args) - } - } - } - } -} diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/CodeExtraction.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/CodeExtraction.scala deleted file mode 100644 index c1e994c74a..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/CodeExtraction.scala +++ /dev/null @@ -1,2120 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import ast.SymbolIdentifier -import frontend.UnsupportedCodeException -import extraction.xlang.{trees => xt} - -import scala.reflect.internal.util._ -import scala.collection.mutable.{Map => MutableMap, ListBuffer} - -import scala.language.implicitConversions - -/** - * Extract Scalac Trees into Stainless Trees. - * - * This trait builds a mapping between (scalac) symbols and (stainless/inox) identifiers - * to avoid generating two different identifiers for the same object/function/type/... - * when extracting each compilation unit on its own. See [[cache]] below. - * - * However, if an object/function/type/... is modified and recompiled, the produced identifier - * will be different in order to support incremental compilation. - */ -trait CodeExtraction extends ASTExtractors { - self: StainlessExtraction => - - import global._ - import global.definitions._ - import StructuralExtractors._ - import ExpressionExtractors._ - import scala.collection.immutable.Set - - lazy val ignoredClasses = Set( - ObjectClass.tpe, - SerializableClass.tpe, - ProductRootClass.tpe, - AnyRefClass.tpe, - AnyValClass.tpe, - ) - - def isIgnored(tp: Type): Boolean = { - // `normalize` ensures we go through aliases, such as scala.Serializable ~> java.io.Serializable - ignoredClasses.contains(tp.normalize) - } - - /** Extract the classes and functions from the given compilation unit. */ - def extractUnit(u: CompilationUnit): (xt.UnitDef, Seq[xt.ClassDef], Seq[xt.FunDef], Seq[xt.TypeDef]) = { - val (id, stats) = u.body match { - // package object - case PackageDef(refTree, List(pd @ PackageDef(inner, body))) => - (FreshIdentifier(extractRef(inner).mkString("$")), pd.stats) - - case pd @ PackageDef(refTree, lst) => - (FreshIdentifier(u.source.file.name.replaceFirst("[.][^.]+$", "")), pd.stats) - - case _ => - (FreshIdentifier(u.source.file.name.replaceFirst("[.][^.]+$", "")), List.empty) - } - - val (imports, classes, functions, typeDefs, subs, allClasses, allFunctions, allTypeDefs, _) = extractStatic(stats) - assert(functions.isEmpty, "Packages shouldn't contain functions") - assert(typeDefs.isEmpty, "Packages shouldn't contain type defintions") - - val unit = xt.UnitDef( - id, - imports, - classes, - subs, - !(Main.libraryFiles contains u.source.file.absolute.path) - ).setPos(u.body.pos) - - (unit, allClasses, allFunctions, allTypeDefs) - } - - private lazy val reporter = self.ctx.reporter - given givenDebugSection: frontend.DebugSectionExtraction.type = frontend.DebugSectionExtraction - - implicit def scalaPosToInoxPos(p: global.Position): inox.utils.Position = { - if (p == NoPosition) { - inox.utils.NoPosition - } else if (p.isRange) { - val start = p.focusStart - val end = p.focusEnd - inox.utils.RangePosition(start.line, start.column, start.point, - end.line, end.column, end.point, - p.source.file.file) - } else { - inox.utils.OffsetPosition(p.line, p.column, p.point, - p.source.file.file) - } - } - - def outOfSubsetError(pos: inox.utils.Position, msg: String) = { - throw frontend.UnsupportedCodeException(pos, msg) - } - - private def outOfSubsetError(pos: Position, msg: String) = { - throw new UnsupportedCodeException(pos, msg) - } - - private def outOfSubsetError(t: Tree, msg: String) = { - throw new UnsupportedCodeException(t.pos, msg) - } - - - /** - * Mapping from input symbols to output symbols/identifiers. - * - * In order to reuse the same stainless/inox identifiers, the [[cache]] - * need to be re-used between different runs of the compiler. Because - * different instances of the compiler (and [[Global]]) are used for - * each runs, the [[cache]] need to be stored outside the compiler. - */ - protected val cache: SymbolMapping - private def getIdentifier(sym: Symbol): SymbolIdentifier = cache fetch sym - - private def annotationsOf(sym: Symbol, ignoreOwner: Boolean = false): Seq[xt.Flag] = { - getAnnotations(sym, ignoreOwner) - .filter { case (name, _) => !name.startsWith("isabelle") } - .map { case (name, args) => - xt.extractFlag(name, args.map(extractTree(_)(using DefContext()))) - } - } - - private case class DefContext( - tparams: Map[Symbol, xt.TypeParameter] = Map(), - vars: Map[Symbol, () => xt.Expr] = Map(), - mutableVars: Map[Symbol, () => xt.Variable] = Map(), - localFuns: Map[Symbol, (Identifier, Seq[xt.TypeParameterDef], xt.FunctionType)] = Map(), - localClasses: Map[Identifier, xt.LocalClassDef] = Map(), - isExtern: Boolean = false, - resolveTypes: Boolean = false, - wrappingArithmetic: Boolean = false, - ){ - def union(that: DefContext) = { - copy(this.tparams ++ that.tparams, - this.vars ++ that.vars, - this.mutableVars ++ that.mutableVars, - this.localFuns ++ that.localFuns, - this.localClasses ++ that.localClasses, - this.isExtern || that.isExtern, - this.resolveTypes || that.resolveTypes, - this.wrappingArithmetic || that.wrappingArithmetic) - } - - def isVariable(s: Symbol) = (vars contains s) || (mutableVars contains s) - - def withNewTypeParams(ntparams: Iterable[(Symbol, xt.TypeParameter)]) = { - copy(tparams = tparams ++ ntparams) - } - - def withNewTypeParam(tparam: (Symbol, xt.TypeParameter)) = { - copy(tparams = tparams + tparam) - } - - def withNewVars(nvars: Iterable[(Symbol, () => xt.Expr)]) = { - copy(vars = vars ++ nvars) - } - - def withNewVar(s: Symbol, v: () => xt.Variable) = { - copy(vars = vars + (s -> v)) - } - - def withNewMutableVar(s: Symbol, v: () => xt.Variable) = { - copy(mutableVars = mutableVars + (s -> v)) - } - - def withLocalFun(sym: Symbol, id: Identifier, tparams: Seq[xt.TypeParameterDef], tpe: xt.FunctionType) = { - copy(localFuns = this.localFuns + (sym -> ((id, tparams, tpe)))) - } - - def withLocalClass(lcd: xt.LocalClassDef) = { - copy(localClasses = this.localClasses + (lcd.id -> lcd)) - } - - def setResolveTypes(resolveTypes: Boolean) = { - copy(resolveTypes = resolveTypes) - } - - def setWrappingArithmetic(wrappingArithmetic: Boolean) = { - copy(wrappingArithmetic = wrappingArithmetic) - } - } - - // This one never fails, on error, it returns Untyped - private def stainlessType(tpt: Type)(using dctx: DefContext, pos: Position): xt.Type = { - try { - extractType(tpt) - } catch { - case e: UnsupportedCodeException => - reporter.debug(e.pos, "[ignored] " + e.getMessage, e) - xt.Untyped - } - } - - private def extractStatic(stats: List[Tree]): ( - Seq[xt.Import], - Seq[Identifier], // classes - Seq[Identifier], // functions - Seq[Identifier], // typeDefs - Seq[xt.ModuleDef], - Seq[xt.ClassDef], - Seq[xt.FunDef], - Seq[xt.TypeDef], - Option[Identifier] - ) = { - var imports : Seq[xt.Import] = Seq.empty - var classes : Seq[Identifier] = Seq.empty - var functions : Seq[Identifier] = Seq.empty - var typeDefs : Seq[Identifier] = Seq.empty - var subs : Seq[xt.ModuleDef] = Seq.empty - - var allClasses : Seq[xt.ClassDef] = Seq.empty - var allFunctions : Seq[xt.FunDef] = Seq.empty - var allTypeDefs : Seq[xt.TypeDef] = Seq.empty - - var companionOf : Option[Identifier] = None - - for (d <- stats) d match { - case EmptyTree => - // ignore - - case l: Literal => - // top level literal are ignored - - case t if annotationsOf(t.symbol) contains xt.Ignore => - // ignore - - case ExtractorHelpers.ExSymbol("stainless", "annotation", "ignore") => - // ignore (can't be @ignored because of the dotty compiler) - - case ExConstructorDef() => - // ignore - - case i @ Import(_, _) => - imports ++= extractImports(i) - - case pd @ PackageDef(ref, stats) => - val (imports, classes, functions, typeDefs, modules, newClasses, newFunctions, newTypeDefs, _) = extractStatic(stats) - val pid = FreshIdentifier(extractRef(ref).mkString("$")) - subs :+= xt.ModuleDef(pid, imports, classes, functions, typeDefs, modules) - allClasses ++= newClasses - allFunctions ++= newFunctions - allTypeDefs ++= newTypeDefs - - case td @ ExObjectDef(_, _) => - val (obj, newClasses, newFunctions, newTypeDefs, companionOf) = extractObject(td) - subs :+= obj - allClasses ++= newClasses - allFunctions ++= newFunctions.map { fd => - if ( - fd.id.name.startsWith("apply$default") && - !fd.flags.exists(_.isInstanceOf[xt.ClassParamInit]) - ) { - fd.copy(flags = (xt.ClassParamInit(companionOf.get) +: fd.flags)).setPos(fd) - } else { - fd - } - } - allTypeDefs ++= newTypeDefs - - case t @ DefDef(_, name, _, _, tpt, _) - if t.symbol.isSynthetic && - !canExtractSynthetic(t.symbol) && - name.toString == "apply" => - extractType(tpt)(using DefContext()) match { - case xt.ClassType(cid, _) => - assert(companionOf.forall(_ != cid), - s"Error during Stainless extraction, couldn't tie companion object to class: $cid" - ) - companionOf = Some(cid) - case _ => - } - - case t@ExFieldDef(fsym, _, rhs) => - val fd = extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using DefContext()) - functions :+= fd.id - allFunctions :+= fd - - case t@ExLazyFieldDef(fsym, _, rhs) => - val fd = extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using DefContext()) - functions :+= fd.id - allFunctions :+= fd - - // this case goes after `ExObjectDef` in order to explore synthetic objects that may contain - // field initializers - case t if t.symbol.isSynthetic && !canExtractSynthetic(t.symbol) => - // ignore - - case md: ModuleDef if !md.symbol.isSynthetic && md.symbol.isCase => - val (xcd, newFunctions, newTypeDefs) = extractClass(md)(using DefContext()) - classes :+= xcd.id - allClasses :+= xcd - allFunctions ++= newFunctions - allTypeDefs ++= newTypeDefs - - case cd: ClassDef => - val (xcd, newFunctions, newTypeDefs) = extractClass(cd)(using DefContext()) - classes :+= xcd.id - allClasses :+= xcd - allFunctions ++= newFunctions - allTypeDefs ++= newTypeDefs - - case dd @ ExFunctionDef(fsym, tparams, vparams, tpt, rhs) => - val fd = extractFunction(fsym, tparams, vparams, rhs)(using DefContext()) - functions :+= fd.id - allFunctions :+= fd - - case t @ ExMutableFieldDef(fsym, _, _) if fsym.isMutable && annotationsOf(fsym).contains(xt.Extern) => - // Ignore @extern variables in static context - - case t @ ExFieldAccessorFunction(fsym, _, vparams, rhs) => - val fd = extractFunction(fsym, Seq.empty, vparams, rhs)(using DefContext()) - functions :+= fd.id - allFunctions :+= fd - - case t @ ExLazyFieldAccessorFunction(fsym, _, rhs) => - val fd = extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using DefContext()) - functions :+= fd.id - allFunctions :+= fd - - case t if t.symbol.isSynthetic => - // ignore - - case t: TypeDef if t.symbol.isAliasType => - val td = extractTypeDef(t)(using DefContext()) - typeDefs :+= td.id - allTypeDefs :+= td - - case t @ ExMutableFieldDef(_, _, _) => - outOfSubsetError(t, "Mutable fields in static containers such as objects are not supported") - - case other => - reporter.warning(other.pos, s"Stainless does not support the following tree in static containers:\n$other") - } - - (imports, classes, functions, typeDefs, subs, allClasses, allFunctions, allTypeDefs, companionOf) - } - - private def extractTypeDef(td: TypeDef)(using dctx: DefContext): xt.TypeDef = { - val sym = td.symbol - val id = getIdentifier(sym) - val flags = annotationsOf(sym) ++ - (if (sym.isAbstractType) Some(xt.IsAbstract) else None) - - val tparamsSyms = sym.tpe match { - case TypeRef(_, _, tps) => typeParamSymbols(tps) - case _ => Nil - } - - val tparams = extractTypeParams(tparamsSyms) - - val tpCtx = dctx.withNewTypeParams(tparamsSyms zip tparams) - val body = extractType(td.rhs)(using tpCtx) match { - case xt.TypeBounds(lo, hi, fls) => xt.TypeBounds(lo, hi, fls ++ flags.filterNot(_ == xt.IsAbstract)) - case tp => tp - } - - new xt.TypeDef( - id, - tparams.map(xt.TypeParameterDef(_)), - body, - flags - ) - } - - private def extractObject(obj: ModuleDef): (xt.ModuleDef, Seq[xt.ClassDef], Seq[xt.FunDef], Seq[xt.TypeDef], Option[Identifier]) = { - val ExObjectDef(_, template) = obj: @unchecked - - val (imports, classes, functions, typeDefs, subs, allClasses, allFunctions, allTypeDefs, companionOf) = extractStatic(template.body) - - val module = xt.ModuleDef( - getIdentifier(obj.symbol), - imports, - classes, - functions, - typeDefs, - subs - ).setPos(obj.pos) - - (module, allClasses, allFunctions, allTypeDefs, companionOf) - } - - private def extractClass(cd: ImplDef)(using dctx: DefContext): (xt.ClassDef, Seq[xt.FunDef], Seq[xt.TypeDef]) = { - val sym = cd.symbol - val id = getIdentifier(sym.moduleClass.orElse(sym)) - - val isValueClass = cd.impl.parents.map(_.tpe).exists(_ == AnyValClass.tpe) - - val annots = annotationsOf(sym) - val flags = annots ++ - (if (isValueClass) Some(xt.ValueClass) else None) ++ - (if (sym.isAbstractClass) Some(xt.IsAbstract) else None) ++ - (if (sym.isSealed) Some(xt.IsSealed) else None) ++ - (if (sym.tpe.typeSymbol.isModuleClass && sym.isCase) Some(xt.IsCaseObject) else None) - - val tparamsSyms = sym.tpe match { - case TypeRef(_, _, tps) => typeParamSymbols(tps) - case _ => Nil - } - - val tparams = extractTypeParams(tparamsSyms)(using DefContext()) - - val tpCtx = dctx.copy(tparams = dctx.tparams ++ (tparamsSyms zip tparams).toMap) - - val parents = cd.impl.parents.flatMap(p => p.tpe match { - case tpe if isIgnored(tpe) => None - case tpe if tpe =:= ThrowableTpe && (flags exists (_.name == "library")) => None - case tp @ TypeRef(_, _, _) => - extractType(tp)(using tpCtx, p.pos) match { - case ct: xt.ClassType => Some(ct) - case lct: xt.LocalClassType => Some(lct.toClassType) - case _ => None - } - case _ => None - }) - - val constructorOpt = cd.impl.children.find { - case ExConstructorDef() => true - case _ => false - }.asInstanceOf[Option[DefDef]] - - val vds = cd.impl.children.collect { - case vd: ValDef if !sym.isImplicit && !vd.symbol.isAccessor && vd.symbol.isCaseAccessor && vd.symbol.isParamAccessor => vd - case vd: ValDef if sym.isImplicit && !vd.symbol.isAccessor && vd.symbol.isParamAccessor => vd - } - - val fields = vds map { vd => - val sym = vd.symbol - val id = getIdentifier(sym) - val flags = annotationsOf(sym, ignoreOwner = true) - val tpe = stainlessType(vd.tpt.tpe)(using tpCtx, vd.pos) - val (isExtern, isPure) = (flags contains xt.Extern, flags contains xt.IsPure) - val isMutable = sym.isVar || isExtern && !isPure - - (if (isMutable) xt.VarDef(id, tpe, flags) else xt.ValDef(id, tpe, flags)).setPos(sym.pos) - } - - val hasExternFields = fields.exists(_.flags.contains(xt.Extern)) - - val defCtx = tpCtx.withNewVars((vds.map(_.symbol) zip fields.map(vd => () => vd.toVariable)).toMap) - - var invariants: Seq[xt.Expr] = Seq.empty - var methods: Seq[xt.FunDef] = Seq.empty - var typeMembers: Seq[xt.TypeDef] = Seq.empty - - for ((d, i) <- cd.impl.body.zipWithIndex) d match { - case EmptyTree => - // ignore - - case i: Import => - // ignore - - case t if annotationsOf(t.symbol).contains(xt.Ignore) && !t.symbol.isCaseAccessor => - // ignore - - case t if t.symbol.isSynthetic && !canExtractSynthetic(t.symbol) => - // ignore - - case ExCaseClassSyntheticJunk() | ExConstructorDef() => - // ignore - - case ExRequiredExpression(body, isStatic) => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - invariants :+= wrap(extractTree(body)(using defCtx)) - - case t @ ExFunctionDef(fsym, _, _, _, _) - if hasExternFields && (isCopyMethod(fsym) || isDefaultGetter(fsym)) => - // we cannot extract copy method if the class has ignored fields as - // the type of copy and the getters mention what might be a type we - // cannot extract. - - case t @ ExFunctionDef(fsym, tparams, vparams, tpt, rhs) => - methods :+= extractFunction(fsym, tparams, vparams, rhs)(using defCtx) - - case t @ ExFieldDef(fsym, _, rhs) => - val fd = extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using defCtx) - methods :+= fd.copy(flags = fd.flags :+ xt.FieldDefPosition(i)) - - case t @ ExLazyFieldDef(fsym, _, rhs) => - methods :+= extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using defCtx) - - case t @ ExFieldAccessorFunction(fsym, _, vparams, rhs) => - methods :+= extractFunction(fsym, Seq.empty, vparams, rhs)(using defCtx) - - case t @ ExLazyFieldAccessorFunction(fsym, _, rhs) => - methods :+= extractFunction(fsym, Seq.empty, Seq.empty, rhs)(using defCtx) - - case t @ ExMutableFieldDef(_, _, rhs) if rhs != EmptyTree => - outOfSubsetError(t, "Mutable fields in traits or abstract classes cannot have default values") - - case vd @ ExMutableFieldDef(sym, _, _) if vd.symbol.owner.isAbstract || vd.symbol.owner.isTrait => - methods :+= extractFunction(sym, Seq.empty, Seq.empty, EmptyTree)(using defCtx) - - case t: TypeDef => - val td = extractTypeDef(t)(using defCtx) - typeMembers :+= td - - case ValDef(_, _, _, _) => - // ignore (corresponds to constructor fields) - - case d if d.symbol.isSynthetic => - // ignore - - case d if d.symbol.isVar => - // ignore - - case other => - reporter.warning(other.pos, s"In class $id, Stainless does not support:\n$other") - } - - val optInv = if (invariants.isEmpty) None else Some({ - val id = cache fetchInvIdForClass sym - val pos = inox.utils.Position.between(invariants.map(_.getPos).min, invariants.map(_.getPos).max) - new xt.FunDef(id, Seq.empty, Seq.empty, xt.BooleanType().setPos(pos), - if (invariants.size == 1) invariants.head else xt.And(invariants).setPos(pos), - (Seq(xt.IsInvariant) ++ - annots.filterNot(annot => - annot == xt.IsMutable || - annot.name == "cCode.export" || - annot.name.startsWith("cCode.global") - ) - ).distinct - ).setPos(pos) - }) - - val allMethods = (methods ++ optInv).map(fd => fd.copy(flags = fd.flags :+ xt.IsMethodOf(id))) - val allTypeMembers = typeMembers.map(td => td.copy(flags = td.flags :+ xt.IsTypeMemberOf(id))) - - val xcd = new xt.ClassDef( - id, - tparams.map(tp => xt.TypeParameterDef(tp)), - parents, - fields, - flags - ).setPos(sym.pos) - - (xcd, allMethods, allTypeMembers) - } - - private def getSelectChain(e: Tree): List[String] = { - def rec(e: Tree): List[Name] = e match { - case Select(q, name) => name :: rec(q) - case Ident(name) => List(name) - case EmptyTree => List() - case This(sym) => List(sym) - case _ => - ctx.reporter.internalError("getSelectChain: unexpected Tree:\n" + e.toString) - } - rec(e).reverseIterator.map(_.toString).toList - } - - private def extractRef(ref: RefTree): List[String] = { - (getSelectChain(ref.qualifier) :+ ref.name.toString).filter(_ != "") - } - - private def extractImports(i: Import): Seq[xt.Import] = { - val Import(expr, sels) = i - - val prefix = getSelectChain(expr) - - val allSels = sels map { prefix :+ _.name.toString } - - // Make a different import for each selector at the end of the chain - allSels flatMap { selectors => - assert(selectors.nonEmpty, "selectors is actually empty") - val (thePath, isWild) = selectors.last match { - case "_" => (selectors.dropRight(1), true) - case _ => (selectors, false) - } - - Some(xt.Import(thePath, isWild)) - } - } - - private def extractFunction( - sym: Symbol, - tparams: Seq[Symbol], - vparams: Seq[ValDef], - rhs: Tree, - typeParams: Option[Seq[xt.TypeParameter]] = None - )(using dctx: DefContext): xt.FunDef = { - - // Type params of the function itself - val extparams = typeParamSymbols(sym.typeParams.map(_.tpe)) - val ntparams = typeParams.getOrElse(extractTypeParams(extparams)) - - val tctx = dctx.copy(tparams = dctx.tparams ++ (extparams zip ntparams).toMap) - - val (newParams, nctx) = sym.info.paramss.flatten.foldLeft((Seq.empty[xt.ValDef], tctx)) { - case ((vds, vctx), sym) => - val ptpe = stainlessType(sym.tpe)(using vctx, sym.pos) - val tpe = if (sym.isByNameParam) xt.FunctionType(Seq(), ptpe).setPos(sym.pos) else ptpe - val flags = annotationsOf(sym, ignoreOwner = true) - val vd = xt.ValDef(getIdentifier(sym), tpe, flags).setPos(sym.pos) - val expr = if (sym.isByNameParam) { - () => xt.Application(vd.toVariable, Seq()).setPos(vd) - } else { - () => vd.toVariable - } - (vds :+ vd, vctx.withNewVar(sym, () => vd.toVariable)) - } - - val id = getIdentifier(sym) - val isAbstract = rhs == EmptyTree - - var flags = annotationsOf(sym).filterNot(annot => annot == xt.IsMutable || annot.name == "inlineInvariant") ++ - (if (sym.isImplicit && sym.isSynthetic) Seq(xt.Inline, xt.Synthetic) else Seq()) ++ - (if (sym.isPrivate) Seq(xt.Private) else Seq()) ++ - (if (sym.isFinal) Seq(xt.Final) else Seq()) ++ - (if (sym.isVal || sym.isLazy) Seq(xt.IsField(sym.isLazy)) else Seq()) ++ - (if (isDefaultGetter(sym) || isCopyMethod(sym)) Seq(xt.Synthetic, xt.Inline) else Seq()) ++ - (if (!sym.isLazy && sym.isAccessor) - Seq(xt.IsAccessor(Option(getIdentifier(sym.accessedOrSelf)).filterNot(_ => isAbstract))) - else Seq()) - - if (sym.name == nme.unapply) { - def matchesParams(member: Symbol) = member.paramss match { - case Nil => true - // TODO: Original is ... && (ps corresponds Seq())(_.tpe =:= _) but that's just equivalent to ... && ps.isEmpty ? - case ps :: rest => (rest.isEmpty || isImplicitParamss(rest)) && ps.isEmpty - } - val isEmptySym = sym.info.finalResultType member nme.isEmpty filter matchesParams - val getSym = sym.info.finalResultType member nme.get filter matchesParams - flags :+= xt.IsUnapply(getIdentifier(isEmptySym), getIdentifier(getSym)) - } - - val body = rhs - - val paramsMap = (vparams.map(_.symbol) zip newParams).map { case (s, vd) => - s -> (if (s.isByNameParam) () => xt.Application(vd.toVariable, Seq()).setPos(vd.toVariable) else () => vd.toVariable) - }.toMap - - val fctx = nctx - .withNewVars(paramsMap) - .copy(tparams = dctx.tparams ++ (tparams zip ntparams)) - .copy(isExtern = dctx.isExtern || (flags contains xt.Extern)) - - lazy val retType = stainlessType(sym.info.finalResultType)(using nctx, sym.pos) - - val (finalBody, returnType) = if (isAbstract) { - flags :+= xt.IsAbstract - (xt.NoTree(retType).setPos(sym.pos), retType) - } else { - val fullBody = xt.exprOps.flattenBlocks(extractTreeOrNoTree(body)(using fctx)) - val localClasses = xt.exprOps.collect[xt.LocalClassDef] { - case xt.LetClass(lcds, _) => lcds.toSet - case _ => Set() - } (fullBody) - - if (localClasses.isEmpty) (fullBody, retType) - else { - // If the function contains local classes, we need to add those to the - // context in order to type its body. - val tctx = localClasses.toSeq.foldLeft(nctx)(_ withLocalClass _) - - val returnType = stainlessType(sym.info.finalResultType)(using tctx, sym.pos) - val bctx = fctx.copy(localClasses = fctx.localClasses ++ tctx.localClasses) - // FIXME: `flattenBlocks` should not change the positions that appear in `ntparams` - (xt.exprOps.flattenBlocks(extractTreeOrNoTree(body)(using bctx)), returnType) - } - } - - // For @extern function, check that their extracted body does not contain further specs that couldn't be extracted out. - // For instance, this is fine: - // @extern - // def f(x: BigInt): Unit = { - // require(x >= 10) - // val t = x + 3 - // } - // the `require(x >= 10)` will be recognized and treated as a precondition, and the extracted body (val t = x + 3), - // does not contain any further specs. - // On the other hand, the following will be rejected: - // @extern - // def f(x: BigInt): Unit = { - // var t = x - // t += 1 - // require(t >= 10) - // } - // The `require(t >= 10)` won't be recognized as a precondition (due to the presence of impure constructs) - // even though that is the intent. As such, it would be wise to reject the program. - object KeywordChecker extends xt.ConcreteStainlessSelfTreeTraverser { - override def traverse(e: xt.Expr) = { - e match { - case _: xt.Require => - outOfSubsetError(e.getPos, s"This require does not appear at the top-level of this @extern function.") - case _: xt.Ensuring => - outOfSubsetError(e.getPos, s"This ensuring does not appear not at the top-level of this @extern function.") - case _: xt.Reads => - outOfSubsetError(e.getPos, s"This reads does not appear at the top-level of this @extern function.") - case _: xt.Modifies => - outOfSubsetError(e.getPos, s"This modifies does not appear at the top-level of this @extern function.") - case _ => - () - } - super.traverse(e) - } - } - - val fullBody = if (fctx.isExtern) { - val specced = xt.exprOps.BodyWithSpecs(finalBody) - specced.bodyOpt foreach { KeywordChecker.traverse } - specced.withBody(xt.NoTree(returnType)).reconstructed.setPos(body.pos) - } else { - finalBody - } - - new xt.FunDef( - id, - ntparams.map(tp => xt.TypeParameterDef(tp)), - newParams, - returnType, - fullBody, - flags.distinct - ).setPos(sym.pos) - } - - private def typeParamSymbols(tps: Seq[Type]): Seq[Symbol] = tps.flatMap { - case TypeRef(_, sym, Nil) => - Some(sym) - case t => - outOfSubsetError(t.typeSymbol.pos, "Unhandled type for parameter: "+t) - None - } - - private def extractTypeParams(syms: Seq[Symbol])(using dctx: DefContext): Seq[xt.TypeParameter] = { - syms.foldLeft((dctx, Seq[xt.TypeParameter]())) { case ((dctx, tparams), sym) => - val variance = - if (sym.isCovariant) Some(xt.Variance(true)) - else if (sym.isContravariant) Some(xt.Variance(false)) - else None - - val bounds = sym.info match { - case TypeBounds(lo, hi) => - val (loType, hiType) = (extractType(lo)(using dctx, sym.pos), extractType(hi)(using dctx, sym.pos)) - if (loType != xt.NothingType() || hiType != xt.AnyType()) Some(xt.Bounds(loType, hiType)) - else None - case _ => None - } - - val flags = annotationsOf(sym, ignoreOwner = true) - val tp = xt.TypeParameter(getIdentifier(sym), flags ++ variance.toSeq ++ bounds).setPos(sym.pos) - (dctx.copy(tparams = dctx.tparams + (sym -> tp)), tparams :+ tp) - }._2 - } - - private def extractPattern(p: Tree, binder: Option[xt.ValDef] = None)(using dctx: DefContext): (xt.Pattern, DefContext) = p match { - case b @ Bind(name, t @ Typed(pat, tpt)) => - val vd = xt.ValDef(FreshIdentifier(name.toString), extractType(tpt), annotationsOf(b.symbol, ignoreOwner = true)).setPos(b.pos) - val pctx = dctx.withNewVar(b.symbol, () => vd.toVariable) - extractPattern(t, Some(vd))(using pctx) - - case b @ Bind(name, pat) => - val vd = xt.ValDef(FreshIdentifier(name.toString), extractType(b), annotationsOf(b.symbol, ignoreOwner = true)).setPos(b.pos) - val pctx = dctx.withNewVar(b.symbol, () => vd.toVariable) - extractPattern(pat, Some(vd))(using pctx) - - case t @ Typed(Ident(nme.WILDCARD), tpt) => - extractType(tpt) match { - case ct: xt.ClassType => - (xt.InstanceOfPattern(binder, ct).setPos(p.pos), dctx) - - case lt => - outOfSubsetError(tpt, "Invalid type "+tpt.tpe+" for .isInstanceOf") - } - - case Ident(nme.WILDCARD) => - (xt.WildcardPattern(binder).setPos(p.pos), dctx) - - case s @ Select(_, b) if s.tpe.typeSymbol.isCase => - extractType(s) match { - case ct: xt.ClassType => - (xt.ClassPattern(binder, ct, Seq()).setPos(p.pos), dctx) - case _ => - outOfSubsetError(s, "Invalid instance pattern: " + s) - } - - case id @ Ident(_) if id.tpe.typeSymbol.isCase => - extractType(id) match { - case ct: xt.ClassType => - (xt.ClassPattern(binder, ct, Seq()).setPos(p.pos), dctx) - case _ => - outOfSubsetError(id, "Invalid instance pattern: " + id) - } - - case a @ Apply(fn, args) => - extractType(a) match { - case ct: xt.ClassType => - val (subPatterns, subDctx) = args.map(extractPattern(_)).unzip - val nctx = subDctx.foldLeft(dctx)(_ union _) - (xt.ClassPattern(binder, ct, subPatterns).setPos(p.pos), nctx) - - case xt.TupleType(argsTpes) => - val (subPatterns, subDctx) = args.map(extractPattern(_)).unzip - val nctx = subDctx.foldLeft(dctx)(_ union _) - (xt.TuplePattern(binder, subPatterns).setPos(p.pos), nctx) - - case _ => - outOfSubsetError(a, "Invalid type "+a.tpe+" for .isInstanceOf") - } - - case ExBigIntPattern(n: Literal) => - val lit = xt.IntegerLiteral(BigInt(n.value.stringValue)) - (xt.LiteralPattern(binder, lit), dctx) - - case ExInt8Literal(i) => (xt.LiteralPattern(binder, xt.Int8Literal(i)), dctx) - case ExInt16Literal(i) => (xt.LiteralPattern(binder, xt.Int16Literal(i)), dctx) - case ExInt32Literal(i) => (xt.LiteralPattern(binder, xt.Int32Literal(i)), dctx) - case ExInt64Literal(i) => (xt.LiteralPattern(binder, xt.Int64Literal(i)), dctx) - case ExBooleanLiteral(b) => (xt.LiteralPattern(binder, xt.BooleanLiteral(b)), dctx) - case ExUnitLiteral() => (xt.LiteralPattern(binder, xt.UnitLiteral()), dctx) - case ExStringLiteral(s) => (xt.LiteralPattern(binder, xt.StringLiteral(s)), dctx) - - case up @ ExUnapplyPattern(t, args) => - val (sub, ctx) = args.map (extractPattern(_)).unzip - val nctx = ctx.foldLeft(dctx)(_ union _) - val id = getIdentifier(t.symbol) - val tps = t match { - case TypeApply(_, tps) => tps.map(extractType) - case _ => Seq.empty - } - - (xt.UnapplyPattern(binder, Seq(), id, tps, sub).setPos(up.pos), ctx.foldLeft(dctx)(_ union _)) - - case _ => - outOfSubsetError(p, "Unsupported pattern: " + p) - } - - private def extractMatchCase(cd: CaseDef)(using DefContext): xt.MatchCase = { - val (recPattern, ndctx) = extractPattern(cd.pat) - val recBody = extractTree(cd.body)(using ndctx) - - if(cd.guard == EmptyTree) { - xt.MatchCase(recPattern, None, recBody).setPos(cd.pos) - } else { - val recGuard = extractTree(cd.guard)(using ndctx) - xt.MatchCase(recPattern, Some(recGuard), recBody).setPos(cd.pos) - } - } - - private def extractTreeOrNoTree(tr: Tree)(using dctx: DefContext): xt.Expr = { - try { - extractTree(tr) - } catch { - case e: UnsupportedCodeException => - if (dctx.isExtern) { - checkNoSpecsRemaining(tr) - xt.NoTree(extractType(tr)).setPos(tr.pos) - } else { - throw e - } - } - } - - private def extractBlock(es: List[Tree])(using dctx: DefContext): xt.Expr = { - val fctx = es.collect { - case ExFunctionDef(sym, tparams, vparams, tpt, rhs) => (sym, tparams) - }.foldLeft(dctx) { case (dctx, (sym, tparams)) => - val extparams = typeParamSymbols(sym.typeParams.map(_.tpe)) - val tparams = extractTypeParams(extparams)(using dctx) - val nctx = dctx.copy(tparams = dctx.tparams ++ (extparams zip tparams).toMap) - - val tparamDefs = tparams.map(tp => xt.TypeParameterDef(tp).copiedFrom(tp)) - - val tpe = xt.FunctionType( - sym.info.paramss.flatten.map { sym => - val ptpe = stainlessType(sym.tpe)(using nctx, sym.pos) - if (sym.isByNameParam) xt.FunctionType(Seq(), ptpe).setPos(sym.pos) else ptpe - }, - stainlessType(sym.info.finalResultType)(using nctx, sym.pos) - ).setPos(sym.pos) - - dctx.withLocalFun(sym, getIdentifier(sym), tparamDefs, tpe) - } - - val (vds, vctx) = es.collect { - case v @ ValDef(_, name, tpt, _) => (v.symbol, name, tpt) - }.foldLeft((Map.empty[Symbol, xt.ValDef], fctx)) { case ((vds, dctx), (sym, name, tpt)) => - if (!sym.isMutable) { - val laziness = if (sym.isLazy) Some(xt.Lazy) else None - val vd = xt.ValDef(FreshIdentifier(name.toString), extractType(tpt)(using dctx), annotationsOf(sym, ignoreOwner = true) ++ laziness).setPos(sym.pos) - (vds + (sym -> vd), dctx.withNewVar(sym, () => vd.toVariable)) - } else { - val vd = xt.VarDef(FreshIdentifier(name.toString), extractType(tpt)(using dctx), annotationsOf(sym, ignoreOwner = true)).setPos(sym.pos) - (vds + (sym -> vd), dctx.withNewMutableVar(sym, () => vd.toVariable)) - } - } - - val (lcds, cctx) = es.collect { - case cd: ClassDef => cd - }.foldLeft((Map.empty[Symbol, xt.LocalClassDef], vctx)) { case ((lcds, dctx), cd) => - val (xcd, methods, typeDefs) = extractClass(cd)(using dctx) - val lcd = xt.LocalClassDef(xcd, methods, typeDefs).setPos(xcd) - (lcds + (cd.symbol -> lcd), dctx.withLocalClass(lcd)) - } - - def recOrNoTree(es: List[Tree]): xt.Expr = { - try { - rec(es) - } catch { - case e: frontend.UnsupportedCodeException => - if (dctx.isExtern) { - es.foreach(checkNoSpecsRemaining) - xt.NoTree(extractType(es.last)).setPos(es.last.pos) - } else { - throw e - } - } - } - - def rec(es: List[Tree]): xt.Expr = es match { - case Nil => xt.UnitLiteral() - - case (i: Import) :: xs => recOrNoTree(xs) - - case (e @ ExAssertExpression(contract, oerr, isStatic)) :: xs => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - val const = extractTree(contract)(using cctx) - val b = recOrNoTree(xs) - xt.Assert(wrap(const), oerr, b).setPos(e.pos) - - case (e @ ExRequiredExpression(contract, isStatic)) :: xs => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - val pre = extractTree(contract)(using cctx) - val b = recOrNoTree(xs) - xt.Require(wrap(pre), b).setPos(e.pos) - - case (e @ ExDecreasesExpression(ranks)) :: xs => - val rs = ranks.map(extractTree(_)(using cctx)) - val b = recOrNoTree(xs) - xt.Decreases(xt.tupleWrap(rs), b).setPos(e.pos) - - case (e @ ExReadsExpression(objs)) :: xs => - xt.Reads(extractTree(objs)(using cctx), recOrNoTree(xs)).setPos(e.pos) - - case (e @ ExModifiesExpression(objs)) :: xs => - xt.Modifies(extractTree(objs)(using cctx), recOrNoTree(xs)).setPos(e.pos) - - case (d @ ExFunctionDef(sym, tparams, vparams, tpt, rhs)) :: xs => - val (id, tdefs, tpe) = cctx.localFuns(sym) - val fd = extractFunction(sym, tparams, vparams, rhs, typeParams = Some(tdefs.map(_.tp)))(using cctx) - val letRec = xt.LocalFunDef(id, tdefs, fd.params, fd.returnType, fd.fullBody, fd.flags).setPos(d.pos) - - recOrNoTree(xs) match { - case xt.LetRec(defs, body) => xt.LetRec(letRec +: defs, body).setPos(d.pos) - case other => xt.LetRec(Seq(letRec), other).setPos(d.pos) - } - - case (cd: ClassDef) :: xs => - val lcd = lcds(cd.symbol) - - // Drop companion object and/or synthetic modules Scalac inserts after local class declarations - val rest = xs dropWhile (x => x.symbol != null && x.symbol.isSynthetic && x.symbol.isModule) - recOrNoTree(rest) match { - case xt.LetClass(defs, body) => xt.LetClass(lcd +: defs, body).setPos(cd.pos) - case other => xt.LetClass(Seq(lcd), other).setPos(cd.pos) - } - - case (v @ ValDef(mods, name, tpt, _)) :: xs => - if (mods.isMutable) { - xt.LetVar(vds(v.symbol), extractTree(v.rhs)(using cctx), recOrNoTree(xs)).setPos(v.pos) - } else { - xt.Let(vds(v.symbol), extractTree(v.rhs)(using cctx), recOrNoTree(xs)).setPos(v.pos) - } - - case x :: Nil => - extractTree(x)(using cctx) - - case (x @ Block(_, _)) :: rest => - val re = recOrNoTree(rest) - val (elems, last) = re match { - case xt.Block(elems, last) => (elems, last) - case e => (Seq(), e) - } - - extractTree(x)(using vctx) match { - case xt.Decreases(m, b) => xt.Decreases(m, xt.Block(b +: elems, last).setPos(re)).setPos(x.pos) - case xt.Require(pre, b) => xt.Require(pre, xt.Block(b +: elems, last).setPos(re)).setPos(x.pos) - case xt.Reads(objs, b) => xt.Reads(objs, xt.Block(b +: elems, last).setPos(re)).setPos(x.pos) - case xt.Modifies(objs, b) => xt.Modifies(objs, xt.Block(b +: elems, last).setPos(re)).setPos(x.pos) - case b => xt.Block(b +: elems, last).setPos(x.pos) - } - - case x :: rest => - recOrNoTree(rest) match { - case xt.Block(elems, last) => - xt.Block(extractTree(x)(using cctx) +: elems, last).setPos(x.pos) - case e => - xt.Block(Seq(extractTree(x)(using cctx)), e).setPos(x.pos) - } - } - - recOrNoTree(es) - } - - private def extractArgs(sym: Symbol, args: Seq[Tree])(using DefContext): Seq[xt.Expr] = { - (sym.paramLists.flatten zip args.map(extractTree)).map { - case (sym, e) => if (sym.isByNameParam) xt.Lambda(Seq.empty, e).setPos(e) else e - } - } - - def stripAnnotationsExceptStrictBV(tpe: xt.Type): xt.Type = tpe match { - case xt.AnnotatedType(tp, flags) if flags.contains(xt.StrictBV) => - xt.AnnotatedType(stripAnnotationsExceptStrictBV(tp), Seq(xt.StrictBV)) - case xt.AnnotatedType(tp, _) => - stripAnnotationsExceptStrictBV(tp) - case _ => tpe - } - - private def extractTree(tr: Tree)(using dctx: DefContext): xt.Expr = (tr match { - case ExObjectDef(_, _) => xt.UnitLiteral() - case ExCaseClassSyntheticJunk() => xt.UnitLiteral() - case md: ModuleDef if md.symbol.isSynthetic => xt.UnitLiteral() - - case Block(es, e) => - extractBlock(es :+ e) match { - case xt.Block(Seq(ens @ xt.Ensuring(body, lam @ xt.Lambda(Seq(res), post))), xt.UnitLiteral()) => - // Scalac will automatically wrap bodies in Block(Seq(body), UnitLiteral()) when Unit is - // expected, but the actual return type is another. This obscures postconditions. - val lam2 = xt.Lambda(Seq(res.copy(tpe = xt.UnitType())), post).copiedFrom(lam) - val b2 = xt.Block(Seq(body), xt.UnitLiteral().copiedFrom(body)).copiedFrom(body) - xt.Ensuring(xt.exprOps.flattenBlocks(b2), lam2).copiedFrom(ens) - case b => - xt.exprOps.flattenBlocks(b) - } - - - case Try(body, cses, fin) => - val rb = extractTree(body) - val rc = cses.map(extractMatchCase) - xt.Try(rb, rc, if (fin == EmptyTree) None else Some(extractTree(fin))) - - case Return(e) => xt.Return(extractTree(e)) - - case Throw(ex) => - xt.Throw(extractTree(ex)) - - case ExAssertExpression(e, oerr, isStatic) => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - xt.Assert(wrap(extractTree(e)), oerr, xt.UnitLiteral().setPos(tr.pos)) - - case ExRequiredExpression(body, isStatic) => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - xt.Require(wrap(extractTree(body)), xt.UnitLiteral().setPos(tr.pos)) - - case ExReadsExpression(objs) => - xt.Reads(extractTree(objs), xt.UnitLiteral().setPos(tr.pos)) - - case ExModifiesExpression(objs) => - xt.Modifies(extractTree(objs), xt.UnitLiteral().setPos(tr.pos)) - - case ExEnsuredExpression(body, contract, isStatic) => - def wrap(x: xt.Expr) = if (isStatic) xt.Annotated(x, Seq(xt.Ghost)).setPos(x) else x - - val post = extractTree(contract) - val b = extractTreeOrNoTree(body) - - xt.Ensuring(b, post match { - case l: xt.Lambda => l.copy(body = wrap(l.body)).copiedFrom(l) - case other => - val tpe = extractType(body) - val vd = xt.ValDef.fresh("res", tpe).setPos(other) - xt.Lambda(Seq(vd), wrap(extractType(contract) match { - case xt.BooleanType() => post - case _ => xt.Application(other, Seq(vd.toVariable)).setPos(post) - })).setPos(post) - }) - - case ExThrowingExpression(body, contract) => - val throwing = extractTree(contract) - val b = extractTreeOrNoTree(body) - - xt.Throwing(b, throwing match { - case l: xt.Lambda => l - case other => - val tpe = extractType(exceptionSym.info)(using dctx, contract.pos) - val vd = xt.ValDef.fresh("res", tpe).setPos(other) - xt.Lambda(Seq(vd), xt.Application(other, Seq(vd.toVariable)).setPos(other)).setPos(other) - }) - - case t @ ExHoldsWithProofExpression(body, ExMaybeBecauseExpressionWrapper(proof)) => - val vd = xt.ValDef.fresh("holds", xt.BooleanType().setPos(tr.pos)).setPos(tr.pos) - val p = extractTreeOrNoTree(proof) - val and = xt.And(p, vd.toVariable).setPos(tr.pos) - val post = xt.Lambda(Seq(vd), and).setPos(tr.pos) - val b = extractTreeOrNoTree(body) - xt.Ensuring(b, post).setPos(post) - - case t @ ExHoldsExpression(body) => - val vd = xt.ValDef.fresh("holds", xt.BooleanType().setPos(tr.pos)).setPos(tr.pos) - val post = xt.Lambda(Seq(vd), vd.toVariable).setPos(tr.pos) - val b = extractTreeOrNoTree(body) - xt.Ensuring(b, post).setPos(post) - - // If the because statement encompasses a holds statement - case t @ ExBecauseExpression(ExHoldsExpression(body), proof) => - val vd = xt.ValDef.fresh("holds", xt.BooleanType().setPos(tr.pos)).setPos(tr.pos) - val p = extractTree(proof) - val and = xt.And(p, vd.toVariable).setPos(tr.pos) - val post = xt.Lambda(Seq(vd), and).setPos(tr.pos) - val b = extractTreeOrNoTree(body) - xt.Ensuring(b, post).setPos(post) - - case ExPasses(in, out, cases) => - val ine = extractTree(in) - val oute = extractTree(out) - val rc = cases.map(extractMatchCase) - - xt.Passes(ine, oute, rc) - - case t @ ExBigLengthExpression(input) => - xt.StringLength(extractTree(input)) - - case t @ ExBigSubstringExpression(input, start) => - val in = extractTree(input) - val st = extractTree(start) - val vd = xt.ValDef.fresh("s", xt.StringType().setPos(t.pos), true).setPos(t.pos) - xt.Let(vd, in, xt.SubString(vd.toVariable, st, xt.StringLength(vd.toVariable).setPos(t.pos)).setPos(t.pos)) - - case t @ ExBigSubstring2Expression(input, start, end) => - val in = extractTree(input) - val st = extractTree(start) - val en = extractTree(end) - xt.SubString(in, st, en) - - case ExArrayLiteral(tpe, args) => - xt.FiniteArray(args.map(extractTree), extractType(tpe)(using dctx, tr.pos)) - - case s @ ExCaseObject(sym) => - extractType(s) match { - case ct: xt.ClassType => xt.ClassConstructor(ct, Seq.empty) - case tpe => outOfSubsetError(tr, "Unexpected class constructor type: " + tpe) - } - - case ExTuple(tpes, exprs) => - xt.Tuple(exprs map extractTree) - - case ExOldExpression(t) => xt.Old(extractTree(t)) - - case ExSnapshotExpression(t) => xt.Snapshot(extractTree(t)) - - case ExFreshCopyExpression(t) => xt.FreshCopy(extractTree(t)) - - case ExErrorExpression(str, tpt) => - xt.Error(extractType(tpt), str) - - case ExTupleExtract(tuple, index) => - xt.TupleSelect(extractTree(tuple), index) - - /** - * XLang Extractors - */ - - case a @ ExAssign(sym, rhs) => - // we assume type-correct code, so `sym` must be defined and in scope - xt.Assignment(dctx.mutableVars(sym)().setPos(a.pos), extractTree(rhs)) - - case a @ ExFieldAssign(sym, lhs@Select(thiss: This, _), rhs) => - xt.FieldAssignment(extractTree(thiss), getIdentifier(sym), extractTree(rhs)) - - case wh @ ExWhile(cond, body, invOpt, weakInvOpt, inline, opaque) => - val inlineFlag = if (inline) Some(xt.InlineOnce) else None - val opaqueFlag = if (opaque) Some(xt.Opaque) else None - val flags = inlineFlag.toSeq ++ opaqueFlag - xt.While(extractTree(cond), extractTree(body), invOpt.map(extractTree), weakInvOpt.map(extractTree), flags) - - case ExBigIntLiteral(n: Literal) => - xt.IntegerLiteral(BigInt(n.value.stringValue)) - - case ExIntToBigInt(tree) => - extractTree(tree) match { - case xt.Int32Literal(n) => xt.IntegerLiteral(BigInt(n)) - case _ => outOfSubsetError(tr, "Conversion from Int to BigInt") - } - - case ExIntToBV(FrontendBVKind(signed, size), tree) => - extractTree(tree) match { - case xt.Int32Literal(n) - if !signed && 0 <= n && BigInt(n) < BigInt(2).pow(size) => - - xt.BVLiteral(signed, BigInt(n), size) - case xt.Int32Literal(n) - if signed && -(BigInt(2).pow(size-1)) <= BigInt(n) && BigInt(n) < BigInt(2).pow(size-1) => - - xt.BVLiteral(signed, BigInt(n), size) - case _ => outOfSubsetError(tr, "`intToBV` may only be used on `Int` literals (in the right range)") - } - - case ExIntToBV(typ, tree) => - outOfSubsetError(tr, s"`intToBV` cannot be instantiated on non-bitvector type $typ") - - case ExBigIntToBV(signed, size, tree) => - extractTree(tree) match { - case xt.IntegerLiteral(n) - if signed && 0 <= n && n < BigInt(2).pow(size) => - - xt.BVLiteral(signed, n, size) - case xt.IntegerLiteral(n) - if signed && -(BigInt(2).pow(size-1)) <= n && n < BigInt(2).pow(size-1) => - - xt.BVLiteral(signed, n, size) - case _ => outOfSubsetError(tr, "`bigIntToBV` implicit may only be used on `BigInt` literals (in the right range)") - } - - case ExMaxBV(signed, size) => - if (signed) xt.BVLiteral(signed, (BigInt(2) pow (size - 1)) - 1, size) - else xt.BVLiteral(signed, (BigInt(2) pow size) - 1, size) - - case ExMinBV(signed, size) => - if (signed) xt.BVLiteral(signed, -(BigInt(2) pow (size - 1)), size) - else xt.BVLiteral(signed, BigInt(0), size) - - case ExFromByte(tree) => extractTree(tree) - case ExFromShort(tree) => extractTree(tree) - case ExFromInt(tree) => extractTree(tree) - case ExFromLong(tree) => extractTree(tree) - - case ExWrapping(tree) => - val body = extractTree(tree)(using dctx.setWrappingArithmetic(true)) - xt.Annotated(body, Seq(xt.Wrapping)) - - case ExRealLiteral(n, d) => - (extractTree(n), extractTree(d)) match { - case (xt.IntegerLiteral(n), xt.IntegerLiteral(d)) => xt.FractionLiteral(n, d) - case _ => outOfSubsetError(tr, "Real not build from literals") - } - - case ExRealIntLiteral(n) => - extractTree(n) match { - case xt.IntegerLiteral(n) => xt.FractionLiteral(n, 1) - case _ => outOfSubsetError(tr, "Real not build from literals") - } - - case ExInt8Literal(v) => xt.Int8Literal(v) - case ExInt16Literal(v) => xt.Int16Literal(v) - case ExInt32Literal(v) => xt.Int32Literal(v) - case ExInt64Literal(v) => xt.Int64Literal(v) - case ExBooleanLiteral(v) => xt.BooleanLiteral(v) - case ExUnitLiteral() => xt.UnitLiteral() - case ExCharLiteral(c) => xt.CharLiteral(c) - case ExStringLiteral(s) => xt.StringLiteral(s) - - case ExIdentity(body) => extractTree(body) - - case ExTyped(ExtractorHelpers.ExSymbol("scala", "Predef", "$qmark$qmark$qmark"), tpe) => - xt.NoTree(extractType(tpe)) - - case ExTyped(e, _) => extractTree(e) - - // References to parameterless case objects will have the form of an `Ident` - case ex @ ExIdentifier(sym, tpt) if sym.isModule && sym.isCase => - extractType(tpt) match { - case lct: xt.LocalClassType => xt.LocalClassConstructor(lct, Seq()) - case ct: xt.ClassType => xt.ClassConstructor(ct, Seq()) - case _ => outOfSubsetError(tr, "Unexpected constructor " + tr) - } - - case ex @ ExIdentifier(sym, tpt) => - dctx.vars.get(sym).orElse(dctx.mutableVars.get(sym)) match { - case Some(builder) => builder().setPos(ex.pos) - case None => dctx.localFuns.get(sym) match { - case Some((id, tparams, tpe)) => - assert(tparams.isEmpty, "Unexpected application " + ex + " without type parameters") - xt.ApplyLetRec(id, Seq.empty, tpe, Seq.empty, Seq.empty) - case None => xt.FunctionInvocation(getIdentifier(sym), Seq.empty, Seq.empty).setPos(ex.pos) - } - } - - case ExtractorHelpers.ExSymbol("scala", "Predef", "$qmark$qmark$qmark") => xt.NoTree(extractType(tr)) - - case chs @ ExChooseExpression(body) => - ctx.reporter.warning(tr.pos, "`choose` expressions may be unsafe due to difficulty in checking their realizability automatically") - extractTree(body) match { - case xt.Lambda(Seq(vd), body) => - xt.Choose(vd, body) - case _ => outOfSubsetError(tr, "Unexpected choose definition") - } - - case swap @ ExSwapExpression(array1, index1, array2, index2) => - xt.Swap(extractTree(array1), extractTree(index1), extractTree(array2), extractTree(index2)) - - case l @ ExLambdaExpression(args, body) => - val vds = args map(vd => xt.ValDef( - FreshIdentifier(vd.symbol.name.toString), - extractType(vd.tpt), - annotationsOf(vd.symbol, ignoreOwner = true) - ).setPos(vd.pos)) - - val newVars = (args zip vds).map { case (vd, lvd) => - vd.symbol -> (() => lvd.toVariable) - } - - val exBody = extractTree(body)(using dctx.withNewVars(newVars)) - xt.Lambda(vds, exBody) - - case ExForallExpression(contract) => - extractTree(contract) match { - case l: xt.Lambda => xt.Forall(l.params, l.body).setPos(l) - case pred => extractType(contract) match { - case xt.FunctionType(from, to) => - val args = from.map(tpe => xt.ValDef(FreshIdentifier("x", true), tpe).setPos(pred)) - xt.Forall(args, xt.Application(pred, args.map(_.toVariable)).setPos(pred)) - case _ => - outOfSubsetError(tr, "Unsupported forall definition: " + tr) - } - } - - case ExMutableMapWithDefault(tptFrom, tptTo, default) => - xt.MutableMapWithDefault(extractType(tptFrom), extractType(tptTo), extractTree(default)) - - case ExFiniteMap(tptFrom, tptTo, args) => - val to = extractType(tptTo) - val pairs = args.map { - case ExTuple(_, Seq(key, value)) => - (extractTree(key), extractTree(value)) - case tree => - val ex = extractTree(tree) - (xt.TupleSelect(ex, 1), xt.TupleSelect(ex, 2)) - } - - val somePairs = pairs.map { case (key, value) => - key -> xt.ClassConstructor( - xt.ClassType(getIdentifier(someSymbol), Seq(to)).setPos(tr.pos), - Seq(value) - ).setPos(tr.pos) - } - - val dflt = xt.ClassConstructor( - xt.ClassType(getIdentifier(noneSymbol), Seq(to)).setPos(tr.pos), - Seq.empty - ).setPos(tr.pos) - - val optTo = xt.ClassType(getIdentifier(optionSymbol), Seq(to)) - xt.FiniteMap(somePairs, dflt, extractType(tptFrom), optTo) - - case ExFiniteSet(tpt, args) => - xt.FiniteSet(args.map(extractTree), extractType(tpt)) - - case ExFiniteBag(tpt, args) => - xt.FiniteBag(args.map { - case ExTuple(_, Seq(key, value)) => - (extractTree(key), extractTree(value)) - case tree => - val ex = extractTree(tree) - (xt.TupleSelect(ex, 1), xt.TupleSelect(ex, 2)) - }, extractType(tpt)) - - case ExClassConstruction(tpe, args) => - extractType(tpe)(using dctx, tr.pos) match { - case lct: xt.LocalClassType => - xt.LocalClassConstructor(lct, args.map(extractTree)) - case ct: xt.ClassType => - xt.ClassConstructor(ct, args.map(extractTree)) - case _ => - outOfSubsetError(tr, "Construction of a non-class type.") - } - - case ExNot(e) => xt.Not(extractTree(e)) - case ExUMinus(e) => injectCast(xt.UMinus.apply)(e) - case ExBVNot(e) => injectCast(xt.BVNot.apply)(e) - - case ExNotEquals(l, r) => xt.Not(((extractTree(l), extractType(l), extractTree(r), extractType(r)) match { - case (bi @ xt.BVLiteral(_, _, _), _, e, xt.IntegerType()) => xt.Equals(xt.IntegerLiteral(bi.toBigInt).setPos(l.pos), e) - case (e, xt.IntegerType(), bi @ xt.BVLiteral(_, _, _), _) => xt.Equals(e, xt.IntegerLiteral(bi.toBigInt).setPos(r.pos)) - - case (l2, StrictBVType(signed, size), xt.IntegerLiteral(value), _) => - xt.Equals(l2, xt.BVLiteral(signed, value, size).setPos(r.pos)) - case (l2, StrictBVType(signed, size), xt.Int32Literal(value), _) => - xt.Equals(l2, xt.BVLiteral(signed, BigInt(value), size).setPos(r.pos)) - case (xt.IntegerLiteral(value), _, r2, StrictBVType(signed, size)) => - xt.Equals(xt.BVLiteral(signed, value, size).setPos(l.pos), r2) - case (xt.Int32Literal(value), _, r2, StrictBVType(signed, size)) => - xt.Equals(xt.BVLiteral(signed, BigInt(value), size).setPos(l.pos), r2) - - case (l2, tpeL @ StrictBVType(_, _), r2, tpeR) => - if (tpeL == tpeR) xt.Equals(l2, r2) - else outOfSubsetError(tr, "Bitvectors can only be compared with bitvectors of the same type.") - case (l2, tpeL, r2, tpeR @ StrictBVType(_, _)) => - if (tpeL == tpeR) xt.Equals(l2, r2) - else outOfSubsetError(tr, "Bitvectors can only be compared with bitvectors of the same type.") - - case _ => injectCasts(xt.Equals.apply)(l, r) - }).setPos(tr.pos)) - - case ExEquals(l, r) => (extractTree(l), extractType(l), extractTree(r), extractType(r)) match { - case (bi @ xt.BVLiteral(_, _, _), _, e, xt.IntegerType()) => xt.Equals(xt.IntegerLiteral(bi.toBigInt).setPos(l.pos), e) - case (e, xt.IntegerType(), bi @ xt.BVLiteral(_, _, _), _) => xt.Equals(e, xt.IntegerLiteral(bi.toBigInt).setPos(r.pos)) - - case (l2, StrictBVType(signed, size), xt.IntegerLiteral(value), _) => - xt.Equals(l2, xt.BVLiteral(signed, value, size).setPos(r.pos)) - case (l2, StrictBVType(signed, size), xt.Int32Literal(value), _) => - xt.Equals(l2, xt.BVLiteral(signed, BigInt(value), size).setPos(r.pos)) - case (xt.IntegerLiteral(value), _, r2, StrictBVType(signed, size)) => - xt.Equals(xt.BVLiteral(signed, value, size).setPos(l.pos), r2) - case (xt.Int32Literal(value), _, r2, StrictBVType(signed, size)) => - xt.Equals(xt.BVLiteral(signed, BigInt(value), size).setPos(l.pos), r2) - - case (l2, tpeL @ StrictBVType(_, _), r2, tpeR) => - if (tpeL == tpeR) xt.Equals(l2, r2) - else outOfSubsetError(tr, "Bitvectors can only be compared with bitvectors of the same type.") - case (l2, tpeL, r2, tpeR @ StrictBVType(_, _)) => - if (tpeL == tpeR) xt.Equals(l2, r2) - else outOfSubsetError(tr, "Bitvectors can only be compared with bitvectors of the same type.") - - case _ => injectCasts(xt.Equals.apply)(l, r) - } - - case ExIfThenElse(t1,t2,t3) => - xt.IfExpr(extractTree(t1), extractTree(t2), extractTree(t3)) - - case ExAsInstanceOf(expr, tt) => - extractType(tt) match { - case ct: xt.ClassType => xt.AsInstanceOf(extractTree(expr), ct) - case lct: xt.LocalClassType => xt.AsInstanceOf(extractTree(expr), lct.toClassType) - case _ => outOfSubsetError(tr, "asInstanceOf can only cast to class types") - } - - case ExIsInstanceOf(expr, tt) => - extractType(tt) match { - case ct: xt.ClassType => xt.IsInstanceOf(extractTree(expr), ct) - case lct: xt.LocalClassType => xt.IsInstanceOf(extractTree(expr), lct.toClassType) - case _ => outOfSubsetError(tr, "isInstanceOf can only be used with class types") - } - - case pm @ ExPatternMatching(sel, cses) => - val rs = extractTree(sel) - val rc = cses.map(extractMatchCase) - xt.MatchExpr(rs, rc) - - case t: This => - extractType(t) match { - case ct: xt.ClassType => xt.This(ct) - case lct: xt.LocalClassType => xt.LocalThis(lct) - case _ => outOfSubsetError(t, "Invalid usage of `this`") - } - - case s: Super => - extractType(s) match { - case ct: xt.ClassType => xt.Super(ct) - case lct: xt.LocalClassType => xt.Super(lct.toClassType) - - case _ => outOfSubsetError(s, s"Invalid usage of `super`") - } - - case ExArrayFill(baseType, length, defaultValue) => - val lengthRec = extractTree(length) - val defaultValueRec = extractTree(defaultValue) - xt.LargeArray(Map.empty, extractTree(defaultValue), extractTree(length), extractType(baseType)) - - case ExArrayUpdate(array, index, newValue) => - xt.ArrayUpdate(extractTree(array), extractTree(index), extractTree(newValue)) - - case ExArrayUpdated(array, index, newValue) => - xt.ArrayUpdated(extractTree(array), extractTree(index), extractTree(newValue)) - - case ExArrayApplyBV(array, bvType, index) => bvType match { - case FrontendBVType(signed, size) => - xt.ArraySelect( - extractTree(array), - toSigned(extractTree(index), signed, size, 32) - ) - case tpe => - outOfSubsetError(bvType, s"ArrayIndexing implicit must be used on a BitVector type (found $tpe instead)") - - } - - case ExArrayLength(array) => - xt.ArrayLength(extractTree(array)) - - case ExArrayApply(array, index) => xt.ArraySelect(extractTree(array), extractTree(index)) - - case l @ ExListLiteral(tpe, elems) => invariantListLiteral(tpe, elems, l.pos) - - case l @ ExCovListLiteral(tpe, elems) => covariantListLiteral(tpe, elems, l.pos) - - case ExImplies(lhs, rhs) => - xt.Implies(extractTree(lhs), extractTree(rhs)) - - case ExSplitAnd(lhs, rhs) => - xt.SplitAnd(extractTree(lhs), extractTree(rhs)) - - case ExGhost(body) => - xt.Annotated(extractTree(body), Seq(xt.Ghost)) - - case at@ExAndThen(lhs, rhs) => - val elhs = extractTree(lhs) - val erhs = extractTree(rhs) - extractType(at) match { - case xt.FunctionType(Seq(from), to) => - val x = xt.ValDef.fresh("x", from) - xt.Lambda(Seq(x), xt.Application(erhs, Seq(xt.Application(elhs, Seq(x.toVariable))))) - case other => outOfSubsetError(at, s"Unexpected type $other for andThen combinator") - } - - case at@ExCompose(lhs, rhs) => - val elhs = extractTree(lhs) - val erhs = extractTree(rhs) - extractType(at) match { - case xt.FunctionType(Seq(from), to) => - val x = xt.ValDef.fresh("x", from) - xt.Lambda(Seq(x), xt.Application(elhs, Seq(xt.Application(erhs, Seq(x.toVariable))))) - case other => outOfSubsetError(at, s"Unexpected type $other for compose combinator") - } - - case c @ ExCall(rec, sym, tps, args) => rec match { - case None if sym.owner.isModuleClass && sym.owner.isCase => - val ct = extractType(sym.owner.tpe)(using dctx, c.pos).asInstanceOf[xt.ClassType] - xt.MethodInvocation( - xt.This(ct).setPos(tr.pos), - getIdentifier(sym), - tps map extractType, - args map extractTree - ).setPos(tr.pos) - - case None => - dctx.localFuns.get(sym) match { - case None => - xt.FunctionInvocation(getIdentifier(sym), tps.map(extractType), extractArgs(sym, args)).setPos(c.pos) - case Some((id, tparams, tpe)) => - xt.ApplyLetRec(id, tparams.map(_.tp), tpe, tps.map(extractType), extractArgs(sym, args)).setPos(c.pos) - } - - case Some(lhs) => stripAnnotationsExceptStrictBV(extractType(lhs)(using dctx.setResolveTypes(true))) match { - case ct: xt.ClassType => - val isField = sym.isParamAccessor || sym.isCaseAccessor - val isMethod = sym.isMethod || sym.isAccessor || !isField - - if (isMethod) xt.MethodInvocation( - extractTree(lhs), - getIdentifier(sym), - tps.map(extractType), - extractArgs(sym, args) - ).setPos(c.pos) else args match { - case Seq() if sym.isParamAccessor => - xt.ClassSelector(extractTree(lhs), getIdentifier(sym)).setPos(c.pos) - case _ => - outOfSubsetError(tr, s"Unexpected call: $tr") - } - - case lct: xt.LocalClassType => - val isField = sym.isParamAccessor || sym.isCaseAccessor - val isMethod = sym.isMethod || sym.isAccessor || !isField - - if (isMethod) { - val lcd = dctx.localClasses(lct.id) - val id = getIdentifier(sym) - val fd = lcd.methods.find(_.id == id).get - xt.LocalMethodInvocation( - extractTree(lhs), - xt.ValDef(id, xt.FunctionType(fd.params.map(_.tpe), fd.returnType)).toVariable, - fd.tparams.map(_.tp), - tps.map(extractType), - extractArgs(sym, args) - ) - } else args match { - case Seq() if sym.isParamAccessor => - val lcd = dctx.localClasses(lct.id) - val id = getIdentifier(sym) - val field = lcd.fields.collectFirst { case vd @ xt.ValDef(`id`, _, _) => vd } - xt.LocalClassSelector(extractTree(lhs), id, field.map(_.tpe).getOrElse(xt.Untyped)) - case _ => - outOfSubsetError(tr, "Unexpected call") - } - - case ft: xt.FunctionType => - xt.Application(extractTree(lhs), args.map(extractTree)).setPos(ft) - - case tpe => (tpe, sym.name.decode.toString, args) match { - case (xt.StringType(), "+", Seq(rhs)) => xt.StringConcat(extractTree(lhs), extractTree(rhs)) - case (xt.IntegerType() | xt.BVType(_, _) | xt.RealType(), "+", Seq(rhs)) => injectCasts(xt.Plus.apply)(lhs, rhs) - - case (xt.SetType(_), "+", Seq(rhs)) => xt.SetAdd(extractTree(lhs), extractTree(rhs)) - case (xt.SetType(_), "++", Seq(rhs)) => xt.SetUnion(extractTree(lhs), extractTree(rhs)) - case (xt.SetType(_), "&", Seq(rhs)) => xt.SetIntersection(extractTree(lhs), extractTree(rhs)) - case (xt.SetType(_), "--", Seq(rhs)) => xt.SetDifference(extractTree(lhs), extractTree(rhs)) - case (xt.SetType(_), "subsetOf", Seq(rhs)) => xt.SubsetOf(extractTree(lhs), extractTree(rhs)) - case (xt.SetType(_), "contains", Seq(rhs)) => xt.ElementOfSet(extractTree(rhs), extractTree(lhs)) - case (xt.SetType(b), "isEmpty", Seq()) => xt.Equals(extractTree(lhs), xt.FiniteSet(Seq(), b)) - case (xt.SetType(b), "-", Seq(rhs)) => xt.SetDifference(extractTree(lhs), xt.FiniteSet(Seq(extractTree(rhs)), b).setPos(tr.pos)) - - case (xt.BagType(_), "+", Seq(rhs)) => xt.BagAdd(extractTree(lhs), extractTree(rhs)) - case (xt.BagType(_), "++", Seq(rhs)) => xt.BagUnion(extractTree(lhs), extractTree(rhs)) - case (xt.BagType(_), "&", Seq(rhs)) => xt.BagIntersection(extractTree(lhs), extractTree(rhs)) - case (xt.BagType(_), "--", Seq(rhs)) => xt.BagDifference(extractTree(lhs), extractTree(rhs)) - case (xt.BagType(_), "get", Seq(rhs)) => xt.MultiplicityInBag(extractTree(rhs), extractTree(lhs)) - - case (xt.MutableMapType(_, _), "apply", Seq(rhs)) => - xt.MutableMapApply(extractTree(lhs), extractTree(rhs)) - - case (xt.MutableMapType(_, _), "update", Seq(key, value)) => - xt.MutableMapUpdate(extractTree(lhs), extractTree(key), extractTree(value)) - - case (xt.MutableMapType(_, _), "updated", Seq(key, value)) => - xt.MutableMapUpdated(extractTree(lhs), extractTree(key), extractTree(value)) - - case (xt.MutableMapType(_, _), "duplicate", Seq()) => - xt.MutableMapDuplicate(extractTree(lhs)) - - case (xt.MapType(_, _), "get", Seq(rhs)) => - xt.MapApply(extractTree(lhs), extractTree(rhs)) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "apply", Seq(rhs)) => - val (l, r) = (extractTree(lhs), extractTree(rhs)) - val someTpe = xt.ClassType(getIdentifier(someSymbol), Seq(to)).setPos(tr.pos) - xt.Assert( - xt.IsInstanceOf(xt.MapApply(l, r).setPos(tr.pos), someTpe).setPos(tr.pos), - Some("Map undefined at this index"), - xt.ClassSelector( - xt.AsInstanceOf(xt.MapApply(l, r).setPos(tr.pos), someTpe).setPos(tr.pos), - getIdentifier(someSymbol.caseFieldAccessors.head.accessedOrSelf) - ).setPos(tr.pos) - ) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "isDefinedAt" | "contains", Seq(rhs)) => - xt.Not(xt.Equals( - xt.MapApply(extractTree(lhs), extractTree(rhs)).setPos(tr.pos), - xt.ClassConstructor( - xt.ClassType(getIdentifier(noneSymbol), Seq(to)).setPos(tr.pos), - Seq.empty - ).setPos(tr.pos) - ).setPos(tr.pos)) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "updated" | "+", Seq(key, value)) => - xt.MapUpdated( - extractTree(lhs), extractTree(key), - xt.ClassConstructor( - xt.ClassType(getIdentifier(someSymbol), Seq(to)).setPos(tr.pos), - Seq(extractTree(value)) - ).setPos(tr.pos) - ) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "+", Seq(rhs)) => - val value = extractTree(rhs) - xt.MapUpdated( - extractTree(lhs), xt.TupleSelect(value, 1).setPos(tr.pos), - xt.ClassConstructor( - xt.ClassType(getIdentifier(someSymbol), Seq(to)).setPos(tr.pos), - Seq(xt.TupleSelect(value, 2).setPos(tr.pos)) - ).setPos(tr.pos) - ) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "removed" | "-", Seq(key)) => - xt.MapUpdated( - extractTree(lhs), extractTree(key), - xt.ClassConstructor( - xt.ClassType(getIdentifier(noneSymbol), Seq(to)).setPos(tr.pos), - Seq.empty - ).setPos(tr.pos) - ) - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "++", Seq(rhs)) => - extractTree(rhs) match { - case xt.FiniteMap(pairs, default, keyType, valueType) => - pairs.foldLeft(extractTree(lhs)) { case (map, (k, v)) => - xt.MapUpdated(map, k, v).setPos(tr.pos) - } - - case _ => outOfSubsetError(tr, "Can't extract map union with non-finite map") - } - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "--", Seq(rhs)) => - extractTree(rhs) match { - case xt.FiniteSet(els, _) => - val none = xt.ClassConstructor( - xt.ClassType(getIdentifier(noneSymbol), Seq(to)).setPos(tr.pos), - Seq.empty - ).setPos(tr.pos) - - els.foldLeft(extractTree(lhs)) { case (map, k) => - xt.MapUpdated(map, k, none).setPos(tr.pos) - } - - case _ => outOfSubsetError(tr, "Can't extract map diff with non-finite map") - } - - case (xt.MapType(_, xt.ClassType(_, Seq(to))), "getOrElse", Seq(key, orElse)) => - xt.MethodInvocation( - xt.MapApply(extractTree(lhs), extractTree(key)).setPos(tr.pos), - getIdentifier(optionSymbol.tpe.member(TermName("getOrElse"))), - Seq.empty, - Seq(xt.Lambda(Seq(), extractTree(orElse)).setPos(tr.pos)) - ).setPos(c.pos) - - case (StrictBVType(_, _), "unary_~", Seq()) => xt.BVNot(extractTree(lhs)) - case (StrictBVType(_, _), "unary_-", Seq()) => xt.UMinus(extractTree(lhs)) - case (StrictBVType(_, _), "+", Seq(rhs)) => xt.Plus(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "-", Seq(rhs)) => xt.Minus(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "*", Seq(rhs)) => xt.Times(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "%", Seq(rhs)) => xt.Remainder(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "mod", Seq(rhs)) => xt.Modulo(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "/", Seq(rhs)) => xt.Division(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), ">", Seq(rhs)) => xt.GreaterThan(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), ">=", Seq(rhs)) => xt.GreaterEquals(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "<", Seq(rhs)) => xt.LessThan(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "<=", Seq(rhs)) => xt.LessEquals(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "|", Seq(rhs)) => xt.BVOr(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "&", Seq(rhs)) => xt.BVAnd(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "^", Seq(rhs)) => xt.BVXor(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), "<<", Seq(rhs)) => xt.BVShiftLeft(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), ">>", Seq(rhs)) => xt.BVAShiftRight(extractTree(lhs), extractTree(rhs)) - case (StrictBVType(_, _), ">>>", Seq(rhs)) => xt.BVLShiftRight(extractTree(lhs), extractTree(rhs)) - - case (StrictBVType(signed, size), "widen", Seq()) => tps match { - case Seq(FrontendBVType(signed2, size2)) => - if (signed2 != signed) outOfSubsetError(tr, "Method `widen` must be used with a bitvector type of the same sign") - else if (size2 <= size) outOfSubsetError(tr, "Method `widen` must be used with a bitvector type of a larger size") - else xt.BVWideningCast(extractTree(lhs), xt.BVType(signed2, size2)) - } - case (StrictBVType(signed, size), "narrow", Seq()) => tps match { - case Seq(FrontendBVType(signed2, size2)) => - if (signed2 != signed) outOfSubsetError(tr, "Method `narrow` must be used with a bitvector type of the same sign") - else if (size2 >= size) outOfSubsetError(tr, "Method `narrow` must be used with a bitvector type of a smaller size") - else xt.BVNarrowingCast(extractTree(lhs), xt.BVType(signed2, size2)) - } - - case (StrictBVType(signed, size), "toSigned", Seq()) => tps match { - case Seq(FrontendBVType(signed2, size2)) => - if (signed) outOfSubsetError(tr, "Method `toSigned` must be used on unsigned bitvectors") - else if (!signed2) outOfSubsetError(tr, "Method `toSigned` must be instantiated with a signed bitvector type") - else if (size != size2) outOfSubsetError(tr, "Method `toSigned` must be instantiated with a signed bitvector type of the same size as the original bitvector") - else xt.BVUnsignedToSigned(extractTree(lhs)) - case _ => - outOfSubsetError(tr, "Method `toSigned` must be instantiated with a signed bitvector type of the same size as the original bitvector") - } - case (StrictBVType(signed, size), "toUnsigned", Seq()) => tps match { - case Seq(FrontendBVType(signed2, size2)) => - if (!signed) outOfSubsetError(tr, "Method `toUnsigned` must be used on signed bitvectors") - else if (signed2) outOfSubsetError(tr, "Method `toUnsigned` must be instantiated with a unsigned bitvector type") - else if (size != size2) outOfSubsetError(tr, "Method `toUnsigned` must be instantiated with a unsigned bitvector type of the same size as the original bitvector") - else xt.BVSignedToUnsigned(extractTree(lhs)) - case _ => - outOfSubsetError(tr, "Method `toSigned` must be instantiated with a signed bitvector type of the same size as the original bitvector") - } - - case (StrictBVType(signed, size), "toByte", Seq()) => - toSigned(extractTree(lhs), signed, size, 8) - case (StrictBVType(signed, size), "toShort", Seq()) => - toSigned(extractTree(lhs), signed, size, 16) - case (StrictBVType(signed, size), "toInt", Seq()) => - toSigned(extractTree(lhs), signed, size, 32) - case (StrictBVType(signed, size), "toLong", Seq()) => - toSigned(extractTree(lhs), signed, size, 64) - - case (_, "unary_+", Seq()) => injectCast(e => e)(lhs) - case (_, "-", Seq(rhs)) => injectCasts(xt.Minus.apply)(lhs, rhs) - case (_, "*", Seq(rhs)) => injectCasts(xt.Times.apply)(lhs, rhs) - case (_, "%", Seq(rhs)) => injectCasts(xt.Remainder.apply)(lhs, rhs) - case (_, "mod", Seq(rhs)) => xt.Modulo(extractTree(lhs), extractTree(rhs)) - case (_, "/", Seq(rhs)) => injectCasts(xt.Division.apply)(lhs, rhs) - case (_, ">", Seq(rhs)) => injectCasts(xt.GreaterThan.apply)(lhs, rhs) - case (_, ">=", Seq(rhs)) => injectCasts(xt.GreaterEquals.apply)(lhs, rhs) - case (_, "<", Seq(rhs)) => injectCasts(xt.LessThan.apply)(lhs, rhs) - case (_, "<=", Seq(rhs)) => injectCasts(xt.LessEquals.apply)(lhs, rhs) - - case (xt.BVType(_, _), "|", Seq(rhs)) => injectCasts(xt.BVOr.apply)(lhs, rhs) - case (xt.BVType(_, _), "&", Seq(rhs)) => injectCasts(xt.BVAnd.apply)(lhs, rhs) - case (xt.BVType(_, _), "^", Seq(rhs)) => injectCasts(xt.BVXor.apply)(lhs, rhs) - case (xt.BVType(_, _), "<<", Seq(rhs)) => injectCastsForShift(xt.BVShiftLeft.apply)(lhs, rhs) - case (xt.BVType(_, _), ">>", Seq(rhs)) => injectCastsForShift(xt.BVAShiftRight.apply)(lhs, rhs) - case (xt.BVType(_, _), ">>>", Seq(rhs)) => injectCastsForShift(xt.BVLShiftRight.apply)(lhs, rhs) - - case (xt.BooleanType(), "|", Seq(rhs)) => xt.BoolBitwiseOr(extractTree(lhs), extractTree(rhs)) - case (xt.BooleanType(), "&", Seq(rhs)) => xt.BoolBitwiseAnd(extractTree(lhs), extractTree(rhs)) - case (xt.BooleanType(), "^", Seq(rhs)) => xt.BoolBitwiseXor(extractTree(lhs), extractTree(rhs)) - - case (_, "&&", Seq(rhs)) => xt.And(extractTree(lhs), extractTree(rhs)) - case (_, "||", Seq(rhs)) => xt.Or(extractTree(lhs), extractTree(rhs)) - - case (tpe, "toByte", Seq()) => tpe match { - case xt.BVType(true, 8) => extractTree(lhs) - case xt.BVType(true, 16 | 32 | 64) => xt.BVNarrowingCast(extractTree(lhs), xt.BVType(true, 8)) - case tpe => outOfSubsetError(tr, s"Unexpected cast .toByte from $tpe") - } - - case (tpe, "toShort", Seq()) => tpe match { - case xt.BVType(true, 8) => xt.BVWideningCast(extractTree(lhs), xt.BVType(true, 16)) - case xt.BVType(true, 16) => extractTree(lhs) - case xt.BVType(true, 32 | 64) => xt.BVNarrowingCast(extractTree(lhs), xt.BVType(true, 16)) - case tpe => outOfSubsetError(tr, s"Unexpected cast .toShort from $tpe") - } - - case (tpe, "toInt", Seq()) => tpe match { - case xt.BVType(true, 8 | 16) => xt.BVWideningCast(extractTree(lhs), xt.BVType(true, 32)) - case xt.BVType(true, 32) => extractTree(lhs) - case xt.BVType(true, 64) => xt.BVNarrowingCast(extractTree(lhs), xt.BVType(true, 32)) - case tpe => outOfSubsetError(tr, s"Unexpected cast .toInt from $tpe") - } - - case (tpe, "toLong", Seq()) => tpe match { - case xt.BVType(true, 8 | 16 | 32 ) => xt.BVWideningCast(extractTree(lhs), xt.BVType(true, 64)) - case xt.BVType(true, 64) => extractTree(lhs) - case tpe => outOfSubsetError(tr, s"Unexpected cast .toLong from $tpe") - } - - case (tpe, name, args) => - outOfSubsetError(tr, s"Unsupported call to $name on $lhs") - } - } - } - - // default behaviour is to complain :) - case _ => outOfSubsetError(tr, s"Stainless does not support expression (${tr.getClass}): `$tr`") - }).ensurePos(tr.pos) - - private def invariantListLiteral(tpt: Tree, args: List[Tree], pos: inox.utils.Position)(using dctx: DefContext): xt.Expr = - listLiteral(tp => xt.ClassType(getIdentifier(consSymbol), Seq(tp)), tp => xt.ClassType(getIdentifier(nilSymbol), Seq(tp)))(tpt, args, pos) - - private def covariantListLiteral(tpt: Tree, args: List[Tree], pos: inox.utils.Position)(using dctx: DefContext): xt.Expr = - listLiteral(tp => xt.ClassType(getIdentifier(covConsSymbol), Seq(tp)), _ => xt.ClassType(getIdentifier(covNilSymbol), Seq.empty))(tpt, args, pos) - - private def listLiteral(mkCons: xt.Type => xt.ClassType, mkNil: xt.Type => xt.ClassType) - (tpe: Tree, elems: Seq[Tree], pos: inox.utils.Position)(using dctx: DefContext): xt.Expr = { - val rtpe = extractType(tpe) - val cons = mkCons(rtpe) - val nil = mkNil(rtpe) - elems.foldRight(xt.ClassConstructor(nil, Seq()).setPos(pos)) { - case (e, ls) => xt.ClassConstructor(cons, Seq(extractTree(e), ls)) - } - } - - /** Inject casts for our BitVectors library for methods toByte, toShort, toInt, toLong */ - private def toSigned(e: xt.Expr, signed: Boolean, size1: Int, size2: Int): xt.Expr = { - // already signed - if (signed && size1 < size2) xt.BVWideningCast(e, xt.BVType(signed, size2)).copiedFrom(e) - else if (signed && size1 > size2) xt.BVNarrowingCast(e, xt.BVType(signed, size2)).copiedFrom(e) - else if (signed) e - // unsigned - else if (size1 < size2) - xt.BVUnsignedToSigned(xt.BVWideningCast(e, xt.BVType(signed, size2)).copiedFrom(e)).copiedFrom(e) - else if (size1 > size2) - xt.BVUnsignedToSigned(xt.BVNarrowingCast(e, xt.BVType(signed, size2)).copiedFrom(e)).copiedFrom(e) - else - xt.BVUnsignedToSigned(e).copiedFrom(e) - } - - /** Inject implicit widening casts according to the Java semantics (5.6.2. Binary Numeric Promotion) */ - private def injectCasts(ctor: (xt.Expr, xt.Expr) => xt.Expr) - (lhs0: Tree, rhs0: Tree) - (using DefContext): xt.Expr = { - injectCastsImpl(ctor)(lhs0, rhs0, shift = false) - } - - /** - * Inject casts, special edition for shift operations. - * - * NOTE In THEORY, the rhs needs to be promoted independently of lhs. - * In PRACTICE, Inox requires that both operands have the same type. - * [[CodeGeneration]] is applying a narrowing cast from Long to Int - * if needed. Here we add the opposite, and safe operation when lhs - * is a Long. We do not support shift operations when rhs is Long - * but lhs is a smaller BVType. - */ - private def injectCastsForShift(ctor: (xt.Expr, xt.Expr) => xt.Expr) - (lhs0: Tree, rhs0: Tree) - (using DefContext): xt.Expr = { - injectCastsImpl(ctor)(lhs0, rhs0, shift = true) - } - - private def injectCastsImpl(ctor: (xt.Expr, xt.Expr) => xt.Expr) - (lhs0: Tree, rhs0: Tree, shift: Boolean) - (using dctx: DefContext): xt.Expr = { - def checkBits(tr: Tree, tpe: xt.Type) = tpe match { - case xt.BVType(_, 8 | 16 | 32 | 64) => // Byte, Short, Int or Long are ok - case xt.BVType(_, s) => outOfSubsetError(tr, s"Unexpected integer of $s bits") - case _ => // non-bitvector types are ok too - } - - val lhs = extractTree(lhs0) - val rhs = extractTree(rhs0) - - val ltpe = extractType(lhs0)(using dctx.setResolveTypes(true)) - checkBits(lhs0, ltpe) - val rtpe = extractType(rhs0)(using dctx.setResolveTypes(true)) - checkBits(rhs0, rtpe) - - val id = { (e: xt.Expr) => e } - val widen32 = { (e: xt.Expr) => xt.BVWideningCast(e, xt.BVType(true, 32).copiedFrom(e)).copiedFrom(e) } - val widen64 = { (e: xt.Expr) => xt.BVWideningCast(e, xt.BVType(true, 64).copiedFrom(e)).copiedFrom(e) } - - val (lctor, rctor) = (ltpe, rtpe) match { - case (xt.BVType(true, 64), xt.BVType(true, 64)) => (id, id) - case (xt.BVType(true, 64), xt.BVType(true, _)) => (id, widen64) - case (xt.BVType(true, _), xt.BVType(true, 64)) if shift => outOfSubsetError(rhs0, s"Unsupported shift") - case (xt.BVType(true, _), xt.BVType(true, 64)) => (widen64, id) - case (xt.BVType(true, 32), xt.BVType(true, 32)) => (id, id) - case (xt.BVType(true, 32), xt.BVType(true, _)) => (id, widen32) - case (xt.BVType(true, _), xt.BVType(true, 32)) => (widen32, id) - case (xt.BVType(true, _), xt.BVType(true, _)) => (widen32, widen32) - - case (xt.BVType(_, _), _) | (_, xt.BVType(_, _)) => - outOfSubsetError(lhs0, s"Unexpected combination of types: $ltpe and $rtpe") - - case (_, _) => (id, id) - } - - ctor(lctor(lhs), rctor(rhs)) - } - - /** Inject implicit widening cast according to the Java semantics (5.6.1. Unary Numeric Promotion) */ - private def injectCast(ctor: xt.Expr => xt.Expr)(e0: Tree)(using DefContext): xt.Expr = { - val e = extractTree(e0) - val etpe = extractType(e0) - - val id = { (e: xt.Expr) => e } - val widen32 = { (e: xt.Expr) => xt.BVWideningCast(e, xt.Int32Type().copiedFrom(e)).copiedFrom(e) } - - val ector = etpe match { - case xt.BVType(true, 8 | 16) => widen32 - case xt.BVType(true, 32 | 64) => id - case xt.BVType(_, s) => outOfSubsetError(e0, s"Unexpected integer type of $s bits") - case _ => id - } - - ctor(ector(e)) - } - - private def extractLocalClassType(sym: Symbol, cid: Identifier, tps: List[xt.Type]) - (using dctx: DefContext, pos: Position): xt.LocalClassType = { - - val tparamsSyms = typeParamSymbols(sym.tpe.typeArgs) - val tparams = extractTypeParams(tparamsSyms) - - val tpCtx = dctx.copy(tparams = dctx.tparams ++ (tparamsSyms zip tparams).toMap) - val parents = sym.tpe.parents.filterNot(isIgnored).map(extractType(_)(using tpCtx, pos)) - - xt.LocalClassType(cid, tparams.map(xt.TypeParameterDef(_)), tps, parents) - } - - private def extractType(t: Tree)(using dctx: DefContext): xt.Type = { - extractType(t.tpe)(using dctx, t.pos) - } - - object StrictBVType { - def unapply(tpe: xt.Type): Option[(Boolean, Int)] = tpe match { - case xt.AnnotatedType(xt.BVType(signed, size), flags) if flags.contains(xt.StrictBV) => - Some((signed , size)) - case _ => None - } - } - - private def extractType(tpt: Type)(using dctx: DefContext, pos: Position): xt.Type = (tpt match { - case CharTpe => xt.CharType() - case ByteTpe => xt.Int8Type() - case ShortTpe => xt.Int16Type() - case IntTpe => xt.Int32Type() - case LongTpe => xt.Int64Type() - case BooleanTpe => xt.BooleanType() - case UnitTpe => xt.UnitType() - case AnyTpe | ObjectTpe | AnyRefTpe => xt.AnyType() - case NothingTpe => xt.NothingType() - - case ct: ConstantType => - extractType(ct.value.tpe) - - case TypeBounds(lo, hi) => - xt.TypeBounds(extractType(lo), extractType(hi), Seq.empty) - - case TypeRef(_, sym, _) if isBigIntSym(sym) => xt.IntegerType() - case TypeRef(_, sym, _) if isRealSym(sym) => xt.RealType() - case TypeRef(_, sym, _) if isStringSym(sym) => xt.StringType() - - case TypeRef(_, sym, btt :: Nil) if isSetSym(sym) => - xt.SetType(extractType(btt)) - - case TypeRef(_, sym, btt :: Nil) if isBagSym(sym) => - xt.BagType(extractType(btt)) - - case FrontendBVType(signed, size) => - xt.AnnotatedType(xt.BVType(signed, size), Seq(xt.StrictBV)) - - case TypeRef(_, sym, List(ftt,ttt)) if isMapSym(sym) => - xt.MapType(extractType(ftt), xt.ClassType(getIdentifier(optionSymbol), Seq(extractType(ttt))).setPos(pos)) - - case TypeRef(_, sym, List(ftt,ttt)) if isMutableMapSym(sym) => - xt.MutableMapType(extractType(ftt), extractType(ttt)) - - case TypeRef(_, sym, tps) if isTuple(sym, tps.size) => - xt.TupleType(tps map extractType) - - case TypeRef(_, sym, btt :: Nil) if isArrayClassSym(sym) => - xt.ArrayType(extractType(btt)) - - case TypeRef(_, sym, subs) if subs.nonEmpty && isFunction(sym, subs.size - 1) => - val from = subs.init - val to = subs.last - xt.FunctionType(from map extractType, extractType(to)) - - case TypeRef(_, sym, tps) if isByNameSym(sym) => - extractType(tps.head) - - case TypeRef(_, sym, _) if sym.isAbstractType && (dctx.tparams contains sym) => - dctx.tparams(sym) - - case tr @ TypeRef(_, sym, tps) if sym.isClass => - val id = getIdentifier(sym) - dctx.localClasses.get(id) match { - case Some(lcd) => extractLocalClassType(sym, lcd.id, tps map extractType) - case None => xt.ClassType(id, tps map extractType) - } - - case tr @ TypeRef(_, sym, tps) if dctx.resolveTypes && (sym.isAliasType || sym.isAbstractType) => - if (tr != tr.dealias) extractType(tr.dealias) - else extractType(tr)(using dctx.setResolveTypes(false), pos) - - case tr @ TypeRef(prefix, sym, tps) if sym.isAbstractType || sym.isAliasType => - val selector = prefix match { - case _ if prefix.typeSymbol.isModuleClass => - None - case thiss: ThisType => - val id = getIdentifier(thiss.sym) - dctx.localClasses.get(id) match { - case Some(lcd) => Some(xt.LocalThis(extractType(thiss).asInstanceOf[xt.LocalClassType])) - case None => Some(xt.This(extractType(thiss).asInstanceOf[xt.ClassType])) - } - case SingleType(_, sym) if dctx.vars contains sym => - Some(dctx.vars(sym)()) - case SingleType(_, sym) => - ctx.reporter.internalError(s"extractType: could not find variable $sym in context") - case _ => - None - } - - xt.TypeApply(xt.TypeSelect(selector, getIdentifier(sym)), tps map extractType) - - case tt: ThisType => - val id = getIdentifier(tt.sym) - val params = tt.sym.typeParams.map(dctx.tparams) - dctx.localClasses.get(id) match { - case Some(lcd) => extractLocalClassType(tt.sym, lcd.id, params) - case None => xt.ClassType(id, params) - } - - case st @ SuperType(thisTpe, superTpe) => - extractType(superTpe) - - case SingleType(pre, sym) if sym.isModule => - xt.ClassType(getIdentifier(sym.moduleClass), Nil) - - case SingleType(_, sym) if sym.isTerm => - extractType(tpt.widen) - - case RefinedType(parents, defs) if defs.isEmpty => - /** - * For cases like if(a) e1 else e2 where - * e1 <: C1, - * e2 <: C2, - * with C1,C2 <: C - * - * Scala might infer a type for C such as: Product with Serializable with C - * we generalize to the first known type, e.g. C. - */ - parents.find(ptpe => !isIgnored(ptpe)).map(extractType) match { - case Some(tpe) => - tpe - - case None => - outOfSubsetError(tpt.typeSymbol.pos, s"Stainless does not support type $tpt") - } - - case AnnotatedType(Seq(ExIndexedAt(n)), tpe) => - xt.AnnotatedType(extractType(tpe), Seq(xt.IndexedAt(extractTree(n)))) - - case AnnotatedType(_, tpe) => extractType(tpe) - - case _ => - if (tpt ne null) { - outOfSubsetError(tpt.typeSymbol.pos, s"Stainless does not support type $tpt") - throw new Exception() - } else { - outOfSubsetError(NoPosition, "Tree with null-pointer as type found") - } - }).setPos(pos) - - // @extern function may contain constructs that are not supported by Stainless. - // However, we must be sure that we have captured all contracts. - // For instance, the following function uses the `.toString` method that we do not support: - // @extern - // def f(x: BigInt, y: BigInt): Unit = { - // require(x >= 10) - // val t = x.toString - // ... - // } - // We will recognize the `require(x >= 10)` as a spec. - // The extraction will stop at `val t = x.toString` and replace it (and the rest of the function) with a NoTree. - // This is fine if there is no further specs, as the body of @extern function are meant to be removed anyway. - // On the other hand, if a spec appears further, that spec won't be extracted, which is problematic because - // the implementation may rely on such assumption. - // For instance, the `require(x >= y)` will be dropped - // @extern - // def f(x: BigInt, y: BigInt): Unit = { - // require(x >= 10) - // val t = x.toString - // require(x >= y) - // ... - // } - // Here, the `t` does not interfere with the below require, but in general, specs cannot always be faithfully extracted - // in presence of previously-encountered unsupported features. - private def checkNoSpecsRemaining(tree: Tree): Unit = { - val traverser = new Traverser { - override def traverse(tree: Tree): Unit = tree match { - case ExRequiredExpression(_, _) => - outOfSubsetError(tree, s"This require cannot be extracted due to encountering an unsupported feature before.") - - case ExEnsuredExpression(_, _, _) => - outOfSubsetError(tree, s"This ensure cannot be extracted due to encountering an unsupported feature before.") - - case ExReadsExpression(_) => - outOfSubsetError(tree, s"This reads cannot be extracted due to encountering an unsupported feature before.") - - case ExModifiesExpression(_) => - outOfSubsetError(tree, s"This modifies cannot be extracted due to encountering an unsupported feature before.") - - case _ => super.traverse(tree) - } - } - traverser.traverse(tree) - } -} - diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/FragmentChecker.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/FragmentChecker.scala deleted file mode 100644 index 314792b62d..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/FragmentChecker.scala +++ /dev/null @@ -1,535 +0,0 @@ -package stainless.frontends.scalac - -import scala.collection.mutable -import scala.tools.nsc.SubComponent - -/** - * This class contains a traverser that rejects Scala programs that don't fit in the - * PureScala or ImperativeScala fragments. - * - * Some interesting cases: - * - * - pattern match variables are fresh variables that have to "inherit" the @ghost annotation - * from the corresponding case class field - * - case classes generate a large number of synthetic methods that need to be patched with @ghost - * in cases where there are @ghost parameters - * - some case class synthetics will contain invalid accesses to ghost code (i.e. methods equals and unapply) - * but we don't report the errors in synthetic code. This is harmless and the ghost code will be - * removed as usual - * - * This class mutates some symbols by adding the @ghost annotation (see cases above). The AST is not changed. - */ -trait FragmentChecker extends SubComponent { self: StainlessExtraction => - import global._ - - import ExpressionExtractors.ExCall - import StructuralExtractors.ExObjectDef - - private val errors = mutable.Set.empty[(Int, Int, String)] - - /** - * Report an error, unless there is already an error with the same message reported in an enclosing position. - */ - private def reportError(pos: Position, msg: String): Unit = { - if (!errorsEnclosing(pos.start, pos.end)(msg)) { - ctx.reporter.error(pos, msg) - errors += ((pos.start, pos.end, msg)) - } - } - - private def errorsEnclosing(start: Int, end: Int): Set[String] = { - errors.flatMap { case (s, e, msg) => - if (s <= start && end <= e) Some(msg) - else None - }.toSet - } - - def hasErrors(): Boolean = errors.nonEmpty - - // Note: we wrap Symbols into an Option to emphasize the fact that the symbol may not exist - // and that care must be taken in case it does not. - private def getClassIfDefinedOrNone(cls: String): Option[ClassSymbol] = { - val sym = rootMirror.getClassIfDefined(cls) - if (sym.exists) Some(sym.asClass) else None - } - - private def getModuleIfDefinedOrNone(mod: String): Option[ModuleSymbol] = { - val sym = rootMirror.getModuleIfDefined(mod) - if (sym.exists) Some(sym.asModule) else None - } - - private def getModuleClassIfDefinedOrNone(cls: String): Option[Symbol] = - getModuleIfDefinedOrNone(cls).map(_.moduleClass) - - private def getPackageIfDefinedOrNone(pkg: String): Option[ModuleSymbol] = { - val sym = rootMirror.getPackageIfDefined(pkg) - if (sym.exists) Some(sym.asModule) else None - } - - class GhostAnnotationChecker extends Traverser { - private val ghostAnnotation = getClassIfDefinedOrNone("stainless.annotation.ghost") - - private var ghostContext: Boolean = false - - def withinGhostContext[A](body: => A): A = { - val old = ghostContext - ghostContext = true - val res = body - ghostContext = old - res - } - - var patternContext: Boolean = false - - def withinPatternContext[A](body: => A): A = { - val old = patternContext - patternContext = true - val res = body - patternContext = old - res - } - - private def isGhostDefaultGetter(m: Tree): Boolean = m match { - case DefDef(mods, name, tparams, vparamss, tpt, field @ Select(This(_), f)) => - field.symbol.hasGhostAnnotation - case _ => false - } - - /** - * Synthetics introduced by typer for case classes won't propagate the @ghost annotation - * to the copy method or for default arguments, leading to invalid accesses from non-ghost - * code to ghost code. We fix it here by adding @ghost to these synthetics - */ - private def propagateGhostAnnotation(m: MemberDef): Unit = { - val sym = m.symbol - - if (sym.isArtifact) m match { - case vd @ ValDef(mods, _, _, ExCall(_, c, _, _)) if isDefaultGetter(c) && c.hasGhostAnnotation => - sym.addGhostAnnotation() - case _ => () - } - else if (sym.isCaseCopy) { - val caseClassParams = sym.owner.primaryConstructor.info.params - for ((copyParam, caseParam) <- sym.info.params.zip(caseClassParams) if caseParam.hasGhostAnnotation) - copyParam.addGhostAnnotation() - } - else if (sym.isDefaultGetter && isGhostDefaultGetter(m)) { - sym.addGhostAnnotation() - } - else if (sym.isSetter && sym.hasGhostAnnotation) { - // make the setter parameter ghost but the setter itself stays non-ghost. this allows it - // to be called from non-ghost code and at the same time allows assigning ghost state via the ghost argument - sym.removeGhostAnnotation() - sym.info.params.head.addGhostAnnotation() - } - else if (sym.isModuleOrModuleClass && sym.companionClass.hasGhostAnnotation) { - sym.addGhostAnnotation() - sym.moduleClass.addGhostAnnotation() - } - } - - /** - * Methods that should be considered as part of a ghost context, even though they are not - * explicitly ghost. They are typically synthetic methods for case classes that are harmless - * if they touch ghost code - */ - private def effectivelyGhost(sym: Symbol): Boolean = { - sym.isSynthetic && - ( - ( - sym.owner.isCaseClass && - ( - sym.name == nme.equals_ || - sym.name == nme.productElement || - sym.name == nme.hashCode_ - ) - ) || - ( - sym.owner.companionClass.isCaseClass && - sym.name == nme.unapply - ) - ) - } - - private def symbolIndex(tree: Tree): Int = tree match { - case Apply(fun, args) => symbolIndex(fun) + 1 - case _ => 0 - } - - override def traverse(tree: Tree): Unit = { - val sym = tree.symbol - tree match { - case Ident(_) if sym.hasGhostAnnotation && !ghostContext => - reportError(tree.pos, s"Cannot access a ghost symbol outside of a ghost context. [ $tree in $currentOwner ]") - - case Select(qual, _) if sym.hasGhostAnnotation && !ghostContext => - reportError(tree.pos, s"Cannot access a ghost symbol outside of a ghost context. [ $tree in $currentOwner ]") - super.traverse(tree) - - case m: MemberDef => - if (m.symbol.isSynthetic || m.symbol.isAccessor || m.symbol.isArtifact) - propagateGhostAnnotation(m) - - // We consider some synthetic methods values as being inside ghost - // but don't auto-annotate as such because we don't want all code to be removed. - // They are synthetic case class methods that are harmless if they see some ghost nulls - if (m.symbol.hasGhostAnnotation || effectivelyGhost(sym)) - withinGhostContext(super.traverse(m)) - else - super.traverse(m) - - case CaseDef(pat, guard, body) => - withinPatternContext(traverse(pat)) - traverse(guard) - traverse(body) - - case f @ Apply(fun, args) if fun.symbol.hasGhostAnnotation => - traverse(fun) - withinGhostContext(traverseTrees(args)) - - case Apply(fun, args) if patternContext => - traverse(fun) - - // pattern match variables need to get the ghost annotation from their case class argument - for ((param, arg) <- fun.tpe.paramLists(symbolIndex(fun)).zip(args)) - if (param.hasGhostAnnotation) { - arg match { - case b@Bind(_, body) => - b.symbol.addGhostAnnotation() - traverse(body) - case _ => - traverse(arg) - } - } else - traverse(arg) - - case f @ Apply(fun, args) => - traverse(fun) - - for ((param, arg) <- f.symbol.info.paramLists(symbolIndex(fun)).zip(args)) - if (param.hasGhostAnnotation) - withinGhostContext(traverse(arg)) - else - traverse(arg) - - case Assign(lhs, rhs) => - if (lhs.symbol.hasGhostAnnotation) - withinGhostContext(traverse(rhs)) - else - super.traverse(tree) - - case _ => - super.traverse(tree) - } - } - - extension (sym: Symbol) { - private def hasGhostAnnotation: Boolean = ghostAnnotation.exists(sym.hasAnnotation) - private def addGhostAnnotation(): Unit = ghostAnnotation.foreach(sym.addAnnotation) - private def removeGhostAnnotation(): Unit = ghostAnnotation.foreach(sym.removeAnnotation) - } - } - - class Checker extends Traverser { - private val ScalaEnsuringMethod = rootMirror.getRequiredModule("scala.Predef").info.decl(newTermName("Ensuring")) - .alternatives.filter(_.isMethod).head - - private val StainlessLangPackage = getPackageIfDefinedOrNone("stainless.lang") - private val ExternAnnotation = getClassIfDefinedOrNone("stainless.annotation.extern") - private val IgnoreAnnotation = getClassIfDefinedOrNone("stainless.annotation.ignore") - private val StainlessOld = StainlessLangPackage.map(_.info.decl(newTermName("old"))) - private val StainlessBVObject = getModuleIfDefinedOrNone("stainless.math.BitVectors") - private val StainlessBVClass = getClassIfDefinedOrNone("stainless.math.BitVectors.BV") - private val StainlessBVArrayIndex = getClassIfDefinedOrNone("stainless.math.BitVectors.ArrayIndexing").map(_.companionClass) - private val BigInt_ApplyMethods = - (StainlessLangPackage.map(_.info.decl(newTermName("BigInt")).info.decl(nme.apply).alternatives).getOrElse(Nil) - ++ rootMirror.getRequiredModule("scala.math.BigInt").info.decl(nme.apply).alternatives).toSet - - private val RequireMethods = - definitions.PredefModule.info.decl(newTermName("require")).alternatives.toSet - ++ getModuleIfDefinedOrNone("stainless.lang.StaticChecks").map(_.info.decl(newTermName("require")).alternatives.toSet).getOrElse(Set.empty) - - private val stainlessReplacement = mutable.Map( - definitions.ListClass -> "stainless.collection.List", - definitions.NilModule.moduleClass -> "stainless.collection.Nil", - definitions.OptionClass -> "stainless.lang.Option", - rootMirror.getRequiredClass("scala.util.Either") -> "stainless.lang.Either", - definitions.ScalaPackageClass.info.decl(newTermName("Nil")) -> "stainless.collection.Nil", - rootMirror.getRequiredClass("scala.collection.Map") -> "stainless.lang.Map", - rootMirror.getRequiredClass("scala.collection.immutable.Map") -> "stainless.lang.Map", - rootMirror.getRequiredClass("scala.collection.Set") -> "stainless.lang.Set", - rootMirror.getRequiredClass("scala.collection.immutable.Set") -> "stainless.lang.Set", - ) - - // We do not in general support the types for these methods, but we do extract them. - // We therefore skip the typing check for them. - private val bvSpecialFunctions = - StainlessBVObject.map(bvObj => Set( - bvObj.info.decl(newTermName("min")), - bvObj.info.decl(newTermName("max")) - )).getOrElse(Set.empty) ++ - StainlessBVArrayIndex.map(bvArray => Set(bvArray.info.decl(nme.apply))).getOrElse(Set.empty) ++ - StainlessBVClass.map(bvClass => Set( - bvClass.info.decl(newTermName("widen")), - bvClass.info.decl(newTermName("narrow")), - bvClass.info.decl(newTermName("toSigned")), - bvClass.info.decl(newTermName("toUnsigned")), - )).getOrElse(Set.empty) - - private val objectMethods = Set[Symbol]( - definitions.Object_eq, definitions.Object_ne, definitions.Object_synchronized, definitions.Object_clone, - definitions.Object_finalize, definitions.Object_notify, definitions.Object_notifyAll, definitions.Object_getClass - ) - - // Types that may show up due to inference that will later cause a missing dependency error - // should we not report an error before. - private val unsupportedTypes: Set[Symbol] = Set( - definitions.SerializableClass, - definitions.ProductRootClass, - ) - - // method println is overloaded, so we need to add all overloads to our map - addOverloadsToMap(definitions.PredefModule.info.decl(newTermName("println")), "stainless.io.StdOut.println") - - private def addOverloadsToMap(sym: Symbol, replacement: String): Unit = { - sym.alternatives.foreach(a => stainlessReplacement += a -> replacement) - } - - private def checkType(tree: Tree, tpe0: Type): Unit = { - object Errors { - def empty: Errors = Errors(Set.empty, Set.empty) - } - case class Errors(libRepls: Set[(Symbol, String)], unsupported: Set[Type]) { - def addReplacement(ts: (Symbol, String)): Errors = copy(libRepls = libRepls + ts) - def addUnsupported(t: Type): Errors = copy(unsupported = unsupported + t) - } - var errs = Errors.empty - def strip(tpe: Type): Type = { - // The extraction frontend filters out type that are unsupported before extracting them, - // so we do here the same to avoid having false positive. - tpe.map { - case tpe@RefinedType(parents, defs) if defs.isEmpty => - val filtered = parents.filterNot(isIgnored) - if (filtered.size == 1) filtered.head - else tpe - case tpe => tpe - } - } - val tpe = strip(tpe0) - for (tp <- tpe) { - val tpSym = tp.typeSymbol - if (stainlessReplacement.contains(tpSym)) - errs = errs.addReplacement(tpSym -> stainlessReplacement(tpSym)) - else tp match { - case _: ExistentialType => errs = errs.addUnsupported(tp) - case _ => () - } - } - - for ((sym, replacement) <- errs.libRepls) { - reportError(tree.pos, s"Scala API `${sym.name}` is not directly supported, please use `$replacement` instead.") - } - - def contains(hay: Type, needle: Type): Boolean = { - var found = false - hay.foreach { tp => if (tp eq needle) found = true } - found - } - - def sourceOfType(tree: Tree, tp: Type): Unit = { - tree match { - case dd: DefDef => - // It seems to be impossible to know whether a type was inferred or ascribed - val ddTpe = strip(dd.tpt.tpe) - if (existentialsInType(ddTpe).nonEmpty) { - reportError(dd.tpt.pos, s"Hint: the return type of ${dd.name} is `$ddTpe`") - sourceOfType(dd.rhs, tp) - } - case bl: Block => sourceOfType(bl.expr, tp) - case vd: ValDef => - val vdTpe = strip(repackExistential(vd.tpt.tpe)) - if (existentialsInType(vdTpe).nonEmpty) { - reportError(vd.tpt.pos, s"Hint: the type of ${vd.name} is `$vdTpe`") - sourceOfType(vd.rhs, tp) - } - case ite: If => - def branches(ite: If): Seq[Tree] = { - Seq(ite.thenp) ++ (ite.elsep match { - case ite2: If => branches(ite2) - case t => Seq(t) - }) - } - val iteTp = strip(repackExistential(ite.tpe)) - val iteTpWiden = iteTp.widen - if (existentialsInType(iteTpWiden).nonEmpty) { - reportError(ite.pos, s"Hint: the widened type of this if expression is `$iteTpWiden`") - val brchs = branches(ite) - for (branch <- brchs) { - reportError(branch.pos, s"Hint: this branch type is `${branch.tpe}`") - } - } - case mtch: Match => - val mtchTp = strip(repackExistential(mtch.tpe)) - val mtchTpWiden = mtchTp.widen - if (existentialsInType(mtchTpWiden).nonEmpty) { - reportError(mtch.pos, s"Hint: the widened type of this match expression is `$mtchTpWiden`") - for (branch <- mtch.cases) { - reportError(branch.pos, s"Hint: this case type is `${branch.tpe}`") - } - } - case _ => () - } - } - - for (tp <- errs.unsupported) { - reportError(tree.pos, s"Type `$tp` is unsupported") - sourceOfType(tree, tp) - } - } - - private var classBody = false - def inClassBody[T](f: => T): T = { - val old = classBody - classBody = true - val res = f - classBody = old - res - } - - override def traverse(tree: Tree): Unit = { - val sym = tree.symbol - if (sym ne null) { - // exit early if it's a subtree we shouldn't validate - if (skipTraversal(sym)) { - return - } - // Ignore class constructors because they duplicate param accessors (fields) which we already check - // or skip if they are annotated with @extern. - // We do not check the *type* for unapply call since these refer to Scala Option API - // These can be extracted anyway (note that we still validate the sub-trees) - if ((sym.tpe ne null) && !sym.isClassConstructor && sym.name != nme.unapply) { - checkType(tree, sym.tpe) - } - } - - tree match { - case od @ ExObjectDef(_, tmpl) => - if (tmpl.parents.exists(p => !isIgnored(p.tpe))) { - reportError(tree.pos, "Objects cannot extend classes or implement traits, use a case object instead") - } - super.traverse(od) - - case ClassDef(mods, name, tparams, impl) => - val isSupported = { - sym.isAbstractClass || - sym.isCaseClass || - sym.isModuleClass || - sym.isAnonymousClass || - sym.isImplicit || - sym.isNonBottomSubClass(definitions.AnnotationClass) - } - - if (!isSupported) { - reportError(tree.pos, "Only abstract classes, case classes, anonymous classes, and objects are allowed in Stainless.") - } - - val parents = impl.parents.map(_.tpe).filterNot(isIgnored) - if (parents.length > 1) { - reportError(tree.pos, s"Stainless supports only simple type hierarchies: Classes can only inherit from a single class/trait") - } - - impl foreach { - case cd: ClassDef if !cd.symbol.owner.isMethod => - reportError(cd.pos, "Classes can only be defined at the top-level, within objects, or within methods") - - case _ => () - } - - atOwner(sym)(traverse(impl)) - - case DefDef(_, _, _, _, _, rhs) if sym.isClassConstructor => - if (sym.isAuxiliaryConstructor) - reportError(tree.pos, "Auxiliary constructors are not allowed in Stainless.") - if (!sym.info.paramss.flatten.isEmpty && sym.owner.isAbstractClass) - reportError(tree.pos, "Abstract class constructor parameters are not allowed in Stainless.") - atOwner(sym)(traverse(rhs)) - - case DefDef(_, _, _, _, _, rhs) => - // recurse only inside `rhs`, as parameter/type parameters have been checked already in `checkType` - atOwner(sym)(traverse(rhs)) - - case vd @ ValDef(mods, _, _, _) if sym.owner.isClass && !sym.owner.isAbstractClass && mods.isMutable && !mods.isCaseAccessor => - reportError(tree.pos, "Variables are only allowed within functions and as constructor parameters in Stainless.") - - case Apply(fun, List(arg)) if StainlessOld.contains(sym) => - arg match { - case This(_) => () - case t if t.symbol != null && t.symbol.isVariable => () - case t => - reportError(t.pos, s"Stainless `old` is only defined on `this` and variables.") - } - super.traverse(tree) - - case Apply(fun, args) if BigInt_ApplyMethods(sym) => - if (args.size != 1 || !args.head.isInstanceOf[Literal]) - reportError(args.head.pos, "Only literal arguments are allowed for BigInt.") - super.traverse(tree) - - case ExCall(Some(s @ Select(rec: Super, _)), _, _, _) => - if (s.symbol.isAbstract && !s.symbol.isConstructor) - reportError(tree.pos, "Cannot issue a super call to an abstract method.") - super.traverse(tree) - - case Throw(ex) => - reportError(tree.pos, "throw expressions are unsupported in Stainless") - super.traverse(tree) - - case Apply(fun, args) => - if (stainlessReplacement.contains(sym)) - reportError(tree.pos, s"Scala API ($sym) no longer extracted, please use ${stainlessReplacement(sym)}") - if (objectMethods(sym)) - reportError(tree.pos, s"Method ${sym.name} on Object is not supported") - super.traverse(tree) - - case Try(_, cases, finalizer) => - if (cases.isEmpty && finalizer.isEmpty) reportError(tree.pos, "try expressions are not supported in Stainless") - else if (cases.isEmpty && !finalizer.isEmpty) reportError(tree.pos, "try-finally expressions are not supported in Stainless") - else if (finalizer.isEmpty) reportError(tree.pos, "try-catch expressions are not supported in Stainless") - else reportError(tree.pos, "try-catch-finally expressions are not supported in Stainless") - super.traverse(tree) - - case Template(_, self, body) => - for (t <- body if !(t.isDef || t.isType || t.isEmpty || t.isInstanceOf[Import])) { - // `require` is allowed inside classes, but not inside objects - if (RequireMethods(t.symbol)) - if (currentOwner.isModuleClass) - reportError(t.pos, "`require` is not allowed inside object bodies.") - else () - else - reportError(t.pos, "Only definitions are allowed inside class bodies.") - } - // We do not visit parents, as they will contain reference to synthetic classes such as Product, Serializable, etc. - atOwner(sym) { - traverse(self) - traverseTrees(body) - } - - case _ => - super.traverse(tree) - } - } - - private def skipTraversal(sym: Symbol): Boolean = { - val isExtern = ExternAnnotation.exists(sym.hasAnnotation) - val isIgnore = IgnoreAnnotation.exists(sym.hasAnnotation) - // * If it's a synthetic symbol, we will still visit if it is either: - // -the scala Ensuring method (that creates the Ensuring class), which for some reasons is synthetic (though StaticChecks.Ensuring is not for instance) - // -an anonymous function - // -an anonymous class - // * We furthermore ignore ClassTag[T].apply() that appear for Array operations, which we can still extract. - isExtern || isIgnore || (sym.isSynthetic && (sym ne ScalaEnsuringMethod) && !sym.isAnonymousFunction && !sym.isAnonymousFunction) || - (sym.owner eq definitions.ClassTagModule.moduleClass) || - bvSpecialFunctions(sym) || StainlessBVClass.contains(sym) - } - } -} diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/GhostAccessRewriter.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/GhostAccessRewriter.scala deleted file mode 100644 index b4b6424c44..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/GhostAccessRewriter.scala +++ /dev/null @@ -1,106 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import extraction.xlang.{trees => xt} -import scala.tools.nsc._ -import scala.tools.nsc.transform.Transform - -import stainless.frontend.{CallBack, UnsupportedCodeException} - -/** Extract each compilation unit and forward them to the Compiler callback */ -trait GhostAccessRewriter extends Transform { - import global._ - - val pluginOptions: PluginOptions - val phaseName = "ghost-removal" - - override def newTransformer(unit: global.CompilationUnit): Transformer = { - if (pluginOptions.enableGhostElimination) { - new GhostRewriteTransformer - } else { - new IdentityTransformer - } - } - - lazy val ghostAnnotation = rootMirror.getRequiredClass("stainless.annotation.ghost") - - private class IdentityTransformer extends Transformer { - override def transform(tree: Tree): Tree = tree - } - - private class GhostRewriteTransformer extends Transformer { - - /** - * Is this symbol @ghost, or enclosed inside a ghost definition? - * - * Note: We exclude constructors from being ghost because we can't remove them anyway - */ - private def effectivelyGhost(sym: Symbol): Boolean = - (!sym.isConstructor && - sym.ownersIterator.exists(_.hasAnnotation(ghostAnnotation))) - - private def symbolIndex(tree: Tree): Int = tree match { - case Apply(fun, args) => symbolIndex(fun) + 1 - case _ => 0 - } - - override def transform(tree: Tree): Tree = tree match { - case Ident(_) if effectivelyGhost(tree.symbol) => - gen.mkZero(tree.tpe) - - case Select(_, _) if effectivelyGhost(tree.symbol) => - gen.mkZero(tree.tpe) - - case DefDef(mods, name, tparams, vparamss, tpt, rhs) if effectivelyGhost(tree.symbol) => - treeCopy.DefDef(tree, mods, name, tparams, vparamss, tpt, gen.mkZero(rhs.tpe)) - - case ValDef(mods, name, tpt, rhs) if effectivelyGhost(tree.symbol) => - treeCopy.ValDef(tree, mods, name, tpt, gen.mkZero(rhs.tpe)) - - // labels are generated by pattern matching but they are not real applications and should not - // be touched. They are simple jumps and tampering with them may lead to runtime verification errors - case Apply(fun, args) if tree.symbol.isLabel => - treeCopy.Apply(tree, fun, transformTrees(args)) - - // This is similarly generated by pattern matching. - // For instance, the pattern `(h: T, t: List[T]): Cons[T]` would match this case with: - // TypeTree = MethodType with params h: T, t: List[T] and result type Cons[T] (pattern matches are represented by a MethodType) - // args = (h @ _), (t @ _) (i.e. Bind trees) - case Apply(tt@TypeTree(), args) => - treeCopy.Apply(tree, tt, transformTrees(args)) - - case f @ Apply(fun, args) if effectivelyGhost(fun.symbol) => - gen.mkZero(tree.tpe) - - case f @ Apply(fun, args) => - val fun1 = super.transform(fun) - val symParams0 = f.symbol.info.paramLists(symbolIndex(fun)) - - // if the function has a repeated parameter the lengths of the two lists don't match - // so we fill params up to the argument list length with the last parameter - val symParams = if (symParams0.nonEmpty && definitions.isRepeated(symParams0.last)) - symParams0 ++ List.fill(args.length - symParams0.length)(symParams0.last) - else - symParams0 - - val args1 = for ((param, arg) <- symParams.zip(args)) yield - if (param.hasAnnotation(ghostAnnotation)) - gen.mkZero(param.tpe) - else - transform(arg) - - treeCopy.Apply(tree, fun1, args1) - - case Assign(lhs, rhs) => - if (effectivelyGhost(lhs.symbol)) - treeCopy.Assign(tree, lhs, gen.mkZero(rhs.tpe)) - else - super.transform(tree) - - case _ => super.transform(tree) - } - } - -} diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/ScalaCompiler.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/ScalaCompiler.scala deleted file mode 100644 index aca83e5ede..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/ScalaCompiler.scala +++ /dev/null @@ -1,226 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import ast.SymbolIdentifier -import frontend.{ Frontend, ThreadedFrontend, FrontendFactory, CallBack } - -import scala.tools.nsc.{ Global, Settings => NSCSettings, CompilerCommand } -import scala.reflect.internal.Positions - -import scala.collection.mutable.{ Map => MutableMap } - -object SymbolMapping { - def getPath(sym: Global#Symbol): String = - sym.ownerChain.reverse map { s => s"${s.name}#${kind(s)}" } mkString "." - - def empty = new SymbolMapping() - - private def kind(sym: Global#Symbol): String = { - if (sym.isPackageClass) "0" - else if (sym.isModule) "1" - else if (sym.isModuleClass) "2" - else if (sym.isClass) "c" + sym.id - else if (sym.isMethod) "m" + sym.id - else if (sym.isType) "tp" + sym.id - else if (sym.isTerm) "t" + sym.id // Many things are terms... Fallback to its id - else ??? - } -} - -class SymbolMapping { - import SymbolMapping.getPath - - private[this] val ignoredClasses = Set( - "scala.Any", - "scala.AnyRef", - "scala.Product", - "scala.Serializable", - "java.lang.Object", - "java.lang.Serializable", - ) - - // Note: We can't compare with the global symbols here because - // the symbol mapping class is re-used across compiler runs - // and thus across `Global` instances, so we have to check - // against the full symbol name instead. - @romac - def isIgnored(sym: Global#Symbol): Boolean = { - val name = sym.fullNameAsName('.').decode.trim - ignoredClasses contains name - } - - def topmostAncestor(sym: Global#Symbol): Global#Symbol = { - sym.overrideChain - .filterNot(s => isIgnored(s.owner)) - .lastOption - .getOrElse(sym) - } - - /** Get the identifier associated with the given [[sym]], creating a new one if needed. */ - def fetch(sym: Global#Symbol): SymbolIdentifier = { - val path = getPath(sym) - s2i.getOrElseUpdate(path, { - val top = topmostAncestor(sym) - val symbol = s2s.getOrElseUpdate(top, { - val name = sym.fullNameAsName('.').decode.trim - ast.Symbol(if (name endsWith "$") name.init else name) - }) - - SymbolIdentifier(symbol) - }) - } - - /** Get the identifier for the class invariant of [[sym]]. */ - def fetchInvIdForClass(sym: Global#Symbol): SymbolIdentifier = { - val id = fetch(sym) - invs.getOrElse(id, { - val res = SymbolIdentifier(invSymbol) - invs(id) = res - res - }) - } - - /** Mapping from [[Global#Symbol]] (or rather: its path) and the stainless identifier. */ - private val s2i = MutableMap[String, SymbolIdentifier]() - - /** Mapping useful to use the same top symbol mapping. */ - private val s2s = MutableMap[Global#Symbol, ast.Symbol]() - - /** Mapping for class invariants: class' id -> inv's id. */ - private val invs = MutableMap[SymbolIdentifier, SymbolIdentifier]() - private val invSymbol = stainless.ast.Symbol("inv") - -} - -class ScalaCompiler(settings: NSCSettings, val ctx: inox.Context, val callback: CallBack, val cache: SymbolMapping) - extends Global(settings, new SimpleReporter(settings, ctx.reporter)) - with Positions { self => - - // Normally, we would initialize the fields with early-initializer. Since this feature has been dropped in Scala 3, - // we work-around that by defining a dummy class overriding all members. - // This ensure that these fields are correctly initialized. - class StainlessExtractionImpl(override val global: self.type, - override val phaseName: String, - override val runsAfter: List[String], - override val runsRightAfter: Option[String], - override val runsBefore: List[String], - override val ctx: self.ctx.type, - override val callback: self.callback.type, - override val cache: self.cache.type) - extends StainlessExtraction with ASTExtractors(global) - - val stainlessExtraction = new StainlessExtractionImpl( - global = self, - phaseName = "stainless", - runsAfter = List("typer"), - runsRightAfter = None, - runsBefore = List("patmat"), - ctx = self.ctx, - callback = self.callback, - cache = self.cache - ) - - override protected def computeInternalPhases() : Unit = { - val phs = List( - syntaxAnalyzer -> "parse source into ASTs, perform simple desugaring", - analyzer.namerFactory -> "resolve names, attach symbols to named trees", - analyzer.packageObjects -> "load package objects", - analyzer.typerFactory -> "the meat and potatoes: type the trees", - stainlessExtraction -> "extracts stainless trees out of scala trees", - // We only care about the phases preceding Stainless *plus* refChecks. - // Note that the Stainless phase is only about extracting the Scala tree into Stainless tree, - // the actual processing is not done as a compiler phase but is done once the compiler finishes. - superAccessors -> "add super accessors in traits and nested classes", - patmat -> "translate match expressions", - extensionMethods -> "add extension methods for inline classes", - pickler -> "serialize symbol tables", - refChecks -> "reference/override checking, translate nested objects" - ) - phs foreach (addToPhasesSet _).tupled - } -} - -object ScalaCompiler { - - private case object CompilationTag - - /** Complying with [[frontend]]'s interface */ - class Factory( - override val extraCompilerArguments: Seq[String], - override val libraryPaths: Seq[String] - ) extends FrontendFactory { - - override def apply(ctx: inox.Context, compilerArgs: Seq[String], callback: CallBack): Frontend = - new ThreadedFrontend(callback, ctx) { - var underlying: ScalaCompiler#Run = _ - val cache = SymbolMapping.empty - - val args = allCompilerArguments(ctx, compilerArgs) - val settings = buildSettings(ctx) - - override val sources: List[String] = getFiles(args, ctx, settings) - - override def initRun(): Unit = { - assert(underlying == null) - val compiler = new ScalaCompiler(settings, ctx, this.callback, cache) - underlying = new compiler.Run - } - - override def onRun(): Unit = { - def report(msg: String) = ctx.reporter.emit(ctx.reporter.ProgressMessage(ctx.reporter.INFO, CompilationTag, msg)) - report(s"Compiling with standard Scala ${Main.compilerVersion} compiler front end...") - underlying.compile(sources) - report("Finished compiling") - } - - override def onEnd(): Unit = { - underlying = null - } - - override def onStop(thread: Thread): Unit = { - underlying.cancel() - thread.join() - } - } - } - - /** Let the frontend analyses the arguments to understand which files should be compiled. */ - private def getFiles(compilerArgs: Seq[String], ctx: inox.Context, settings: NSCSettings): List[String] = { - val command = new CompilerCommand(compilerArgs.toList, settings) { - override val cmdName = "stainless" - } - - if (!command.ok) { ctx.reporter.fatalError("No input program.") } - - command.files - } - - /** Build settings for the nsc tools. */ - private def buildSettings(ctx: inox.Context): NSCSettings = { - val settings = new NSCSettings - - // Attempt to find where the scala lib is. - val scalaLib: String = Option(scala.Predef.getClass.getProtectionDomain.getCodeSource) map { - _.getLocation.getPath - } getOrElse { ctx.reporter.fatalError("No Scala library found.") } - - settings.classpath.value = scalaLib - settings.usejavacp.value = false - settings.feature.value = true - settings.unchecked.value = true - settings.deprecation.value = true - settings.Yrangepos.value = true - // FIXME: When compiling the Stainless lib sources, Scalac looks (for some reasons) for stainless/package.class. - // This compiled packaged.class belongs to stainless-core and has nothing to do with Stainless library; - // it is furthermore compiled with Scala 3. - // Scalac complains that it can't ready TASTy files unless the following option is set. - // The ideal solution would be to somehow inform Scalac to not look for stainless-core .class file. - // Note that adding a dummy stainless/package.scala to Stainless lib does not resolve the issue. - settings.YtastyReader.value = true - - settings - } - -} - diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/SimpleReporter.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/SimpleReporter.scala deleted file mode 100644 index 66032d42f3..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/SimpleReporter.scala +++ /dev/null @@ -1,93 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import scala.reflect.internal.Reporter -import scala.reflect.internal.util.{FakePos, NoPosition, Position, StringOps} -import scala.tools.nsc.Settings -import scala.tools.nsc.reporters.FilteringReporter - -/** This implements a reporter that calls the callback with every line that a - * regular ConsoleReporter would display. */ -class SimpleReporter(val settings: Settings, reporter: inox.Reporter) extends FilteringReporter { - final val ERROR_LIMIT = 5 - - private val count = scala.collection.mutable.Map[Severity, Int]( - ERROR -> 0, - WARNING -> 0, - INFO -> 0, - ) - - override def doReport(pos: Position, msg: String, severity: Severity): Unit = - printMessage(pos, msg, severity) - - override def filter(pos: Position, msg: String, severity: Severity): Int = { - if (isPatmatExhaustivity(msg)) Reporter.Suppress - else super.filter(pos, msg, severity) - } - - private def isPatmatExhaustivity(msg: String): Boolean = - msg.contains("match may not be exhaustive") - - private def label(severity: Severity): String = { - // the labels are not stable identifier, as such we cannot directly pattern patch on them, so we must explicitly compare them with == - severity match { - case x if x == ERROR => "error" - case x if x == WARNING => "warning" - case x if x == INFO => null - case _ => throw new Exception("Severity should be one of ERROR, WARNING, INFO") - } - } - - private def clabel(severity: Severity): String = { - val label0 = label(severity) - if (label0 eq null) "" else label0 + ": " - } - - private def getCountString(severity: Severity): String = - StringOps.countElementsAsString(count(severity), label(severity)) - - /** Prints the message. */ - def printMessage(msg: String, pos: inox.utils.Position, severity: Severity): Unit = { - // the labels are not stable identifier, as such we cannot directly pattern patch on them, so we must explicitly compare them with == - severity match { - case x if x == ERROR => - reporter.error(pos, msg) - case x if x == WARNING => - reporter.warning(pos, msg) - case x if x == INFO => - reporter.info(pos, msg) - case _ => - throw new Exception("Severity should be one of ERROR, WARNING, INFO") - } - } - - /** Prints the message with the given position indication. */ - def printMessage(posIn: Position, msg: String, severity: Severity): Unit = { - val pos = if (posIn eq null) NoPosition - else if (posIn.isDefined) posIn.finalPosition - else posIn - pos match { - case FakePos(fmsg) => - printMessage(fmsg+" "+msg, inox.utils.NoPosition, severity) - case NoPosition => - printMessage(msg, inox.utils.NoPosition, severity) - case _ => - val lpos = inox.utils.OffsetPosition(pos.line, pos.column, pos.point, pos.source.file.file) - printMessage(msg, lpos, severity) - } - } - - def print(pos: Position, msg: String, severity: Severity): Unit = { - printMessage(pos, clabel(severity) + msg, severity) - } - - def display(pos: Position, msg: String, severity: Severity): Unit = { - count(severity) += 1 - if (severity != ERROR || count(severity) <= ERROR_LIMIT) - print(pos, msg, severity) - } - - def displayPrompt(): Unit = {} -} diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessExtraction.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessExtraction.scala deleted file mode 100644 index 68c5f67bca..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessExtraction.scala +++ /dev/null @@ -1,55 +0,0 @@ -/* Copyright 2009-2021 EPFL, Lausanne */ - -package stainless -package frontends.scalac - -import extraction.xlang.{trees => xt} -import scala.tools.nsc -import scala.tools.nsc._ - -import stainless.frontend.{ UnsupportedCodeException, CallBack } - -/** Extract each compilation unit and forward them to the Compiler callback */ -trait StainlessExtraction extends SubComponent with CodeExtraction with FragmentChecker { - import global._ - - val phaseName = "stainless" - - val ctx: inox.Context - import ctx.given - protected val callback: CallBack - - def newPhase(prev: nsc.Phase): StdPhase = new Phase(prev) - - protected def onRun(run: () => Unit): Unit = { - run() - } - - class Phase(prev: nsc.Phase) extends StdPhase(prev) { - - override def apply(u: CompilationUnit): Unit = { - val file = u.source.file.absolute.path - val checker = new Checker - checker(u.body) - - // then check ghost accesses - val ghostChecker = new GhostAnnotationChecker - ghostChecker(u.body) - - if (!hasErrors()) { - try { - val (unit, classes, functions, typeDefs) = extractUnit(u) - callback(file, unit, classes, functions, typeDefs) - } catch { - case UnsupportedCodeException(pos, msg) => - ctx.reporter.error(pos, msg) - case e => throw e - } - } - } - - override def run(): Unit = { - onRun(() => super.run()) - } - } -} diff --git a/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessPlugin.scala b/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessPlugin.scala deleted file mode 100644 index 9fd75ca329..0000000000 --- a/frontends/scalac/src/main/scala/stainless/frontends/scalac/StainlessPlugin.scala +++ /dev/null @@ -1,175 +0,0 @@ -package stainless -package frontends -package scalac - -import scala.reflect.io.AbstractFile -import scala.reflect.internal.util.{NoPosition, Position, BatchSourceFile} -import scala.tools.nsc.Global -import scala.tools.nsc.plugins.Plugin -import scala.tools.nsc.plugins.PluginComponent -import scala.tools.nsc.reporters.{Reporter => ScalacReporter} -import inox.DebugSection -import inox.{utils => InoxPosition} -import stainless.frontend.{CallBack, Frontend} - -object StainlessPlugin { - val PluginName = "stainless" - val PluginDescription = "Inject Stainless verification pipeline" - val EnableVerificationOptionName = "verify:" - val EnableGhostEliminationOptionName = "ghost-elim:" -} - -case class PluginOptions( - var enableVerification: Boolean, - var enableGhostElimination: Boolean, -) { - def enabled: Boolean = enableVerification || enableGhostElimination -} - -class StainlessPlugin(val global: Global) extends Plugin { - import StainlessPlugin._ - - val mainHelper = new stainless.MainHelpers { - override val factory = new frontend.FrontendFactory{ - override def apply(ctx: inox.Context, compilerArgs: Seq[String], callback: CallBack): Frontend = - sys.error("stainless.MainHelpers#factory should never be called from the scalac plugin") - override protected val libraryPaths: Seq[String] = Seq.empty - } - } - - val stainlessContext: inox.Context = { - given stainless.PlainTextReporter = new stainless.PlainTextReporter(Set.empty) - mainHelper.getConfigContext(inox.Options.empty) - } - - override val name: String = PluginName - override val description: String = PluginDescription - - private val pluginOptions: PluginOptions = PluginOptions(true, true) - - override val components: List[PluginComponent] = List( - new StainlessPluginComponent(pluginOptions, global, stainlessContext), - new GhostPluginComponent(pluginOptions, global), - ) - - override def init(options: List[String], error: String => Unit): Boolean = { - for (option <- options) { - if (option.startsWith(EnableVerificationOptionName)) { - val value = option.substring(EnableVerificationOptionName.length) - parseBoolean(value, error) foreach { value => - pluginOptions.enableVerification = value - } - } - else if (option.startsWith(EnableGhostEliminationOptionName)) { - val value = option.substring(EnableGhostEliminationOptionName.length) - parseBoolean(value, error).foreach { value => - pluginOptions.enableGhostElimination = value - } - } - else { - error("Unknown option for Stainless plugin: " + option) - } - } - - pluginOptions.enabled - } - - private def parseBoolean(str: String, error: String => Unit): Option[Boolean] = { - str match { - case "false" => Some(false) - case "true" => Some(true) - case other => - error(s"Invalid boolean value: $other") - None - } - } -} - -class StainlessPluginComponent( - val pluginOptions: PluginOptions, - override val global: Global, - val stainlessContext: inox.Context -) extends PluginComponent with StainlessExtraction with ASTExtractors(global) { - - override def enabled: Boolean = pluginOptions.enableVerification - - override val ctx: inox.Context = { - val adapter = new ReporterAdapter(global.reporter, stainlessContext.reporter.debugSections) - - inox.Context( - reporter = adapter, - interruptManager = new inox.utils.InterruptManager(adapter), - options = stainlessContext.options, - timers = stainlessContext.timers, - ) - } - import ctx.given - - override protected val callback: CallBack = stainless.frontend.getCallBack - - override protected val cache: SymbolMapping = new SymbolMapping - - override val phaseName = "stainless" - override val runsAfter = List("typer") - override val runsRightAfter = None - override val runsBefore = List("patmat") - - override def onRun(run: () => Unit): Unit = try { - callback.beginExtractions() - run() - callback.endExtractions() - callback.join() - - val report = callback.getReport - report foreach { report => - report.emit(ctx) - } - } catch { - case e: Throwable => topLevelErrorHandler(e) - } -} - -class GhostPluginComponent( - val pluginOptions: PluginOptions, - val global: Global, -) extends PluginComponent with GhostAccessRewriter { - override val runsAfter = List[String]("pickler") -} - -class ReporterAdapter(underlying: ScalacReporter, debugSections: Set[DebugSection]) extends inox.PlainTextReporter(debugSections) { - private def toSourceFile(file: java.io.File): BatchSourceFile = { - new BatchSourceFile(AbstractFile.getFile(file)) - } - - private def toScalaPos(pos: InoxPosition.Position): Position = pos match { - case InoxPosition.NoPosition => - NoPosition - - case InoxPosition.OffsetPosition(_, _, point, file) => - Position.offset(toSourceFile(file), point) - - case InoxPosition.RangePosition(_, _, pointFrom, _, _, pointTo, file) => - Position.range(toSourceFile(file), pointFrom, pointFrom, pointTo) - } - - override def clearProgress(): Unit = () - - override def doEmit(message: Message): Unit = { - val pos = toScalaPos(message.position) - - message.msg match { - case msg: ReportMessage => - msg.emit(this) - - case msg: String => - message.severity match { - case INFO => underlying.echo(pos, msg) - case WARNING => underlying.warning(pos, msg) - case ERROR | FATAL | INTERNAL => underlying.error(pos, msg) - case _ => underlying.echo(pos, msg) // DEBUG messages are at reported at INFO level - } - - case _ => () - } - } -} diff --git a/libfiles.txt b/libfiles.txt index c865f531cb..23fe4b09a2 100644 --- a/libfiles.txt +++ b/libfiles.txt @@ -1,6 +1,7 @@ stainless/util/Random.scala stainless/util/Timepoint.scala stainless/lang/Option.scala +stainless/lang/Cell.scala stainless/lang/PartialFunction.scala stainless/lang/StaticChecks.scala stainless/lang/Real.scala diff --git a/sbt-plugin/src/sbt-test/sbt-plugin/ghost/build.sbt b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/build.sbt index 9592c23e84..14125c85d5 100644 --- a/sbt-plugin/src/sbt-test/sbt-plugin/ghost/build.sbt +++ b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/build.sbt @@ -16,6 +16,13 @@ lazy val basic = (project in file("basic")) Compile / run / mainClass := Some("test.Main") ) +lazy val tailrec = (project in file("tailrec")) + .enablePlugins(StainlessPlugin) + .settings(commonSettings) + .settings( + Compile / run / mainClass := Some("test.Main") + ) + lazy val `actor-tests` = (project in file("actor-tests")) .enablePlugins(StainlessPlugin) .settings(commonSettings) diff --git a/sbt-plugin/src/sbt-test/sbt-plugin/ghost/tailrec/TailRec.scala b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/tailrec/TailRec.scala new file mode 100644 index 0000000000..fdd8eac0b4 --- /dev/null +++ b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/tailrec/TailRec.scala @@ -0,0 +1,44 @@ +package test + +import stainless._ +import stainless.annotation.{ghost => ghostAnnot, _} +import stainless.lang._ +import StaticChecks._ + +object Main { + import stainless.lang.WhileDecorations + + def loop1(count: Long, l1: Long, l2: Long, l3: Long): Long = { + require(count >= 0) + decreases(count) + if (count == 0) l1 + else loop1(count - 1, l1, l2, l3) + }.ensuring(_ == l1) + + def loop2(count: Long, l1: Long, l2: Long, l3: Long): Long = { + require(count >= 0) + decreases(count) + if (count == 0) l1 + else { + val myRes = loop2(count - 1, l1, l2, l3) + ghost { + assert(myRes == l1) + } + myRes + } + }.ensuring(_ == l1) + + def whileLoop(upto: Long): Unit = { + var i: Long = 0 + (while(i < upto) { + decreases(upto - i) + i += 1 + }).invariant(i >= 0) + } + + def main(args: Array[String]): Unit = { + loop1(100000, 1, 2, 3) + loop2(100000, 1, 2, 3) + whileLoop(10000) + } +} diff --git a/sbt-plugin/src/sbt-test/sbt-plugin/ghost/test b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/test index aee9a2f286..b4277c4e3b 100644 --- a/sbt-plugin/src/sbt-test/sbt-plugin/ghost/test +++ b/sbt-plugin/src/sbt-test/sbt-plugin/ghost/test @@ -1,5 +1,6 @@ +> + tailrec/run > + basic/run $ exists basic/target/scala-2.13/classes/test/Main.class -$ exists basic/target/scala-3.2.0/classes/test/Main.class +$ exists basic/target/scala-3.3.3/classes/test/Main.class $ absent basic/target/sneakyGhostCalled basic/target/insideGhostCalled > + actor-tests/compile diff --git a/sbt-plugin/src/sbt-test/sbt-plugin/simple/test b/sbt-plugin/src/sbt-test/sbt-plugin/simple/test index 4787c620a4..89f4b96bd5 100644 --- a/sbt-plugin/src/sbt-test/sbt-plugin/simple/test +++ b/sbt-plugin/src/sbt-test/sbt-plugin/simple/test @@ -2,5 +2,5 @@ > + success/compile # check that a module on which stainless verification passes compiles fine (i.e., binaries are produced) $ exists success/target/scala-2.13/classes/Extern1.class -$ exists success/target/scala-3.2.0/classes/Extern1.class +$ exists success/target/scala-3.3.3/classes/Extern1.class # > failure/checkScalaFailures diff --git a/stainless.conf.default b/stainless.conf.default index 528a2c7caa..af3919214b 100644 --- a/stainless.conf.default +++ b/stainless.conf.default @@ -2,9 +2,9 @@ # options listed by `stainless --help` vc-cache = true -debug = ["verification"] timeout = 30 check-models = true print-ids = false print-types = false batched = true +solvers = "smt-z3,smt-cvc5" \ No newline at end of file