diff --git a/.circleci/README.md b/.circleci/README.md index 7f7b01455..466aaa76a 100644 --- a/.circleci/README.md +++ b/.circleci/README.md @@ -4,25 +4,27 @@ The docker images are build locally on the developer machine: -```!sh +```sh cd .circleci/docker/ -docker build -t ethereum/solidity-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 . -docker push ethereum/solidity-buildpack-deps:ubuntu1904 +docker build -t ethereum/solidity-buildpack-deps:ubuntu1904- -f Dockerfile.ubuntu1904 . +docker push ethereum/solidity-buildpack-deps:ubuntu1904- ``` -which you can find on Dockerhub after the push at: +The current revision is stored in a [circle ci pipeline parameter](https://github.com/CircleCI-Public/api-preview-docs/blob/master/docs/pipeline-parameters.md#pipeline-parameters) called `docker-image-rev`. Please update the value assigned to this parameter at the time of a docker image update. Please verify that the value assigned to the parameter matches the revision part of the docker image tag (`` in the docker build/push snippet shown above). Otherwise, the docker image used by circle ci and the one actually pushed to docker hub will differ. - https://hub.docker.com/r/ethereum/solidity-buildpack-deps +Once the docker image has been built and pushed to Dockerhub, you can find it at: -where the image tag reflects the target OS to build Solidity and run its test on. + https://hub.docker.com/r/ethereum/solidity-buildpack-deps:ubuntu1904- + +where the image tag reflects the target OS and revision to build Solidity and run its tests on. ### Testing docker images locally -```!sh +```sh cd solidity # Mounts your local solidity directory in docker container for testing -docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904 /bin/bash +docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904- /bin/bash cd /src/solidity -``` \ No newline at end of file +``` diff --git a/.circleci/config.yml b/.circleci/config.yml index 3e9cebb06..2139fa94d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,11 @@ # - t: test # - ubu: ubuntu # - ems: Emscripten -version: 2 +version: 2.1 +parameters: + docker-image-rev: + type: string + default: "3" defaults: @@ -106,7 +110,7 @@ defaults: - test_ubuntu1904_clang: &test_ubuntu1904_clang docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.docker-image-rev >> steps: - checkout - attach_workspace: @@ -117,7 +121,7 @@ defaults: - test_ubuntu1904: &test_ubuntu1904 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904 + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> steps: - checkout - attach_workspace: @@ -287,10 +291,11 @@ jobs: b_ubu_clang: &build_ubuntu1904_clang docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.docker-image-rev >> environment: CC: clang CXX: clang++ + CMAKE_OPTIONS: -DLLL=ON steps: - checkout - run: *run_build @@ -299,7 +304,9 @@ jobs: b_ubu: &build_ubuntu1904 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904 + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> + environment: + CMAKE_OPTIONS: -DLLL=ON steps: - checkout - run: *run_build @@ -310,12 +317,13 @@ jobs: <<: *build_ubuntu1904 environment: FORCE_RELEASE: ON + CMAKE_OPTIONS: -DLLL=ON b_ubu18: &build_ubuntu1804 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1804 + - image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.docker-image-rev >> environment: - CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 + CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 -DLLL=ON CMAKE_BUILD_TYPE: RelWithDebugInfo steps: - checkout @@ -360,7 +368,7 @@ jobs: <<: *build_ubuntu1904 environment: CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF -DLLL=ON steps: - checkout - run: *run_build @@ -400,6 +408,7 @@ jobs: - image: archlinux/base environment: TERM: xterm + CMAKE_OPTIONS: -DLLL=ON steps: - run: name: Install build dependencies @@ -416,6 +425,7 @@ jobs: environment: TERM: xterm CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DLLL=ON steps: - checkout - restore_cache: @@ -472,7 +482,7 @@ jobs: b_ems: docker: - - image: trzeci/emscripten:sdk-tag-1.38.22-64bit + - image: trzeci/emscripten:sdk-tag-1.39.3-64bit environment: TERM: xterm steps: @@ -519,7 +529,7 @@ jobs: b_docs: docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904 + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> steps: - checkout - run: *setup_prerelease_commit_hash @@ -544,7 +554,7 @@ jobs: t_ubu_cli: &t_ubu_cli docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904 + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> environment: TERM: xterm steps: @@ -597,7 +607,22 @@ jobs: npm --version test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) - t_ems_external_gnosis: + t_ems_compile_ext_gnosis: + docker: + - image: circleci/node:10 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: /tmp/workspace + - run: + name: External GnosisSafe compilation + command: | + export COMPILE_ONLY=1 + test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js + + t_ems_test_ext_gnosis: docker: - image: circleci/node:10 environment: @@ -613,7 +638,22 @@ jobs: - run: *gitter_notify_failure - run: *gitter_notify_success - t_ems_external_zeppelin: + t_ems_compile_ext_zeppelin: + docker: + - image: circleci/node:10 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: /tmp/workspace + - run: + name: External Zeppelin compilation + command: | + export COMPILE_ONLY=1 + test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js + + t_ems_test_ext_zeppelin: docker: - image: circleci/node:10 environment: @@ -629,7 +669,26 @@ jobs: - run: *gitter_notify_failure - run: *gitter_notify_success - t_ems_external_colony: + t_ems_compile_ext_colony: + docker: + - image: circleci/node:10 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: /tmp/workspace + - run: + name: Install test dependencies + command: | + sudo apt-get -qy install lsof + - run: + name: External ColonyNetworks compilation + command: | + export COMPILE_ONLY=1 + test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js + + t_ems_test_ext_colony: docker: - image: circleci/node:10 environment: @@ -693,6 +752,9 @@ workflows: # Emscripten build and selected tests - b_ems: *workflow_trigger_on_tags - t_ems_solcjs: *workflow_emscripten + - t_ems_compile_ext_colony: *workflow_emscripten + - t_ems_compile_ext_gnosis: *workflow_emscripten + - t_ems_compile_ext_zeppelin: *workflow_emscripten nightly: @@ -706,12 +768,6 @@ workflows: - develop_060 jobs: - # Emscripten builds and external tests - - b_ems: *workflow_trigger_on_tags - - t_ems_external_zeppelin: *workflow_emscripten - - t_ems_external_gnosis: *workflow_emscripten - - t_ems_external_colony: *workflow_emscripten - # OSSFUZZ builds and (regression) tests - b_ubu_ossfuzz: *workflow_trigger_on_tags - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz diff --git a/.circleci/docker/Dockerfile.clang.ubuntu1904 b/.circleci/docker/Dockerfile.clang.ubuntu1904 index 4fb9b1132..0306dcf68 100644 --- a/.circleci/docker/Dockerfile.clang.ubuntu1904 +++ b/.circleci/docker/Dockerfile.clang.ubuntu1904 @@ -62,7 +62,7 @@ RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git rm -rf /usr/src/boost # Z3 -RUN git clone --depth 1 -b z3-4.8.6 https://github.com/Z3Prover/z3.git \ +RUN git clone --depth 1 -b z3-4.8.7 https://github.com/Z3Prover/z3.git \ /usr/src/z3; \ cd /usr/src/z3; \ python scripts/mk_make.py --prefix=/usr ; \ @@ -89,43 +89,24 @@ RUN set -ex; \ ninja install/strip; \ rm -rf /usr/src/libprotobuf-mutator -# ETHASH -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ - cd ethash; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/ethash - -# INTX -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ - cd intx; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/intx; - # EVMONE RUN set -ex; \ cd /usr/src; \ - git clone --branch="v0.1.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ cd evmone; \ mkdir build; \ cd build; \ + # isoltest links against the evmone shared library cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ ninja; \ ninja install/strip; \ + # abiv2_proto_ossfuzz links against the evmone standalone static library + cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ rm -rf /usr/src/evmone FROM base COPY --from=libraries /usr/lib /usr/lib COPY --from=libraries /usr/bin /usr/bin -COPY --from=libraries /usr/include /usr/include \ No newline at end of file +COPY --from=libraries /usr/include /usr/include diff --git a/.circleci/docker/Dockerfile.ubuntu1804 b/.circleci/docker/Dockerfile.ubuntu1804 index 0242661e7..fbf664a49 100644 --- a/.circleci/docker/Dockerfile.ubuntu1804 +++ b/.circleci/docker/Dockerfile.ubuntu1804 @@ -74,34 +74,10 @@ RUN set -ex; \ ar r /usr/lib/libFuzzingEngine.a *.o; \ rm -rf /var/lib/libfuzzer -# ETHASH -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ - cd ethash; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/ethash - -# INTX -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ - cd intx; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/intx; - # EVMONE -ARG EVMONE_HASH="f10d12c190f55a9d373e78b2dc0074d35d752c02cb536bb6fe754fb3719dd69e" +ARG EVMONE_HASH="fa4f40daf7cf9ccbcca6c78345977e084ea2136a8eae661e4d19471be852b15b" ARG EVMONE_MAJOR="0" -ARG EVMONE_MINOR="1" +ARG EVMONE_MINOR="3" ARG EVMONE_MICRO="0" RUN set -ex; \ EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \ diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 index 504e13d1f..2c211d956 100644 --- a/.circleci/docker/Dockerfile.ubuntu1904 +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -74,37 +74,14 @@ RUN set -ex; \ ar r /usr/lib/libFuzzingEngine.a *.o; \ rm -rf /var/lib/libfuzzer -# ETHASH -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ - cd ethash; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/ethash - -# INTX -RUN set -ex; \ - cd /usr/src; \ - git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ - cd intx; \ - mkdir build; \ - cd build; \ - cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/intx; - # EVMONE RUN set -ex; \ cd /usr/src; \ - git clone --branch="v0.1.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + git clone --branch="v0.3.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ cd evmone; \ mkdir build; \ cd build; \ + # isoltest links against the evmone shared library cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ ninja; \ ninja install/strip; \ diff --git a/.circleci/osx_install_dependencies.sh b/.circleci/osx_install_dependencies.sh index 22e14af7e..59e7c2234 100755 --- a/.circleci/osx_install_dependencies.sh +++ b/.circleci/osx_install_dependencies.sh @@ -43,17 +43,17 @@ then ./scripts/install_obsolete_jsoncpp_1_7_4.sh # z3 - wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.6/z3-4.8.6-x64-osx-10.14.6.zip - unzip z3-4.8.6-x64-osx-10.14.6.zip - rm -f z3-4.8.6-x64-osx-10.14.6.zip - cp z3-4.8.6-x64-osx-10.14.6/bin/libz3.a /usr/local/lib - cp z3-4.8.6-x64-osx-10.14.6/bin/z3 /usr/local/bin - cp z3-4.8.6-x64-osx-10.14.6/include/* /usr/local/include - rm -rf z3-4.8.6-x64-osx-10.14.6 + wget https://github.com/Z3Prover/z3/releases/download/z3-4.8.7/z3-4.8.7-x64-osx-10.14.6.zip + unzip z3-4.8.7-x64-osx-10.14.6.zip + rm -f z3-4.8.7-x64-osx-10.14.6.zip + cp z3-4.8.7-x64-osx-10.14.6/bin/libz3.a /usr/local/lib + cp z3-4.8.7-x64-osx-10.14.6/bin/z3 /usr/local/bin + cp z3-4.8.7-x64-osx-10.14.6/include/* /usr/local/include + rm -rf z3-4.8.7-x64-osx-10.14.6 # evmone - wget https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-darwin-x86_64.tar.gz - tar xzpf evmone-0.1.0-darwin-x86_64.tar.gz -C /usr/local - rm -f evmone-0.1.0-darwin-x86_64.tar.gz + wget https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz + tar xzpf evmone-0.3.0-darwin-x86_64.tar.gz -C /usr/local + rm -f evmone-0.3.0-darwin-x86_64.tar.gz fi diff --git a/.circleci/soltest_all.sh b/.circleci/soltest_all.sh index 7b4564ee7..74b3f1197 100755 --- a/.circleci/soltest_all.sh +++ b/.circleci/soltest_all.sh @@ -29,9 +29,9 @@ set -e REPODIR="$(realpath $(dirname $0)/..)" for OPTIMIZE in 0 1; do - for EVM in homestead byzantium constantinople petersburg; do + for EVM in homestead byzantium constantinople petersburg istanbul; do EVM=$EVM OPTIMIZE=$OPTIMIZE ${REPODIR}/.circleci/soltest.sh done done -EVM=constantinople OPTIMIZE=1 ABI_ENCODER_V2=1 ${REPODIR}/.circleci/soltest.sh \ No newline at end of file +EVM=istanbul OPTIMIZE=1 ABI_ENCODER_V2=1 ${REPODIR}/.circleci/soltest.sh diff --git a/.travis.yml b/.travis.yml index 5754fa0a9..38d59fa93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -110,7 +110,7 @@ matrix: before_install: - nvm install 8 - nvm use 8 - - docker pull trzeci/emscripten:sdk-tag-1.38.22-64bit + - docker pull trzeci/emscripten:sdk-tag-1.39.3-64bit env: - SOLC_EMSCRIPTEN=On - SOLC_INSTALL_DEPS_TRAVIS=Off @@ -127,7 +127,7 @@ matrix: # # This key here has no significant on anything, apart from caching. Please keep # it in sync with the version above. - - EMSCRIPTEN_VERSION_KEY="1.38.22" + - EMSCRIPTEN_VERSION_KEY="1.39.3" # OS X Mavericks (10.9) # https://en.wikipedia.org/wiki/OS_X_Mavericks diff --git a/CMakeLists.txt b/CMakeLists.txt index 07f26e291..49b2d19c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.13") +set(PROJECT_VERSION "0.5.14") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) include(TestBigEndian) diff --git a/Changelog.md b/Changelog.md index 3ffe69997..0247137b3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,31 @@ +### 0.5.14 (2019-12-09) + +Language Features: + * Allow to obtain the selector of public or external library functions via a member ``.selector``. + * Inline Assembly: Support constants that reference other constants. + * Parser: Allow splitting hexadecimal and regular string literals into multiple parts. + + +Compiler Features: + * Commandline Interface: Allow translation from yul / strict assembly to EWasm using ``solc --yul --yul-dialect evm --machine eWasm`` + * Set the default EVM version to "Istanbul". + * SMTChecker: Add support to constructors including constructor inheritance. + * Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable. + * Yul Optimizer: Perform loop-invariant code motion. + + +Bugfixes: + * SMTChecker: Fix internal error when using ``abi.decode``. + * SMTChecker: Fix internal error when using arrays or mappings of functions. + * SMTChecker: Fix internal error in array of structs type. + * Version Checker: ``^0`` should match ``0.5.0``, but no prerelease. + * Yul: Consider infinite loops and recursion to be not removable. + + +Build System: + * Update to emscripten version 1.39.3. + + ### 0.5.13 (2019-11-14) Language Features: @@ -22,6 +50,7 @@ Bugfixes: * SMTChecker: Fix internal error when implicitly converting string literals to fixed bytes. * Type Checker: Disallow constructor of the same class to be used as modifier. * Type Checker: Treat magic variables as unknown identifiers in inline assembly. + * Code Generator: Fix internal error when trying to convert ``super`` to a different type diff --git a/README.md b/README.md index a8c04da2a..e7e5bdb7c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Instructions about how to build and install the Solidity compiler can be found i A "Hello World" program in Solidity is of even less use than in other languages, but still: -``` +```solidity pragma solidity ^0.5.0; contract HelloWorld { @@ -44,10 +44,10 @@ contract HelloWorld { To get started with Solidity, you can use [Remix](https://remix.ethereum.org/), which is an browser-based IDE. Here are some example contracts: -1. [Voting](https://solidity.readthedocs.io/en/v0.4.24/solidity-by-example.html#voting) -2. [Blind Auction](https://solidity.readthedocs.io/en/v0.4.24/solidity-by-example.html#blind-auction) -3. [Safe remote purchase](https://solidity.readthedocs.io/en/v0.4.24/solidity-by-example.html#safe-remote-purchase) -4. [Micropayment Channel](https://solidity.readthedocs.io/en/v0.4.24/solidity-by-example.html#micropayment-channel) +1. [Voting](https://solidity.readthedocs.io/en/latest/solidity-by-example.html#voting) +2. [Blind Auction](https://solidity.readthedocs.io/en/latest/solidity-by-example.html#blind-auction) +3. [Safe remote purchase](https://solidity.readthedocs.io/en/latest/solidity-by-example.html#safe-remote-purchase) +4. [Micropayment Channel](https://solidity.readthedocs.io/en/latest/solidity-by-example.html#micropayment-channel) ## Documentation diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index eb55feef1..afd145118 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -86,7 +86,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA elseif(EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --memory-init-file 0") # Leave only exported symbols as public and aggressively remove others - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections -fvisibility=hidden") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -fvisibility=hidden") # Optimisation level set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") # Re-enable exception catching (optimisations above -O1 disable it) @@ -110,9 +110,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s STRICT=1") # Export the Emscripten-generated auxiliary methods which are needed by solc-js. # Which methods of libsolc itself are exported is specified in libsolc/CMakeLists.txt. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap','addFunction','removeFunction','Pointer_stringify','lengthBytesUTF8','_malloc','stringToUTF8','setValue']") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap','addFunction','removeFunction','UTF8ToString','lengthBytesUTF8','_malloc','stringToUTF8','setValue']") # Do not build as a WebAssembly target - we need an asm.js output. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=0") + + # Disable warnings about not being pure asm.js due to memory growth. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-almost-asm") endif() endif() diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index 3e72a8be2..1377041b9 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -37,9 +37,9 @@ endif() ExternalProject_Add(jsoncpp-project PREFIX "${prefix}" DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads" - DOWNLOAD_NAME jsoncpp-1.8.4.tar.gz - URL https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz - URL_HASH SHA256=c49deac9e0933bcb7044f08516861a2d560988540b23de2ac1ad443b219afdb6 + DOWNLOAD_NAME jsoncpp-1.9.2.tar.gz + URL https://github.com/open-source-parsers/jsoncpp/archive/1.9.2.tar.gz + URL_HASH SHA256=77a402fb577b2e0e5d0bdc1cf9c65278915cdb25171e3452c68b6da8a561f8f0 CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 402997af1..c412be830 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -758,6 +758,10 @@ "bugs": [], "released": "2019-11-14" }, + "0.5.14": { + "bugs": [], + "released": "2019-12-09" + }, "0.5.2": { "bugs": [ "SignedArrayStorageCopy", diff --git a/docs/contracts/libraries.rst b/docs/contracts/libraries.rst index bfeebdb77..2e77af746 100644 --- a/docs/contracts/libraries.rst +++ b/docs/contracts/libraries.rst @@ -208,6 +208,48 @@ Restrictions for libraries in comparison to contracts: (These might be lifted at a later point.) +.. _library-selectors: + +Function Signatures and Selectors in Libraries +============================================== + +While external calls to public or external library functions are possible, the calling convention for such calls +is considered to be internal to Solidity and not the same as specified for the regular :ref:`contract ABI`. +External library functions support more argument types than external contract functions, for example recursive structs +and storage pointers. For that reason, the function signatures used to compute the 4-byte selector are computed +following an internal naming schema and arguments of types not supported in the contract ABI use an internal encoding. + +The following identifiers are used for the types in the signatures: + + - Value types, non-storage ``string`` and non-storage ``bytes`` use the same identifiers as in the contract ABI. + - Non-storage array types follow the same convention as in the contract ABI, i.e. ``[]`` for dynamic arrays and + ``[M]`` for fixed-size arrays of ``M`` elements. + - Non-storage structs are referred to by their fully qualified name, i.e. ``C.S`` for ``contract C { struct S { ... } }``. + - Storage pointer types use the type identifier of their corresponding non-storage type, but append a single space + followed by ``storage`` to it. + +The argument encoding is the same as for the regular contract ABI, except for storage pointers, which are encoded as a +``uint256`` value referring to the storage slot to which they point. + +Similarly to the contract ABI, the selector consists of the first four bytes of the Keccak256-hash of the signature. +Its value can be obtained from Solidity using the ``.selector`` member as follows: + +:: + + pragma solidity >0.5.13 <0.7.0; + + library L { + function f(uint256) external {} + } + + contract C { + function g() public pure returns (bytes4) { + return L.f.selector; + } + } + + + .. _call-protection: Call Protection For Libraries diff --git a/docs/contributing.rst b/docs/contributing.rst index a78d2e5d7..1b50ccaaa 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -86,7 +86,7 @@ but for quicker feedback, you might want to run specific tests. Solidity includes different types of tests, most of them bundled into the `Boost C++ Test Framework `_ application ``soltest``. -Running ``build/test/soltest` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes. +Running ``build/test/soltest`` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes. Some tests require the ``evmone`` library, others require ``libz3``. @@ -94,7 +94,7 @@ The test system will automatically try to discover the location of the ``evmone` starting from the current directory. The required file is called ``libevmone.so`` on Linux systems, ``evmone.dll`` on Windows systems and ``libevmone.dylib`` on MacOS. If it is not found, the relevant tests are skipped. To run all tests, download the library from -`Github `_ +`Github `_ and either place it in the project root path or inside the ``deps`` folder. If you do not have libz3 installed on your system, you should disable the SMT tests: @@ -113,7 +113,7 @@ See especially: If you are running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-smt``. To run a subset of tests, you can use filters: -``./scripts/soltest.sh -t TestSuite/TestName, +``./scripts/soltest.sh -t TestSuite/TestName``, where ``TestName`` can be a wildcard ``*``. For example, here is an example test you might run; diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 75056dbe6..00b341e82 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -204,7 +204,7 @@ The evaluation order of expressions is not specified (more formally, the order in which the children of one node in the expression tree are evaluated is not specified, but they are of course evaluated before the node itself). It is only guaranteed that statements are executed in order and short-circuiting for -boolean expressions is done. See :ref:`order` for more information. +boolean expressions is done. .. index:: ! assignment diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index c4e684ce1..9ba3c4c85 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -4,6 +4,25 @@ Safe Remote Purchase ******************** +Purchasing goods remotely currently requires multiple parties that need to trust each other. +The simplest configuration involves a seller and a buyer. The buyer would like to receive +an item from the seller and the seller would like to get money (or an equivalent) +in return. The problematic part is the shipment here: There is no way to determine for +sure that the item arrived at the buyer. + +There are multiple ways to solve this problem, but all fall short in one or the other way. +In the following example, both parties have to put twice the value of the item into the +contract as escrow. As soon as this happened, the money will stay locked inside +the contract until the buyer confirms that they received the item. After that, +the buyer is returned the value (half of their deposit) and the seller gets three +times the value (their deposit plus the value). The idea behind +this is that both parties have an incentive to resolve the situation or otherwise +their money is locked forever. + +This contract of course does not solve the problem, but gives an overview of how +you can use state machine-like constructs inside a contract. + + :: pragma solidity >=0.4.22 <0.7.0; @@ -12,7 +31,7 @@ Safe Remote Purchase uint public value; address payable public seller; address payable public buyer; - enum State { Created, Locked, Inactive } + enum State { Created, Locked, Release, Inactive } // The state variable has a default value of the first member, `State.created` State public state; @@ -57,6 +76,7 @@ Safe Remote Purchase event Aborted(); event PurchaseConfirmed(); event ItemReceived(); + event SellerRefunded(); /// Abort the purchase and reclaim the ether. /// Can only be called by the seller before @@ -68,6 +88,10 @@ Safe Remote Purchase { emit Aborted(); state = State.Inactive; + // We use transfer here directly. It is + // reentrancy-safe, because it is the + // last call in this function and we + // already changed the state. seller.transfer(address(this).balance); } @@ -97,12 +121,24 @@ Safe Remote Purchase // It is important to change the state first because // otherwise, the contracts called using `send` below // can call in again here. - state = State.Inactive; - - // NOTE: This actually allows both the buyer and the seller to - // block the refund - the withdraw pattern should be used. + state = State.Release; buyer.transfer(value); - seller.transfer(address(this).balance); + } + + /// This function refunds the seller, i.e. + /// pays back the locked funds of the seller. + function refundSeller() + public + onlySeller + inState(State.Release) + { + emit SellerRefunded(); + // It is important to change the state first because + // otherwise, the contracts called using `send` below + // can call in again here. + state = State.Inactive; + + seller.transfer(3 * value); } } \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index d1ac873de..15dea172d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -66,6 +66,7 @@ They have varying degrees of completeness and up-to-dateness. The English version stands as a reference. * `French `_ (in progress) +* `Italian `_ (in progress) * `Japanese `_ * `Korean `_ (in progress) * `Russian `_ (rather outdated) diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 053160701..426bdbe37 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -459,7 +459,7 @@ a non-rational number). String Literals and Types ------------------------- -String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``), and they can also be split into multiple consecutive parts (``"foo" "bar"`` is equivalent to ``"foobar"``) which can be helpful when dealing with long strings. They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type. @@ -498,7 +498,7 @@ terminate the string literal. Newline only terminates the string literal if it i Hexadecimal Literals -------------------- -Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. +Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``), and they can also be split into multiple consecutive parts (``hex"00112233" hex"44556677"`` is equivalent to ``hex"0011223344556677"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. Hexadecimal literals behave like :ref:`string literals ` and have the same convertibility restrictions. diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 8b8c06735..6c625e997 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -262,7 +262,15 @@ Contract Related the current contract, explicitly convertible to :ref:`address` ``selfdestruct(address payable recipient)``: - destroy the current contract, sending its funds to the given :ref:`address` + Destroy the current contract, sending its funds to the given :ref:`address` + and end execution. + Note that ``selfdestruct`` has some peculiarities inherited from the EVM: + + - the receiving contract's receive function is not executed. + - the contract is only really destroyed at the end of the transaction and ``revert`` s might "undo" the destruction. + + + Furthermore, all functions of the current contract are callable directly including the current function. diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index db09b2a09..fe2d316ba 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -57,7 +57,7 @@ Either add ``--libraries "file.sol:Math:0x12345678901234567890123456789012345678 If ``solc`` is called with the option ``--link``, all input files are interpreted to be unlinked binaries (hex-encoded) in the ``__$53aea86b7d70b31448b230b20ae141a537$__``-format given above and are linked in-place (if the input is read from stdin, it is written to stdout). All options except ``--libraries`` are ignored (including ``-o``) in this case. -If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output. This is the recommended interface for more complex and especially automated uses. +If ``solc`` is called with the option ``--standard-json``, it will expect a JSON input (as explained below) on the standard input, and return a JSON output on the standard output. This is the recommended interface for more complex and especially automated uses. The process will always terminate in a "success" state and report any errors via the JSON output. .. note:: The library placeholder used to be the fully qualified name of the library itself @@ -120,9 +120,9 @@ at each version. Backward compatibility is not guaranteed between each version. - ``constantinople`` - Opcodes ``create2`, ``extcodehash``, ``shl``, ``shr`` and ``sar`` are available in assembly. - Shifting operators use shifting opcodes and thus need less gas. -- ``petersburg`` (**default**) +- ``petersburg`` - The compiler behaves the same way as with constantinople. -- ``istanbul`` +- ``istanbul`` (**default**) - Opcodes ``chainid`` and ``selfbalance`` are available in assembly. - ``berlin`` (**experimental**) @@ -140,6 +140,8 @@ The fields are generally subject to change, some are optional (as noted), but we try to only make backwards compatible changes. The compiler API expects a JSON formatted input and outputs the compilation result in a JSON formatted output. +The standard error output is not used and the process will always terminate in a "success" state, even +if there were errors. Errors are always reported as part of the JSON output. The following subsections describe the format through an example. Comments are of course not permitted and used here only for explanatory purposes. diff --git a/docs/yul.rst b/docs/yul.rst index ae8eb1f9e..304a4b7e5 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -168,7 +168,7 @@ The ``continue`` and ``break`` statements can only be used inside loop bodies and have to be in the same function as the loop (or both have to be at the top level). The condition part of the for-loop has to evaluate to exactly one value. -Functions cannot be defined inside for loop init blocks. +Functions cannot be defined anywhere inside for loop init blocks. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. @@ -182,11 +182,17 @@ introduce new identifiers into these scopes. Identifiers are visible in the block they are defined in (including all sub-nodes and sub-blocks). -As an exception, identifiers defined in the "init" part of the for-loop + +As an exception, identifiers defined directly in the "init" part of the for-loop (the first block) are visible in all other parts of the for-loop (but not outside of the loop). Identifiers declared in the other parts of the for loop respect the regular syntactical scoping rules. + +This means a for-loop of the form ``for { I... } C { P... } { B... }`` is equivalent +to ``{ I... for {} C { P... } { B... } }``. + + The parameters and return parameters of functions are visible in the function body and their names cannot overlap. diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 1b0e97204..b151ba604 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -23,32 +23,59 @@ #include #include #include +#include #include using namespace std; using namespace dev; +namespace +{ + +static char const* upperHexChars = "0123456789ABCDEF"; +static char const* lowerHexChars = "0123456789abcdef"; + +} + +string dev::toHex(uint8_t _data, HexCase _case) +{ + assertThrow(_case != HexCase::Mixed, BadHexCase, "Mixed case can only be used for byte arrays."); + + char const* chars = _case == HexCase::Upper ? upperHexChars : lowerHexChars; + + return std::string{ + chars[(unsigned(_data) / 16) & 0xf], + chars[unsigned(_data) & 0xf] + }; +} + string dev::toHex(bytes const& _data, HexPrefix _prefix, HexCase _case) { - std::ostringstream ret; - if (_prefix == HexPrefix::Add) - ret << "0x"; + std::string ret(_data.size() * 2 + (_prefix == HexPrefix::Add ? 2 : 0), 0); + size_t i = 0; + if (_prefix == HexPrefix::Add) + { + ret[i++] = '0'; + ret[i++] = 'x'; + } + + // Mixed case will be handled inside the loop. + char const* chars = _case == HexCase::Upper ? upperHexChars : lowerHexChars; int rix = _data.size() - 1; for (uint8_t c: _data) { // switch hex case every four hexchars - auto hexcase = std::nouppercase; - if (_case == HexCase::Upper) - hexcase = std::uppercase; - else if (_case == HexCase::Mixed) - hexcase = (rix-- & 2) == 0 ? std::nouppercase : std::uppercase; + if (_case == HexCase::Mixed) + chars = (rix-- & 2) == 0 ? lowerHexChars : upperHexChars; - ret << std::hex << hexcase << std::setfill('0') << std::setw(2) << size_t(c); + ret[i++] = chars[(unsigned(c) / 16) & 0xf]; + ret[i++] = chars[unsigned(c) & 0xf]; } + assertThrow(i == ret.size(), Exception, ""); - return ret.str(); + return ret; } int dev::fromHex(char _i, WhenError _throw) @@ -60,7 +87,7 @@ int dev::fromHex(char _i, WhenError _throw) if (_i >= 'A' && _i <= 'F') return _i - 'A' + 10; if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter() << errinfo_invalidSymbol(_i)); + assertThrow(false, BadHexCharacter, to_string(_i)); else return -1; } @@ -73,22 +100,18 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw) if (_s.size() % 2) { - int h = fromHex(_s[s++], WhenError::DontThrow); + int h = fromHex(_s[s++], _throw); if (h != -1) ret.push_back(h); - else if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter()); else return bytes(); } for (unsigned i = s; i < _s.size(); i += 2) { - int h = fromHex(_s[i], WhenError::DontThrow); - int l = fromHex(_s[i + 1], WhenError::DontThrow); + int h = fromHex(_s[i], _throw); + int l = fromHex(_s[i + 1], _throw); if (h != -1 && l != -1) ret.push_back((uint8_t)(h * 16 + l)); - else if (_throw == WhenError::Throw) - BOOST_THROW_EXCEPTION(BadHexCharacter()); else return bytes(); } @@ -155,3 +178,14 @@ bool dev::isValidDecimal(string const& _string) return false; return true; } + +string dev::formatAsStringOrNumber(string const& _value) +{ + assertThrow(_value.length() <= 32, StringTooLong, "String to be formatted longer than 32 bytes."); + + for (auto const& c: _value) + if (c <= 0x1f || c >= 0x7f || c == '"') + return "0x" + h256(_value, h256::AlignLeft).hex(); + + return "\"" + _value + "\""; +} diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 017a632dc..9db1e3cf5 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -130,9 +130,12 @@ enum class HexCase Mixed = 2, }; -/// Convert a series of bytes to the corresponding string of hex duplets. -/// @param _w specifies the width of the first of the elements. Defaults to two - enough to represent a byte. -/// @example toHex("A\x69") == "4169" +/// Convert a single byte to a string of hex characters (of length two), +/// optionally with uppercase hex letters. +std::string toHex(uint8_t _data, HexCase _case = HexCase::Lower); + +/// Convert a series of bytes to the corresponding string of hex duplets, +/// optionally with "0x" prefix and with uppercase hex letters. std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower); /// Converts a (printable) ASCII hex character into the corresponding integer value. @@ -207,10 +210,6 @@ inline bytes toCompactBigEndian(T _val, unsigned _min = 0) toBigEndian(_val, ret); return ret; } -inline bytes toCompactBigEndian(uint8_t _val, unsigned _min = 0) -{ - return (_min || _val) ? bytes{ _val } : bytes{}; -} /// Convenience function for conversion of a u256 to hex inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) @@ -219,13 +218,18 @@ inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd) return (prefix == HexPrefix::Add) ? "0x" + str : str; } +inline std::string toCompactHexWithPrefix(u256 const& _value) +{ + return toHex(toCompactBigEndian(_value, 1), HexPrefix::Add); +} + /// Returns decimal representation for small numbers and hex for large numbers. inline std::string formatNumber(bigint const& _value) { if (_value < 0) return "-" + formatNumber(-_value); if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), HexPrefix::Add); + return toHex(toCompactBigEndian(_value, 1), HexPrefix::Add); else return _value.str(); } @@ -233,17 +237,11 @@ inline std::string formatNumber(bigint const& _value) inline std::string formatNumber(u256 const& _value) { if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), HexPrefix::Add); + return toCompactHexWithPrefix(_value); else return _value.str(); } -inline std::string toCompactHexWithPrefix(u256 val) -{ - std::ostringstream ret; - ret << std::hex << val; - return "0x" + ret.str(); -} // Algorithms for string and string-like collections. @@ -292,7 +290,6 @@ void iterateReplacing(std::vector& _vector, F const& _f) _vector = std::move(modifiedVector); } - namespace detail { template @@ -357,6 +354,11 @@ std::string getChecksummedAddress(std::string const& _addr); bool isValidHex(std::string const& _string); bool isValidDecimal(std::string const& _string); +/// @returns a quoted string if all characters are printable ASCII chars, +/// or its hex representation otherwise. +/// _value cannot be longer than 32 bytes. +std::string formatAsStringOrNumber(std::string const& _value); + template bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare) { diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 943379d7a..851be4ea1 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -46,11 +46,12 @@ private: DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(BadHexCharacter); +DEV_SIMPLE_EXCEPTION(BadHexCase); DEV_SIMPLE_EXCEPTION(FileError); DEV_SIMPLE_EXCEPTION(DataTooLong); +DEV_SIMPLE_EXCEPTION(StringTooLong); // error information to be added to exceptions -using errinfo_invalidSymbol = boost::error_info; using errinfo_comment = boost::error_info; } diff --git a/libdevcore/IpfsHash.cpp b/libdevcore/IpfsHash.cpp index add459a2c..22ad7100c 100644 --- a/libdevcore/IpfsHash.cpp +++ b/libdevcore/IpfsHash.cpp @@ -17,6 +17,7 @@ #include +#include #include #include #include @@ -55,11 +56,7 @@ string base58Encode(bytes const& _data) bytes dev::ipfsHash(string _data) { - if (_data.length() >= 1024 * 256) - BOOST_THROW_EXCEPTION( - DataTooLong() << - errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.") - ); + assertThrow(_data.length() < 1024 * 256, DataTooLong, "IPFS hash for large (chunked) files not yet implemented."); bytes lengthAsVarint = varintEncoding(_data.size()); diff --git a/libdevcore/JSON.cpp b/libdevcore/JSON.cpp index 0b08616da..5d1ea831d 100644 --- a/libdevcore/JSON.cpp +++ b/libdevcore/JSON.cpp @@ -32,8 +32,8 @@ using namespace std; static_assert( - (JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 8) && (JSONCPP_VERSION_PATCH == 4), - "Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.8.4." + (JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 9) && (JSONCPP_VERSION_PATCH == 2), + "Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.9.2." ); namespace dev @@ -111,16 +111,4 @@ bool jsonParseStrict(string const& _input, Json::Value& _json, string* _errs /* return parse(readerBuilder, _input, _json, _errs); } -bool jsonParse(string const& _input, Json::Value& _json, string *_errs /* = nullptr */) -{ - static Json::CharReaderBuilder readerBuilder; - return parse(readerBuilder, _input, _json, _errs); -} - -bool jsonParseFile(string const& _fileName, Json::Value& _json, string *_errs /* = nullptr */) -{ - return jsonParse(readFileAsString(_fileName), _json, _errs); -} - - } // namespace dev diff --git a/libdevcore/JSON.h b/libdevcore/JSON.h index ecb934673..2390d55f2 100644 --- a/libdevcore/JSON.h +++ b/libdevcore/JSON.h @@ -41,18 +41,4 @@ std::string jsonCompactPrint(Json::Value const& _input); /// \return \c true if the document was successfully parsed, \c false if an error occurred. bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* _errs = nullptr); -/// Parse a JSON string (@a _input) and writes resulting JSON object to (@a _json) -/// \param _input JSON input string -/// \param _json [out] resulting JSON object -/// \param _errs [out] Formatted error messages -/// \return \c true if the document was successfully parsed, \c false if an error occurred. -bool jsonParse(std::string const& _input, Json::Value& _json, std::string* _errs = nullptr); - -/// Parse a JSON string (@a _input) and writes resulting JSON object to (@a _json) -/// \param _input file containing JSON input -/// \param _json [out] resulting JSON object -/// \param _errs [out] Formatted error messages -/// \return \c true if the document was successfully parsed, \c false if an error occurred. -bool jsonParseFile(std::string const& _fileName, Json::Value& _json, std::string* _errs = nullptr); - } diff --git a/libdevcore/Visitor.h b/libdevcore/Visitor.h index 4030c928e..3d79e03e8 100644 --- a/libdevcore/Visitor.h +++ b/libdevcore/Visitor.h @@ -20,109 +20,41 @@ #pragma once -#include -#include - namespace dev { -/// Generic visitor used as follows: -/// boost::apply_visitor(GenericVisitor( -/// [](Class1& _c) { _c.f(); }, -/// [](Class2& _c) { _c.g(); } -/// ), variant); -/// This one does not have a fallback and will fail at -/// compile-time if you do not specify all variants. +/** + * Generic visitor used as follows: + * std::visit(GenericVisitor{ + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + * This one does not have a fallback and will fail at + * compile-time if you do not specify all variants. + * + * Fallback with no return (it will not fail if you do not specify all variants): + * std::visit(GenericVisitor{ + * VisitorFallback<>{}, + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + * + * Fallback with return type R (the fallback returns `R{}`: + * std::visit(GenericVisitor{ + * VisitorFallback{}, + * [](Class1& _c) { _c.f(); }, + * [](Class2& _c) { _c.g(); } + * }, variant); + */ -template -struct GenericVisitor{}; +template struct VisitorFallback; -template -struct GenericVisitor: public GenericVisitor -{ - using GenericVisitor::operator (); - explicit GenericVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} +template +struct VisitorFallback { template R operator()(T&&) const { return {}; } }; - void operator()(Visitable& _v) const { m_visitor(_v); } - - std::function m_visitor; -}; -template <> -struct GenericVisitor<>: public boost::static_visitor<> { - void operator()() const {} -}; - -/// Generic visitor with fallback: -/// boost::apply_visitor(GenericFallbackVisitor( -/// [](Class1& _c) { _c.f(); }, -/// [](Class2& _c) { _c.g(); } -/// ), variant); -/// This one DOES have a fallback and will NOT fail at -/// compile-time if you do not specify all variants. - -template -struct GenericFallbackVisitor{}; - -template -struct GenericFallbackVisitor: public GenericFallbackVisitor -{ - explicit GenericFallbackVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericFallbackVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} - - using GenericFallbackVisitor::operator (); - void operator()(Visitable& _v) const { m_visitor(_v); } - - std::function m_visitor; -}; -template <> -struct GenericFallbackVisitor<>: public boost::static_visitor<> { - template - void operator()(T&) const { } -}; - -/// Generic visitor with fallback that can return a value: -/// boost::apply_visitor(GenericFallbackReturnsVisitor( -/// [](Class1& _c) { return _c.f(); }, -/// [](Class2& _c) { return _c.g(); } -/// ), variant); -/// This one DOES have a fallback and will NOT fail at -/// compile-time if you do not specify all variants. -/// The fallback {}-constructs the return value. - -template -struct GenericFallbackReturnsVisitor{}; - -template -struct GenericFallbackReturnsVisitor: public GenericFallbackReturnsVisitor -{ - explicit GenericFallbackReturnsVisitor( - std::function _visitor, - std::function... _otherVisitors - ): - GenericFallbackReturnsVisitor(std::move(_otherVisitors)...), - m_visitor(std::move(_visitor)) - {} - - using GenericFallbackReturnsVisitor::operator (); - R operator()(Visitable& _v) const { return m_visitor(_v); } - - std::function m_visitor; -}; -template -struct GenericFallbackReturnsVisitor: public boost::static_visitor { - template - R operator()(T&) const { return {}; } -}; +template<> +struct VisitorFallback<> { template void operator()(T&&) const {} }; +template struct GenericVisitor: Visitors... { using Visitors::operator()...; }; +template GenericVisitor(Visitors...) -> GenericVisitor; } diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ae8d001d7..079567193 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -323,7 +323,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data()))); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } } @@ -519,8 +519,11 @@ map Assembly::optimiseInternal( LinkerObject const& Assembly::assemble() const { + // Return the already assembled object, if present. if (!m_assembledObject.bytecode.empty()) return m_assembledObject; + // Otherwise ensure the object is actually clear. + assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references."); size_t subTagSize = 1; for (auto const& sub: m_subs) @@ -638,7 +641,7 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.push_back((uint8_t)Instruction::JUMPDEST); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); } } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 61355530e..53d4b79a0 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -81,7 +81,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const default: break; } - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } int AssemblyItem::arguments() const @@ -212,7 +212,7 @@ string AssemblyItem::toAssemblyText() const assertThrow(false, AssemblyException, "Invalid assembly item."); break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) { @@ -277,7 +277,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " ???"; break; default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); + assertThrow(false, InvalidOpcode, ""); } return _out; } diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index 949d2c75a..c8e2f9210 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -158,11 +158,10 @@ AssemblyItems CSECodeGenerator::generateCode( for (auto id: {p.first, p.second}) if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) { - if (seqNr < _initialSequenceNumber) - // Invalid sequenced operation. - // @todo quick fix for now. Proper fix needs to choose representative with higher - // sequence number during dependency analysis. - BOOST_THROW_EXCEPTION(StackTooDeepException()); + // Invalid sequenced operation. + // @todo quick fix for now. Proper fix needs to choose representative with higher + // sequence number during dependency analysis. + assertThrow(seqNr >= _initialSequenceNumber, StackTooDeepException, ""); sequencedExpressions.insert(make_pair(seqNr, id)); } @@ -222,12 +221,9 @@ void CSECodeGenerator::addDependencies(Id _c) return; // we already computed the dependencies for _c ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); assertThrow(expr.item, OptimizerException, ""); - if (expr.item->type() == UndefinedItem) - BOOST_THROW_EXCEPTION( - // If this exception happens, we need to find a different way to generate the - // compound expression. - ItemNotAvailableException() << errinfo_comment("Undefined item requested but not available.") - ); + // If this exception happens, we need to find a different way to generate the + // compound expression. + assertThrow(expr.item->type() != UndefinedItem, ItemNotAvailableException, "Undefined item requested but not available."); for (Id argument: expr.arguments) { addDependencies(argument); diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 4825bc5cf..36cb2d656 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -96,7 +96,7 @@ bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items) bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const { assertThrow(_data.size() > 0, OptimizerException, "Empty bytecode generated."); - return bigint(GasMeter::dataGas(_data, m_params.isCreation)); + return bigint(GasMeter::dataGas(_data, m_params.isCreation, m_params.evmVersion)); } size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items) @@ -131,7 +131,7 @@ bigint LiteralMethod::gasNeeded() const return combineGas( simpleRunGas({Instruction::PUSH1}), // PUSHX plus data - (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)), + (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)), 0 ); } @@ -142,7 +142,7 @@ bigint CodeCopyMethod::gasNeeded() const // Run gas: we ignore memory increase costs simpleRunGas(copyRoutine()) + GasCosts::copyGas, // Data gas for copy routines: Some bytes are zero, but we ignore them. - bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas), + bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), // Data gas for data itself dataGas(toBigEndian(m_value)) ); @@ -322,7 +322,7 @@ bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const return combineGas( simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)), // Data gas for routine: Some bytes are zero, but we ignore them. - bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas), + bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), 0 ); } diff --git a/libevmasm/Exceptions.h b/libevmasm/Exceptions.h index 06b0ac788..d6557d399 100644 --- a/libevmasm/Exceptions.h +++ b/libevmasm/Exceptions.h @@ -33,5 +33,8 @@ struct OptimizerException: virtual AssemblyException {}; struct StackTooDeepException: virtual OptimizerException {}; struct ItemNotAvailableException: virtual OptimizerException {}; +DEV_SIMPLE_EXCEPTION(InvalidDeposit); +DEV_SIMPLE_EXCEPTION(InvalidOpcode); + } } diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 5ff3a905e..ca8d7fbd4 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -266,13 +266,13 @@ unsigned GasMeter::runGas(Instruction _instruction) return 0; } -u256 GasMeter::dataGas(bytes const& _data, bool _inCreation) +u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion) { bigint gas = 0; if (_inCreation) { for (auto b: _data) - gas += (b != 0) ? GasCosts::txDataNonZeroGas : GasCosts::txDataZeroGas; + gas += (b != 0) ? GasCosts::txDataNonZeroGas(_evmVersion) : GasCosts::txDataZeroGas; } else gas = bigint(GasCosts::createDataGas) * _data.size(); diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index f988138c0..38ab4aa6f 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -53,7 +53,12 @@ namespace GasCosts } inline unsigned balanceGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 400 : 20; + if (_evmVersion >= langutil::EVMVersion::istanbul()) + return 700; + else if (_evmVersion >= langutil::EVMVersion::tangerineWhistle()) + return 400; + else + return 20; } static unsigned const expGas = 10; inline unsigned expByteGas(langutil::EVMVersion _evmVersion) @@ -64,7 +69,12 @@ namespace GasCosts static unsigned const keccak256WordGas = 6; inline unsigned sloadGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 200 : 50; + if (_evmVersion >= langutil::EVMVersion::istanbul()) + return 800; + else if (_evmVersion >= langutil::EVMVersion::tangerineWhistle()) + return 200; + else + return 50; } static unsigned const sstoreSetGas = 20000; static unsigned const sstoreResetGas = 5000; @@ -92,7 +102,10 @@ namespace GasCosts static unsigned const txGas = 21000; static unsigned const txCreateGas = 53000; static unsigned const txDataZeroGas = 4; - static unsigned const txDataNonZeroGas = 68; + inline unsigned txDataNonZeroGas(langutil::EVMVersion _evmVersion) + { + return _evmVersion >= langutil::EVMVersion::istanbul() ? 16 : 68; + } static unsigned const copyGas = 3; } @@ -139,7 +152,7 @@ public: /// @returns the gas cost of the supplied data, depending whether it is in creation code, or not. /// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas /// otherwise code will be stored and have to pay "createDataGas" cost. - static u256 dataGas(bytes const& _data, bool _inCreation); + static u256 dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion); private: /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index f2f5879d9..c93fbf158 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -31,9 +31,6 @@ namespace dev namespace eth { -DEV_SIMPLE_EXCEPTION(InvalidDeposit); -DEV_SIMPLE_EXCEPTION(InvalidOpcode); - /// Virtual machine bytecode instruction. enum class Instruction: uint8_t { diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index 73db60560..3d7c476fd 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -54,17 +54,17 @@ ostream& KnownState::stream(ostream& _out) const _out << "=== State ===" << endl; _out << "Stack height: " << dec << m_stackHeight << endl; - _out << "Equivalence classes: " << endl; + _out << "Equivalence classes:" << endl; for (Id eqClass = 0; eqClass < m_expressionClasses->size(); ++eqClass) streamExpressionClass(_out, eqClass); - _out << "Stack: " << endl; + _out << "Stack:" << endl; for (auto const& it: m_stackElements) { _out << " " << dec << it.first << ": "; streamExpressionClass(_out, it.second); } - _out << "Storage: " << endl; + _out << "Storage:" << endl; for (auto const& it: m_storageContent) { _out << " "; @@ -72,7 +72,7 @@ ostream& KnownState::stream(ostream& _out) const _out << ": "; streamExpressionClass(_out, it.second); } - _out << "Memory: " << endl; + _out << "Memory:" << endl; for (auto const& it: m_memoryContent) { _out << " "; diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h index 928908032..3630365e0 100644 --- a/libevmasm/LinkerObject.h +++ b/libevmasm/LinkerObject.h @@ -35,7 +35,9 @@ namespace eth */ struct LinkerObject { + /// The bytecode. bytes bytecode; + /// Map from offsets in bytecode to library identifiers. The addresses starting at those offsets /// need to be replaced by the actual addresses by the linker. std::map linkReferences; @@ -47,7 +49,7 @@ struct LinkerObject void link(std::map const& _libraryAddresses); /// @returns a hex representation of the bytecode of the given object, replacing unlinked - /// addresses by placeholders. + /// addresses by placeholders. This output is lowercase. std::string toHex() const; /// @returns a 36 character string that is used as a placeholder for the library diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index 761defdc5..9d14ac947 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -40,7 +40,7 @@ GasMeter::GasConsumption PathGasMeter::estimateMax( shared_ptr const& _state ) { - auto path = unique_ptr(new GasPath()); + auto path = make_unique(); path->index = _startIndex; path->state = _state->copy(); queue(move(path)); @@ -120,7 +120,7 @@ GasMeter::GasConsumption PathGasMeter::handleQueueItem() for (u256 const& tag: jumpTags) { - auto newPath = unique_ptr(new GasPath()); + auto newPath = make_unique(); newPath->index = m_items.size(); if (m_tagPositions.count(tag)) newPath->index = m_tagPositions.at(tag); diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index e67f614ae..01b2fa963 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -67,47 +67,48 @@ std::vector> simplificationRuleListPart1( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; return std::vector> { // arithmetic on constants - {{Pattern::Builtins::ADD, {A, B}}, [=]{ return A.d() + B.d(); }, false}, - {{Pattern::Builtins::MUL, {A, B}}, [=]{ return A.d() * B.d(); }, false}, - {{Pattern::Builtins::SUB, {A, B}}, [=]{ return A.d() - B.d(); }, false}, - {{Pattern::Builtins::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }, false}, - {{Pattern::Builtins::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, - {{Pattern::Builtins::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }, false}, - {{Pattern::Builtins::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, - {{Pattern::Builtins::EXP, {A, B}}, [=]{ return Word(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << Pattern::WordSize)); }, false}, - {{Pattern::Builtins::NOT, {A}}, [=]{ return ~A.d(); }, false}, - {{Pattern::Builtins::LT, {A, B}}, [=]() -> Word { return A.d() < B.d() ? 1 : 0; }, false}, - {{Pattern::Builtins::GT, {A, B}}, [=]() -> Word { return A.d() > B.d() ? 1 : 0; }, false}, - {{Pattern::Builtins::SLT, {A, B}}, [=]() -> Word { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }, false}, - {{Pattern::Builtins::SGT, {A, B}}, [=]() -> Word { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }, false}, - {{Pattern::Builtins::EQ, {A, B}}, [=]() -> Word { return A.d() == B.d() ? 1 : 0; }, false}, - {{Pattern::Builtins::ISZERO, {A}}, [=]() -> Word { return A.d() == 0 ? 1 : 0; }, false}, - {{Pattern::Builtins::AND, {A, B}}, [=]{ return A.d() & B.d(); }, false}, - {{Pattern::Builtins::OR, {A, B}}, [=]{ return A.d() | B.d(); }, false}, - {{Pattern::Builtins::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }, false}, - {{Pattern::Builtins::BYTE, {A, B}}, [=]{ + {Builtins::ADD(A, B), [=]{ return A.d() + B.d(); }, false}, + {Builtins::MUL(A, B), [=]{ return A.d() * B.d(); }, false}, + {Builtins::SUB(A, B), [=]{ return A.d() - B.d(); }, false}, + {Builtins::DIV(A, B), [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }, false}, + {Builtins::SDIV(A, B), [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, + {Builtins::MOD(A, B), [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }, false}, + {Builtins::SMOD(A, B), [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, + {Builtins::EXP(A, B), [=]{ return Word(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << Pattern::WordSize)); }, false}, + {Builtins::NOT(A), [=]{ return ~A.d(); }, false}, + {Builtins::LT(A, B), [=]() -> Word { return A.d() < B.d() ? 1 : 0; }, false}, + {Builtins::GT(A, B), [=]() -> Word { return A.d() > B.d() ? 1 : 0; }, false}, + {Builtins::SLT(A, B), [=]() -> Word { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }, false}, + {Builtins::SGT(A, B), [=]() -> Word { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }, false}, + {Builtins::EQ(A, B), [=]() -> Word { return A.d() == B.d() ? 1 : 0; }, false}, + {Builtins::ISZERO(A), [=]() -> Word { return A.d() == 0 ? 1 : 0; }, false}, + {Builtins::AND(A, B), [=]{ return A.d() & B.d(); }, false}, + {Builtins::OR(A, B), [=]{ return A.d() | B.d(); }, false}, + {Builtins::XOR(A, B), [=]{ return A.d() ^ B.d(); }, false}, + {Builtins::BYTE(A, B), [=]{ return A.d() >= Pattern::WordSize / 8 ? 0 : (B.d() >> unsigned(8 * (Pattern::WordSize / 8 - 1 - A.d()))) & 0xff; }, false}, - {{Pattern::Builtins::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, - {{Pattern::Builtins::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, - {{Pattern::Builtins::SIGNEXTEND, {A, B}}, [=]() -> Word { + {Builtins::ADDMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, + {Builtins::MULMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, + {Builtins::SIGNEXTEND(A, B), [=]() -> Word { if (A.d() >= Pattern::WordSize / 8 - 1) return B.d(); unsigned testBit = unsigned(A.d()) * 8 + 7; Word mask = (Word(1) << testBit) - 1; return boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask; }, false}, - {{Pattern::Builtins::SHL, {A, B}}, [=]{ + {Builtins::SHL(A, B), [=]{ if (A.d() >= Pattern::WordSize) return Word(0); return shlWorkaround(B.d(), unsigned(A.d())); }, false}, - {{Pattern::Builtins::SHR, {A, B}}, [=]{ + {Builtins::SHR(A, B), [=]{ if (A.d() >= Pattern::WordSize) return Word(0); return B.d() >> unsigned(A.d()); @@ -115,6 +116,7 @@ std::vector> simplificationRuleListPart1( }; } + template std::vector> simplificationRuleListPart2( Pattern, @@ -125,50 +127,51 @@ std::vector> simplificationRuleListPart2( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; return std::vector> { // invariants involving known constants - {{Pattern::Builtins::ADD, {X, 0}}, [=]{ return X; }, false}, - {{Pattern::Builtins::ADD, {0, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::SUB, {X, 0}}, [=]{ return X; }, false}, - {{Pattern::Builtins::SUB, {~Word(0), X}}, [=]() -> Pattern { return {Pattern::Builtins::NOT, {X}}; }, false}, - {{Pattern::Builtins::MUL, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::MUL, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::MUL, {X, 1}}, [=]{ return X; }, false}, - {{Pattern::Builtins::MUL, {1, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::MUL, {X, Word(-1)}}, [=]() -> Pattern { return {Pattern::Builtins::SUB, {0, X}}; }, false}, - {{Pattern::Builtins::MUL, {Word(-1), X}}, [=]() -> Pattern { return {Pattern::Builtins::SUB, {0, X}}; }, false}, - {{Pattern::Builtins::DIV, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::DIV, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::DIV, {X, 1}}, [=]{ return X; }, false}, - {{Pattern::Builtins::SDIV, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SDIV, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SDIV, {X, 1}}, [=]{ return X; }, false}, - {{Pattern::Builtins::AND, {X, ~Word(0)}}, [=]{ return X; }, false}, - {{Pattern::Builtins::AND, {~Word(0), X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::AND, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::AND, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::OR, {X, 0}}, [=]{ return X; }, false}, - {{Pattern::Builtins::OR, {0, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::OR, {X, ~Word(0)}}, [=]{ return ~Word(0); }, true}, - {{Pattern::Builtins::OR, {~Word(0), X}}, [=]{ return ~Word(0); }, true}, - {{Pattern::Builtins::XOR, {X, 0}}, [=]{ return X; }, false}, - {{Pattern::Builtins::XOR, {0, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::MOD, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::MOD, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::EQ, {X, 0}}, [=]() -> Pattern { return {Pattern::Builtins::ISZERO, {X}}; }, false }, - {{Pattern::Builtins::EQ, {0, X}}, [=]() -> Pattern { return {Pattern::Builtins::ISZERO, {X}}; }, false }, - {{Pattern::Builtins::SHL, {0, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::SHR, {0, X}}, [=]{ return X; }, false}, - {{Pattern::Builtins::SHL, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SHR, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::GT, {X, 0}}, [=]() -> Pattern { return {Pattern::Builtins::ISZERO, {{Pattern::Builtins::ISZERO, {X}}}}; }, false}, - {{Pattern::Builtins::LT, {0, X}}, [=]() -> Pattern { return {Pattern::Builtins::ISZERO, {{Pattern::Builtins::ISZERO, {X}}}}; }, false}, - {{Pattern::Builtins::GT, {X, ~Word(0)}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::LT, {~Word(0), X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::GT, {0, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::LT, {X, 0}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::AND, {{Pattern::Builtins::BYTE, {X, Y}}, {Word(0xff)}}}, [=]() -> Pattern { return {Pattern::Builtins::BYTE, {X, Y}}; }, false}, - {{Pattern::Builtins::BYTE, {Pattern::WordSize / 8 - 1, X}}, [=]() -> Pattern { return {Pattern::Builtins::AND, {X, Word(0xff)}}; }, false} + {Builtins::ADD(X, 0), [=]{ return X; }, false}, + {Builtins::ADD(0, X), [=]{ return X; }, false}, + {Builtins::SUB(X, 0), [=]{ return X; }, false}, + {Builtins::SUB(~Word(0), X), [=]() -> Pattern { return Builtins::NOT(X); }, false}, + {Builtins::MUL(X, 0), [=]{ return Word(0); }, true}, + {Builtins::MUL(0, X), [=]{ return Word(0); }, true}, + {Builtins::MUL(X, 1), [=]{ return X; }, false}, + {Builtins::MUL(1, X), [=]{ return X; }, false}, + {Builtins::MUL(X, Word(-1)), [=]() -> Pattern { return Builtins::SUB(0, X); }, false}, + {Builtins::MUL(Word(-1), X), [=]() -> Pattern { return Builtins::SUB(0, X); }, false}, + {Builtins::DIV(X, 0), [=]{ return Word(0); }, true}, + {Builtins::DIV(0, X), [=]{ return Word(0); }, true}, + {Builtins::DIV(X, 1), [=]{ return X; }, false}, + {Builtins::SDIV(X, 0), [=]{ return Word(0); }, true}, + {Builtins::SDIV(0, X), [=]{ return Word(0); }, true}, + {Builtins::SDIV(X, 1), [=]{ return X; }, false}, + {Builtins::AND(X, ~Word(0)), [=]{ return X; }, false}, + {Builtins::AND(~Word(0), X), [=]{ return X; }, false}, + {Builtins::AND(X, 0), [=]{ return Word(0); }, true}, + {Builtins::AND(0, X), [=]{ return Word(0); }, true}, + {Builtins::OR(X, 0), [=]{ return X; }, false}, + {Builtins::OR(0, X), [=]{ return X; }, false}, + {Builtins::OR(X, ~Word(0)), [=]{ return ~Word(0); }, true}, + {Builtins::OR(~Word(0), X), [=]{ return ~Word(0); }, true}, + {Builtins::XOR(X, 0), [=]{ return X; }, false}, + {Builtins::XOR(0, X), [=]{ return X; }, false}, + {Builtins::MOD(X, 0), [=]{ return Word(0); }, true}, + {Builtins::MOD(0, X), [=]{ return Word(0); }, true}, + {Builtins::EQ(X, 0), [=]() -> Pattern { return Builtins::ISZERO(X); }, false }, + {Builtins::EQ(0, X), [=]() -> Pattern { return Builtins::ISZERO(X); }, false }, + {Builtins::SHL(0, X), [=]{ return X; }, false}, + {Builtins::SHR(0, X), [=]{ return X; }, false}, + {Builtins::SHL(X, 0), [=]{ return Word(0); }, true}, + {Builtins::SHR(X, 0), [=]{ return Word(0); }, true}, + {Builtins::GT(X, 0), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false}, + {Builtins::LT(0, X), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false}, + {Builtins::GT(X, ~Word(0)), [=]{ return Word(0); }, true}, + {Builtins::LT(~Word(0), X), [=]{ return Word(0); }, true}, + {Builtins::GT(0, X), [=]{ return Word(0); }, true}, + {Builtins::LT(X, 0), [=]{ return Word(0); }, true}, + {Builtins::AND(Builtins::BYTE(X, Y), Word(0xff)), [=]() -> Pattern { return Builtins::BYTE(X, Y); }, false}, + {Builtins::BYTE(Word(Pattern::WordSize / 8 - 1), X), [=]() -> Pattern { return Builtins::AND(X, Word(0xff)); }, false} }; } @@ -182,18 +185,19 @@ std::vector> simplificationRuleListPart3( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; return std::vector> { // operations involving an expression and itself - {{Pattern::Builtins::AND, {X, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::OR, {X, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::XOR, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SUB, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::EQ, {X, X}}, [=]{ return Word(1); }, true}, - {{Pattern::Builtins::LT, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SLT, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::GT, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::SGT, {X, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::MOD, {X, X}}, [=]{ return Word(0); }, true} + {Builtins::AND(X, X), [=]{ return X; }, true}, + {Builtins::OR(X, X), [=]{ return X; }, true}, + {Builtins::XOR(X, X), [=]{ return Word(0); }, true}, + {Builtins::SUB(X, X), [=]{ return Word(0); }, true}, + {Builtins::EQ(X, X), [=]{ return Word(1); }, true}, + {Builtins::LT(X, X), [=]{ return Word(0); }, true}, + {Builtins::SLT(X, X), [=]{ return Word(0); }, true}, + {Builtins::GT(X, X), [=]{ return Word(0); }, true}, + {Builtins::SGT(X, X), [=]{ return Word(0); }, true}, + {Builtins::MOD(X, X), [=]{ return Word(0); }, true} }; } @@ -207,25 +211,26 @@ std::vector> simplificationRuleListPart4( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; return std::vector> { // logical instruction combinations - {{Pattern::Builtins::NOT, {{Pattern::Builtins::NOT, {X}}}}, [=]{ return X; }, false}, - {{Pattern::Builtins::XOR, {X, {Pattern::Builtins::XOR, {X, Y}}}}, [=]{ return Y; }, true}, - {{Pattern::Builtins::XOR, {X, {Pattern::Builtins::XOR, {Y, X}}}}, [=]{ return Y; }, true}, - {{Pattern::Builtins::XOR, {{Pattern::Builtins::XOR, {X, Y}}, X}}, [=]{ return Y; }, true}, - {{Pattern::Builtins::XOR, {{Pattern::Builtins::XOR, {Y, X}}, X}}, [=]{ return Y; }, true}, - {{Pattern::Builtins::OR, {X, {Pattern::Builtins::AND, {X, Y}}}}, [=]{ return X; }, true}, - {{Pattern::Builtins::OR, {X, {Pattern::Builtins::AND, {Y, X}}}}, [=]{ return X; }, true}, - {{Pattern::Builtins::OR, {{Pattern::Builtins::AND, {X, Y}}, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::OR, {{Pattern::Builtins::AND, {Y, X}}, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::AND, {X, {Pattern::Builtins::OR, {X, Y}}}}, [=]{ return X; }, true}, - {{Pattern::Builtins::AND, {X, {Pattern::Builtins::OR, {Y, X}}}}, [=]{ return X; }, true}, - {{Pattern::Builtins::AND, {{Pattern::Builtins::OR, {X, Y}}, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::AND, {{Pattern::Builtins::OR, {Y, X}}, X}}, [=]{ return X; }, true}, - {{Pattern::Builtins::AND, {X, {Pattern::Builtins::NOT, {X}}}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::AND, {{Pattern::Builtins::NOT, {X}}, X}}, [=]{ return Word(0); }, true}, - {{Pattern::Builtins::OR, {X, {Pattern::Builtins::NOT, {X}}}}, [=]{ return ~Word(0); }, true}, - {{Pattern::Builtins::OR, {{Pattern::Builtins::NOT, {X}}, X}}, [=]{ return ~Word(0); }, true}, + {Builtins::NOT(Builtins::NOT(X)), [=]{ return X; }, false}, + {Builtins::XOR(X, Builtins::XOR(X, Y)), [=]{ return Y; }, true}, + {Builtins::XOR(X, Builtins::XOR(Y, X)), [=]{ return Y; }, true}, + {Builtins::XOR(Builtins::XOR(X, Y), X), [=]{ return Y; }, true}, + {Builtins::XOR(Builtins::XOR(Y, X), X), [=]{ return Y; }, true}, + {Builtins::OR(X, Builtins::AND(X, Y)), [=]{ return X; }, true}, + {Builtins::OR(X, Builtins::AND(Y, X)), [=]{ return X; }, true}, + {Builtins::OR(Builtins::AND(X, Y), X), [=]{ return X; }, true}, + {Builtins::OR(Builtins::AND(Y, X), X), [=]{ return X; }, true}, + {Builtins::AND(X, Builtins::OR(X, Y)), [=]{ return X; }, true}, + {Builtins::AND(X, Builtins::OR(Y, X)), [=]{ return X; }, true}, + {Builtins::AND(Builtins::OR(X, Y), X), [=]{ return X; }, true}, + {Builtins::AND(Builtins::OR(Y, X), X), [=]{ return X; }, true}, + {Builtins::AND(X, Builtins::NOT(X)), [=]{ return Word(0); }, true}, + {Builtins::AND(Builtins::NOT(X), X), [=]{ return Word(0); }, true}, + {Builtins::OR(X, Builtins::NOT(X)), [=]{ return ~Word(0); }, true}, + {Builtins::OR(Builtins::NOT(X), X), [=]{ return ~Word(0); }, true}, }; } @@ -240,6 +245,7 @@ std::vector> simplificationRuleListPart5( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; std::vector> rules; @@ -248,15 +254,15 @@ std::vector> simplificationRuleListPart5( { Word value = Word(1) << i; rules.push_back({ - {Pattern::Builtins::MOD, {X, value}}, - [=]() -> Pattern { return {Pattern::Builtins::AND, {X, value - 1}}; }, + Builtins::MOD(X, value), + [=]() -> Pattern { return Builtins::AND(X, value - 1); }, false }); } // Replace SHL >=256, X with 0 rules.push_back({ - {Pattern::Builtins::SHL, {A, X}}, + Builtins::SHL(A, X), [=]() -> Pattern { return Word(0); }, true, [=]() { return A.d() >= Pattern::WordSize; } @@ -264,7 +270,7 @@ std::vector> simplificationRuleListPart5( // Replace SHR >=256, X with 0 rules.push_back({ - {Pattern::Builtins::SHR, {A, X}}, + Builtins::SHR(A, X), [=]() -> Pattern { return Word(0); }, true, [=]() { return A.d() >= Pattern::WordSize; } @@ -272,29 +278,29 @@ std::vector> simplificationRuleListPart5( // Replace BYTE(A, X), A >= 32 with 0 rules.push_back({ - {Pattern::Builtins::BYTE, {A, X}}, + Builtins::BYTE(A, X), [=]() -> Pattern { return Word(0); }, true, [=]() { return A.d() >= Pattern::WordSize / 8; } }); - for (auto const& op: std::vector{ - Pattern::Builtins::ADDRESS, - Pattern::Builtins::CALLER, - Pattern::Builtins::ORIGIN, - Pattern::Builtins::COINBASE + for (auto instr: { + Instruction::ADDRESS, + Instruction::CALLER, + Instruction::ORIGIN, + Instruction::COINBASE }) { assertThrow(Pattern::WordSize > 160, OptimizerException, ""); Word const mask = (Word(1) << 160) - 1; rules.push_back({ - {Pattern::Builtins::AND, {{op, mask}}}, - [=]() -> Pattern { return op; }, + Builtins::AND(Pattern{instr}, mask), + [=]() -> Pattern { return {instr}; }, false }); rules.push_back({ - {Pattern::Builtins::AND, {{mask, op}}}, - [=]() -> Pattern { return op; }, + Builtins::AND(mask, Pattern{instr}), + [=]() -> Pattern { return {instr}; }, false }); } @@ -311,30 +317,35 @@ std::vector> simplificationRuleListPart6( Pattern Y ) { + using Builtins = typename Pattern::Builtins; + std::vector> rules; // Double negation of opcodes with boolean result - for (auto const& op: std::vector{ - Pattern::Builtins::EQ, - Pattern::Builtins::LT, - Pattern::Builtins::SLT, - Pattern::Builtins::GT, - Pattern::Builtins::SGT + for (auto instr: { + Instruction::EQ, + Instruction::LT, + Instruction::SLT, + Instruction::GT, + Instruction::SGT }) + { + typename Builtins::PatternGeneratorInstance op{instr}; rules.push_back({ - {Pattern::Builtins::ISZERO, {{Pattern::Builtins::ISZERO, {{op, {X, Y}}}}}}, - [=]() -> Pattern { return {op, {X, Y}}; }, + Builtins::ISZERO(Builtins::ISZERO(op(X, Y))), + [=]() -> Pattern { return op(X, Y); }, false }); + } rules.push_back({ - {Pattern::Builtins::ISZERO, {{Pattern::Builtins::ISZERO, {{Pattern::Builtins::ISZERO, {X}}}}}}, - [=]() -> Pattern { return {Pattern::Builtins::ISZERO, {X}}; }, + Builtins::ISZERO(Builtins::ISZERO(Builtins::ISZERO(X))), + [=]() -> Pattern { return Builtins::ISZERO(X); }, false }); rules.push_back({ - {Pattern::Builtins::ISZERO, {{Pattern::Builtins::XOR, {X, Y}}}}, - [=]() -> Pattern { return { Pattern::Builtins::EQ, {X, Y} }; }, + Builtins::ISZERO(Builtins::XOR(X, Y)), + [=]() -> Pattern { return Builtins::EQ(X, Y); }, false }); @@ -351,42 +362,44 @@ std::vector> simplificationRuleListPart7( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; + std::vector> rules; // Associative operations - for (auto const& opFun: std::vector>>{ - {Pattern::Builtins::ADD, std::plus()}, - {Pattern::Builtins::MUL, std::multiplies()}, - {Pattern::Builtins::AND, std::bit_and()}, - {Pattern::Builtins::OR, std::bit_or()}, - {Pattern::Builtins::XOR, std::bit_xor()} + for (auto&& instrAndFunc: std::vector>>{ + {Instruction::ADD, std::plus()}, + {Instruction::MUL, std::multiplies()}, + {Instruction::AND, std::bit_and()}, + {Instruction::OR, std::bit_or()}, + {Instruction::XOR, std::bit_xor()} }) { - auto op = opFun.first; - auto fun = opFun.second; + typename Builtins::PatternGeneratorInstance op{instrAndFunc.first}; + std::function fun = instrAndFunc.second; // Moving constants to the outside, order matters here - we first add rules // for constants and then for non-constants. // xa can be (X, A) or (A, X) - for (auto xa: {std::vector{X, A}, std::vector{A, X}}) + for (auto const& opXA: {op(X, A), op(A, X)}) { rules += std::vector>{{ // (X+A)+B -> X+(A+B) - {op, {{op, xa}, B}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + op(opXA, B), + [=]() -> Pattern { return op(X, fun(A.d(), B.d())); }, false }, { // (X+A)+Y -> (X+Y)+A - {op, {{op, xa}, Y}}, - [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }, + op(opXA, Y), + [=]() -> Pattern { return op(op(X, Y), A); }, false }, { // B+(X+A) -> X+(A+B) - {op, {B, {op, xa}}}, - [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }, + op(B, opXA), + [=]() -> Pattern { return op(X, fun(A.d(), B.d())); }, false }, { // Y+(X+A) -> (Y+X)+A - {op, {Y, {op, xa}}}, - [=]() -> Pattern { return {op, {{op, {Y, X}}, A}}; }, + op(Y, opXA), + [=]() -> Pattern { return op(op(Y, X), A); }, false }}; } @@ -395,13 +408,13 @@ std::vector> simplificationRuleListPart7( // Combine two SHL by constant rules.push_back({ // SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X) - {Pattern::Builtins::SHL, {{B}, {Pattern::Builtins::SHL, {{A}, {X}}}}}, + Builtins::SHL(B, Builtins::SHL(A, X)), [=]() -> Pattern { bigint sum = bigint(A.d()) + B.d(); if (sum >= Pattern::WordSize) - return {Pattern::Builtins::AND, {X, Word(0)}}; + return Builtins::AND(X, Word(0)); else - return {Pattern::Builtins::SHL, {Word(sum), X}}; + return Builtins::SHL(Word(sum), X); }, false }); @@ -409,13 +422,13 @@ std::vector> simplificationRuleListPart7( // Combine two SHR by constant rules.push_back({ // SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X) - {Pattern::Builtins::SHR, {{B}, {Pattern::Builtins::SHR, {{A}, {X}}}}}, + Builtins::SHR(B, Builtins::SHR(A, X)), [=]() -> Pattern { bigint sum = bigint(A.d()) + B.d(); if (sum >= Pattern::WordSize) - return {Pattern::Builtins::AND, {X, Word(0)}}; + return Builtins::AND(X, Word(0)); else - return {Pattern::Builtins::SHR, {Word(sum), X}}; + return Builtins::SHR(Word(sum), X); }, false }); @@ -423,16 +436,16 @@ std::vector> simplificationRuleListPart7( // Combine SHL-SHR by constant rules.push_back({ // SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) - {Pattern::Builtins::SHR, {{B}, {Pattern::Builtins::SHL, {{A}, {X}}}}}, + Builtins::SHR(B, Builtins::SHL(A, X)), [=]() -> Pattern { Word mask = shlWorkaround(~Word(0), unsigned(A.d())) >> unsigned(B.d()); if (A.d() > B.d()) - return {Pattern::Builtins::AND, {{Pattern::Builtins::SHL, {A.d() - B.d(), X}}, mask}}; + return Builtins::AND(Builtins::SHL(A.d() - B.d(), X), mask); else if (B.d() > A.d()) - return {Pattern::Builtins::AND, {{Pattern::Builtins::SHR, {B.d() - A.d(), X}}, mask}}; + return Builtins::AND(Builtins::SHR(B.d() - A.d(), X), mask); else - return {Pattern::Builtins::AND, {X, mask}}; + return Builtins::AND(X, mask); }, false, [=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; } @@ -441,41 +454,42 @@ std::vector> simplificationRuleListPart7( // Combine SHR-SHL by constant rules.push_back({ // SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) - {Pattern::Builtins::SHL, {{B}, {Pattern::Builtins::SHR, {{A}, {X}}}}}, + Builtins::SHL(B, Builtins::SHR(A, X)), [=]() -> Pattern { Word mask = shlWorkaround((~Word(0)) >> unsigned(A.d()), unsigned(B.d())); if (A.d() > B.d()) - return {Pattern::Builtins::AND, {{Pattern::Builtins::SHR, {A.d() - B.d(), X}}, mask}}; + return Builtins::AND(Builtins::SHR(A.d() - B.d(), X), mask); else if (B.d() > A.d()) - return {Pattern::Builtins::AND, {{Pattern::Builtins::SHL, {B.d() - A.d(), X}}, mask}}; + return Builtins::AND(Builtins::SHL(B.d() - A.d(), X), mask); else - return {Pattern::Builtins::AND, {X, mask}}; + return Builtins::AND(X, mask); }, false, [=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; } }); // Move AND with constant across SHL and SHR by constant - for (auto shiftOp: {Pattern::Builtins::SHL, Pattern::Builtins::SHR}) + for (auto instr: {Instruction::SHL, Instruction::SHR}) { + typename Builtins::PatternGeneratorInstance shiftOp{instr}; auto replacement = [=]() -> Pattern { Word mask = - shiftOp == Pattern::Builtins::SHL ? + instr == Instruction::SHL ? shlWorkaround(A.d(), unsigned(B.d())) : A.d() >> unsigned(B.d()); - return {Pattern::Builtins::AND, {{shiftOp, {B.d(), X}}, std::move(mask)}}; + return Builtins::AND(shiftOp(B.d(), X), std::move(mask)); }; rules.push_back({ // SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) - {shiftOp, {{B}, {Pattern::Builtins::AND, {{X}, {A}}}}}, + shiftOp(B, Builtins::AND(X, A)), replacement, false, [=] { return B.d() < Pattern::WordSize; } }); rules.push_back({ // SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) - {shiftOp, {{B}, {Pattern::Builtins::AND, {{A}, {X}}}}}, + shiftOp(B, Builtins::AND(A, X)), replacement, false, [=] { return B.d() < Pattern::WordSize; } @@ -484,27 +498,27 @@ std::vector> simplificationRuleListPart7( rules.push_back({ // MUL(X, SHL(Y, 1)) -> SHL(Y, X) - {Pattern::Builtins::MUL, {X, {Pattern::Builtins::SHL, {Y, Word(1)}}}}, + Builtins::MUL(X, Builtins::SHL(Y, Word(1))), [=]() -> Pattern { - return {Pattern::Builtins::SHL, {Y, X}}; + return Builtins::SHL(Y, X); }, // Actually only changes the order, does not remove. true }); rules.push_back({ // MUL(SHL(X, 1), Y) -> SHL(X, Y) - {Pattern::Builtins::MUL, {{Pattern::Builtins::SHL, {X, Word(1)}}, Y}}, + Builtins::MUL(Builtins::SHL(X, Word(1)), Y), [=]() -> Pattern { - return {Pattern::Builtins::SHL, {X, Y}}; + return Builtins::SHL(X, Y); }, false }); rules.push_back({ // DIV(X, SHL(Y, 1)) -> SHR(Y, X) - {Pattern::Builtins::DIV, {X, {Pattern::Builtins::SHL, {Y, Word(1)}}}}, + Builtins::DIV(X, Builtins::SHL(Y, Word(1))), [=]() -> Pattern { - return {Pattern::Builtins::SHR, {Y, X}}; + return Builtins::SHR(Y, X); }, // Actually only changes the order, does not remove. true @@ -519,16 +533,16 @@ std::vector> simplificationRuleListPart7( rules.push_back({ // AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B) - {Pattern::Builtins::AND, {A, {Pattern::Builtins::SHR, {B, X}}}}, - [=]() -> Pattern { return {Pattern::Builtins::SHR, {B, X}}; }, + Builtins::AND(A, Builtins::SHR(B, X)), + [=]() -> Pattern { return Builtins::SHR(B, X); }, false, feasibilityFunction }); rules.push_back({ // AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B) - {Pattern::Builtins::AND, {{Pattern::Builtins::SHR, {B, X}}, A}}, - [=]() -> Pattern { return {Pattern::Builtins::SHR, {B, X}}; }, + Builtins::AND(Builtins::SHR(B, X), A), + [=]() -> Pattern { return Builtins::SHR(B, X); }, false, feasibilityFunction }); @@ -545,34 +559,35 @@ std::vector> simplificationRuleListPart8( Pattern Y ) { + using Builtins = typename Pattern::Builtins; std::vector> rules; // move constants across subtractions rules += std::vector>{ { // X - A -> X + (-A) - {Pattern::Builtins::SUB, {X, A}}, - [=]() -> Pattern { return {Pattern::Builtins::ADD, {X, 0 - A.d()}}; }, + Builtins::SUB(X, A), + [=]() -> Pattern { return Builtins::ADD(X, 0 - A.d()); }, false }, { // (X + A) - Y -> (X - Y) + A - {Pattern::Builtins::SUB, {{Pattern::Builtins::ADD, {X, A}}, Y}}, - [=]() -> Pattern { return {Pattern::Builtins::ADD, {{Pattern::Builtins::SUB, {X, Y}}, A}}; }, + Builtins::SUB(Builtins::ADD(X, A), Y), + [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }, false }, { // (A + X) - Y -> (X - Y) + A - {Pattern::Builtins::SUB, {{Pattern::Builtins::ADD, {A, X}}, Y}}, - [=]() -> Pattern { return {Pattern::Builtins::ADD, {{Pattern::Builtins::SUB, {X, Y}}, A}}; }, + Builtins::SUB(Builtins::ADD(A, X), Y), + [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }, false }, { // X - (Y + A) -> (X - Y) + (-A) - {Pattern::Builtins::SUB, {X, {Pattern::Builtins::ADD, {Y, A}}}}, - [=]() -> Pattern { return {Pattern::Builtins::ADD, {{Pattern::Builtins::SUB, {X, Y}}, 0 - A.d()}}; }, + Builtins::SUB(X, Builtins::ADD(Y, A)), + [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }, false }, { // X - (A + Y) -> (X - Y) + (-A) - {Pattern::Builtins::SUB, {X, {Pattern::Builtins::ADD, {A, Y}}}}, - [=]() -> Pattern { return {Pattern::Builtins::ADD, {{Pattern::Builtins::SUB, {X, Y}}, 0 - A.d()}}; }, + Builtins::SUB(X, Builtins::ADD(A, Y)), + [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }, false } }; @@ -591,30 +606,31 @@ std::vector> simplificationRuleListPart9( ) { using Word = typename Pattern::Word; + using Builtins = typename Pattern::Builtins; std::vector> rules; assertThrow(Pattern::WordSize > 160, OptimizerException, ""); Word const mask = (Word(1) << 160) - 1; // CREATE rules.push_back({ - {Pattern::Builtins::AND, {{Pattern::Builtins::CREATE, {W, X, Y}}, mask}}, - [=]() -> Pattern { return {Pattern::Builtins::CREATE, {W, X, Y}}; }, + Builtins::AND(Builtins::CREATE(W, X, Y), mask), + [=]() -> Pattern { return Builtins::CREATE(W, X, Y); }, false }); rules.push_back({ - {Pattern::Builtins::AND, {{mask, {Pattern::Builtins::CREATE, {W, X, Y}}}}}, - [=]() -> Pattern { return {Pattern::Builtins::CREATE, {W, X, Y}}; }, + Builtins::AND(mask, Builtins::CREATE(W, X, Y)), + [=]() -> Pattern { return Builtins::CREATE(W, X, Y); }, false }); // CREATE2 rules.push_back({ - {Pattern::Builtins::AND, {{Pattern::Builtins::CREATE2, {W, X, Y, Z}}, mask}}, - [=]() -> Pattern { return {Pattern::Builtins::CREATE2, {W, X, Y, Z}}; }, + Builtins::AND(Builtins::CREATE2(W, X, Y, Z), mask), + [=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); }, false }); rules.push_back({ - {Pattern::Builtins::AND, {{mask, {Pattern::Builtins::CREATE2, {W, X, Y, Z}}}}}, - [=]() -> Pattern { return {Pattern::Builtins::CREATE2, {W, X, Y, Z}}; }, + Builtins::AND(mask, Builtins::CREATE2(W, X, Y, Z)), + [=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); }, false }); diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index b35b89551..f12b859cd 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include namespace dev @@ -55,84 +56,105 @@ struct SimplificationRule std::function feasible; }; +template struct EVMBuiltins { using InstrType = Instruction; - static auto constexpr STOP = Instruction::STOP; - static auto constexpr ADD = Instruction::ADD; - static auto constexpr SUB = Instruction::SUB; - static auto constexpr MUL = Instruction::MUL; - static auto constexpr DIV = Instruction::DIV; - static auto constexpr SDIV = Instruction::SDIV; - static auto constexpr MOD = Instruction::MOD; - static auto constexpr SMOD = Instruction::SMOD; - static auto constexpr EXP = Instruction::EXP; - static auto constexpr NOT = Instruction::NOT; - static auto constexpr LT = Instruction::LT; - static auto constexpr GT = Instruction::GT; - static auto constexpr SLT = Instruction::SLT; - static auto constexpr SGT = Instruction::SGT; - static auto constexpr EQ = Instruction::EQ; - static auto constexpr ISZERO = Instruction::ISZERO; - static auto constexpr AND = Instruction::AND; - static auto constexpr OR = Instruction::OR; - static auto constexpr XOR = Instruction::XOR; - static auto constexpr BYTE = Instruction::BYTE; - static auto constexpr SHL = Instruction::SHL; - static auto constexpr SHR = Instruction::SHR; - static auto constexpr SAR = Instruction::SAR; - static auto constexpr ADDMOD = Instruction::ADDMOD; - static auto constexpr MULMOD = Instruction::MULMOD; - static auto constexpr SIGNEXTEND = Instruction::SIGNEXTEND; - static auto constexpr KECCAK256 = Instruction::KECCAK256; - static auto constexpr ADDRESS = Instruction::ADDRESS; - static auto constexpr BALANCE = Instruction::BALANCE; - static auto constexpr ORIGIN = Instruction::ORIGIN; - static auto constexpr CALLER = Instruction::CALLER; - static auto constexpr CALLVALUE = Instruction::CALLVALUE; - static auto constexpr CALLDATALOAD = Instruction::CALLDATALOAD; - static auto constexpr CALLDATASIZE = Instruction::CALLDATASIZE; - static auto constexpr CALLDATACOPY = Instruction::CALLDATACOPY; - static auto constexpr CODESIZE = Instruction::CODESIZE; - static auto constexpr CODECOPY = Instruction::CODECOPY; - static auto constexpr GASPRICE = Instruction::GASPRICE; - static auto constexpr EXTCODESIZE = Instruction::EXTCODESIZE; - static auto constexpr EXTCODECOPY = Instruction::EXTCODECOPY; - static auto constexpr RETURNDATASIZE = Instruction::RETURNDATASIZE; - static auto constexpr RETURNDATACOPY = Instruction::RETURNDATACOPY; - static auto constexpr EXTCODEHASH = Instruction::EXTCODEHASH; - static auto constexpr BLOCKHASH = Instruction::BLOCKHASH; - static auto constexpr COINBASE = Instruction::COINBASE; - static auto constexpr TIMESTAMP = Instruction::TIMESTAMP; - static auto constexpr NUMBER = Instruction::NUMBER; - static auto constexpr DIFFICULTY = Instruction::DIFFICULTY; - static auto constexpr GASLIMIT = Instruction::GASLIMIT; - static auto constexpr CHAINID = Instruction::CHAINID; - static auto constexpr SELFBALANCE = Instruction::SELFBALANCE; - static auto constexpr POP = Instruction::POP; - static auto constexpr MLOAD = Instruction::MLOAD; - static auto constexpr MSTORE = Instruction::MSTORE; - static auto constexpr MSTORE8 = Instruction::MSTORE8; - static auto constexpr SLOAD = Instruction::SLOAD; - static auto constexpr SSTORE = Instruction::SSTORE; - static auto constexpr PC = Instruction::PC; - static auto constexpr MSIZE = Instruction::MSIZE; - static auto constexpr GAS = Instruction::GAS; - static auto constexpr LOG0 = Instruction::LOG0; - static auto constexpr LOG1 = Instruction::LOG1; - static auto constexpr LOG2 = Instruction::LOG2; - static auto constexpr LOG3 = Instruction::LOG3; - static auto constexpr LOG4 = Instruction::LOG4; - static auto constexpr CREATE = Instruction::CREATE; - static auto constexpr CALL = Instruction::CALL; - static auto constexpr CALLCODE = Instruction::CALLCODE; - static auto constexpr STATICCALL = Instruction::STATICCALL; - static auto constexpr RETURN = Instruction::RETURN; - static auto constexpr DELEGATECALL = Instruction::DELEGATECALL; - static auto constexpr CREATE2 = Instruction::CREATE2; - static auto constexpr REVERT = Instruction::REVERT; - static auto constexpr INVALID = Instruction::INVALID; - static auto constexpr SELFDESTRUCT = Instruction::SELFDESTRUCT; + + template + struct PatternGenerator + { + template constexpr Pattern operator()(Args&&... _args) const + { + return {inst, {std::forward(_args)...}}; + }; + }; + + struct PatternGeneratorInstance + { + Instruction instruction; + template constexpr Pattern operator()(Args&&... _args) const + { + return {instruction, {std::forward(_args)...}}; + }; + }; + + + static auto constexpr STOP = PatternGenerator{}; + static auto constexpr ADD = PatternGenerator{}; + static auto constexpr SUB = PatternGenerator{}; + static auto constexpr MUL = PatternGenerator{}; + static auto constexpr DIV = PatternGenerator{}; + static auto constexpr SDIV = PatternGenerator{}; + static auto constexpr MOD = PatternGenerator{}; + static auto constexpr SMOD = PatternGenerator{}; + static auto constexpr EXP = PatternGenerator{}; + static auto constexpr NOT = PatternGenerator{}; + static auto constexpr LT = PatternGenerator{}; + static auto constexpr GT = PatternGenerator{}; + static auto constexpr SLT = PatternGenerator{}; + static auto constexpr SGT = PatternGenerator{}; + static auto constexpr EQ = PatternGenerator{}; + static auto constexpr ISZERO = PatternGenerator{}; + static auto constexpr AND = PatternGenerator{}; + static auto constexpr OR = PatternGenerator{}; + static auto constexpr XOR = PatternGenerator{}; + static auto constexpr BYTE = PatternGenerator{}; + static auto constexpr SHL = PatternGenerator{}; + static auto constexpr SHR = PatternGenerator{}; + static auto constexpr SAR = PatternGenerator{}; + static auto constexpr ADDMOD = PatternGenerator{}; + static auto constexpr MULMOD = PatternGenerator{}; + static auto constexpr SIGNEXTEND = PatternGenerator{}; + static auto constexpr KECCAK256 = PatternGenerator{}; + static auto constexpr ADDRESS = PatternGenerator{}; + static auto constexpr BALANCE = PatternGenerator{}; + static auto constexpr ORIGIN = PatternGenerator{}; + static auto constexpr CALLER = PatternGenerator{}; + static auto constexpr CALLVALUE = PatternGenerator{}; + static auto constexpr CALLDATALOAD = PatternGenerator{}; + static auto constexpr CALLDATASIZE = PatternGenerator{}; + static auto constexpr CALLDATACOPY = PatternGenerator{}; + static auto constexpr CODESIZE = PatternGenerator{}; + static auto constexpr CODECOPY = PatternGenerator{}; + static auto constexpr GASPRICE = PatternGenerator{}; + static auto constexpr EXTCODESIZE = PatternGenerator{}; + static auto constexpr EXTCODECOPY = PatternGenerator{}; + static auto constexpr RETURNDATASIZE = PatternGenerator{}; + static auto constexpr RETURNDATACOPY = PatternGenerator{}; + static auto constexpr EXTCODEHASH = PatternGenerator{}; + static auto constexpr BLOCKHASH = PatternGenerator{}; + static auto constexpr COINBASE = PatternGenerator{}; + static auto constexpr TIMESTAMP = PatternGenerator{}; + static auto constexpr NUMBER = PatternGenerator{}; + static auto constexpr DIFFICULTY = PatternGenerator{}; + static auto constexpr GASLIMIT = PatternGenerator{}; + static auto constexpr CHAINID = PatternGenerator{}; + static auto constexpr SELFBALANCE = PatternGenerator{}; + static auto constexpr POP = PatternGenerator{}; + static auto constexpr MLOAD = PatternGenerator{}; + static auto constexpr MSTORE = PatternGenerator{}; + static auto constexpr MSTORE8 = PatternGenerator{}; + static auto constexpr SLOAD = PatternGenerator{}; + static auto constexpr SSTORE = PatternGenerator{}; + static auto constexpr PC = PatternGenerator{}; + static auto constexpr MSIZE = PatternGenerator{}; + static auto constexpr GAS = PatternGenerator{}; + static auto constexpr LOG0 = PatternGenerator{}; + static auto constexpr LOG1 = PatternGenerator{}; + static auto constexpr LOG2 = PatternGenerator{}; + static auto constexpr LOG3 = PatternGenerator{}; + static auto constexpr LOG4 = PatternGenerator{}; + static auto constexpr CREATE = PatternGenerator{}; + static auto constexpr CALL = PatternGenerator{}; + static auto constexpr CALLCODE = PatternGenerator{}; + static auto constexpr STATICCALL = PatternGenerator{}; + static auto constexpr RETURN = PatternGenerator{}; + static auto constexpr DELEGATECALL = PatternGenerator{}; + static auto constexpr CREATE2 = PatternGenerator{}; + static auto constexpr REVERT = PatternGenerator{}; + static auto constexpr INVALID = PatternGenerator{}; + static auto constexpr SELFDESTRUCT = PatternGenerator{}; }; } diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index ca201862a..28b64078a 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -99,7 +99,7 @@ Rules::Rules() assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } -Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): +Pattern::Pattern(Instruction _instruction, std::initializer_list _arguments): m_type(Operation), m_instruction(_instruction), m_arguments(_arguments) diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h index a8c782b26..a01507411 100644 --- a/libevmasm/SimplificationRules.h +++ b/libevmasm/SimplificationRules.h @@ -26,6 +26,8 @@ #include #include +#include + #include #include @@ -87,18 +89,20 @@ public: using Expression = ExpressionClasses::Expression; using Id = ExpressionClasses::Id; - using Builtins = dev::eth::EVMBuiltins; + using Builtins = dev::eth::EVMBuiltins; static constexpr size_t WordSize = 256; using Word = u256; // Matches a specific constant value. Pattern(unsigned _value): Pattern(u256(_value)) {} + Pattern(int _value): Pattern(u256(_value)) {} + Pattern(long unsigned _value): Pattern(u256(_value)) {} // Matches a specific constant value. Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared(_value)) {} // Matches a specific assembly item type or anything if not given. Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} // Matches a given instruction with given arguments - Pattern(Instruction _instruction, std::vector const& _arguments = {}); + Pattern(Instruction _instruction, std::initializer_list _arguments = {}); /// Sets this pattern to be part of the match group with the identifier @a _group. /// Inside one rule, all patterns in the same match group have to match expressions from the /// same expression equivalence class. diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index 95a9eb089..5151d85a9 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -98,7 +98,7 @@ private: EVMVersion(Version _version): m_version(_version) {} - Version m_version = Version::Petersburg; + Version m_version = Version::Istanbul; }; } diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 0d65334fb..3ddb23cb1 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -798,7 +798,7 @@ Token Scanner::scanHexString() literal.complete(); advance(); // consume quote - return Token::StringLiteral; + return Token::HexStringLiteral; } // Parse for regex [:digit:]+(_[:digit:]+)* diff --git a/liblangutil/SemVerHandler.cpp b/liblangutil/SemVerHandler.cpp index 378923420..cc414b1d7 100644 --- a/liblangutil/SemVerHandler.cpp +++ b/liblangutil/SemVerHandler.cpp @@ -88,7 +88,7 @@ bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _versio if (!comp.matches(_version)) return false; - if (comp.version.numbers[0] == 0) + if (comp.version.numbers[0] == 0 && comp.levelsPresent != 1) comp.levelsPresent = 2; else comp.levelsPresent = 1; @@ -105,6 +105,7 @@ bool SemVerMatchExpression::MatchComponent::matches(SemVerVersion const& _versio didCompare = true; cmp = _version.numbers[i] - version.numbers[i]; } + if (cmp == 0 && !_version.prerelease.empty() && didCompare) cmp = -1; diff --git a/liblangutil/Token.h b/liblangutil/Token.h index 35d4f76f3..c5e829524 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -221,6 +221,7 @@ namespace langutil K(FalseLiteral, "false", 0) \ T(Number, nullptr, 0) \ T(StringLiteral, nullptr, 0) \ + T(HexStringLiteral, nullptr, 0) \ T(CommentLiteral, nullptr, 0) \ \ /* Identifiers (not keywords or future reserved words). */ \ diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index ded239559..3de6b68ec 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -41,6 +41,8 @@ set(sources ast/ASTJsonConverter.h ast/ASTPrinter.cpp ast/ASTPrinter.h + ast/ASTUtils.cpp + ast/ASTUtils.h ast/ASTVisitor.h ast/ExperimentalFeatures.h ast/Types.cpp diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 6bafc4caa..37d8c824f 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -35,7 +35,7 @@ unique_ptr ControlFlowBuilder::createFunctionFlow( FunctionDefinition const& _function ) { - auto functionFlow = unique_ptr(new FunctionFlow()); + auto functionFlow = make_unique(); functionFlow->entry = _nodeContainer.newNode(); functionFlow->exit = _nodeContainer.newNode(); functionFlow->revert = _nodeContainer.newNode(); diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index cc28220ca..008e37bdb 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -48,7 +48,7 @@ NameAndTypeResolver::NameAndTypeResolver( m_globalContext(_globalContext) { if (!m_scopes[nullptr]) - m_scopes[nullptr].reset(new DeclarationContainer()); + m_scopes[nullptr] = make_shared(); for (Declaration const* declaration: _globalContext.declarations()) { solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration."); @@ -545,7 +545,7 @@ bool DeclarationRegistrationHelper::visit(SourceUnit& _sourceUnit) { if (!m_scopes[&_sourceUnit]) // By importing, it is possible that the container already exists. - m_scopes[&_sourceUnit].reset(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get())); + m_scopes[&_sourceUnit] = make_shared(m_currentScope, m_scopes[m_currentScope].get()); m_currentScope = &_sourceUnit; return true; } @@ -561,7 +561,7 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import) SourceUnit const* importee = _import.annotation().sourceUnit; solAssert(!!importee, ""); if (!m_scopes[importee]) - m_scopes[importee].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get())); + m_scopes[importee] = make_shared(nullptr, m_scopes[nullptr].get()); m_scopes[&_import] = m_scopes[importee]; registerDeclaration(_import, false); return true; @@ -705,7 +705,7 @@ void DeclarationRegistrationHelper::enterNewSubScope(ASTNode& _subScope) { map>::iterator iter; bool newlyAdded; - shared_ptr container(new DeclarationContainer(m_currentScope, m_scopes[m_currentScope].get())); + shared_ptr container{make_shared(m_currentScope, m_scopes[m_currentScope].get())}; tie(iter, newlyAdded) = m_scopes.emplace(&_subScope, move(container)); solAssert(newlyAdded, "Unable to add new scope."); m_currentScope = &_subScope; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 262db9d1d..4982ee158 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -641,6 +642,8 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) solAssert(var->type(), "Expected variable type!"); if (var->isConstant()) { + var = rootVariableDeclaration(*var); + if (!var->value()) { m_errorReporter.typeError(_identifier.location, "Constant has no value."); @@ -651,7 +654,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) type(*var->value())->category() != Type::Category::RationalNumber )) { - m_errorReporter.typeError(_identifier.location, "Only direct number constants are supported by inline assembly."); + m_errorReporter.typeError(_identifier.location, "Only direct number constants and references to such constants are supported by inline assembly."); return size_t(-1); } else if (_context == yul::IdentifierContext::LValue) diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 58baec906..b18261113 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -21,7 +21,9 @@ #include #include #include + #include +#include using namespace std; using namespace dev; @@ -31,7 +33,7 @@ using namespace dev::solidity; namespace { -class AssemblyViewPureChecker: public boost::static_visitor +class AssemblyViewPureChecker { public: explicit AssemblyViewPureChecker( @@ -52,21 +54,21 @@ public: { checkInstruction(_instr.location, _instr.instruction); for (auto const& arg: _instr.arguments) - boost::apply_visitor(*this, arg); + std::visit(*this, arg); } void operator()(yul::ExpressionStatement const& _expr) { - boost::apply_visitor(*this, _expr.expression); + std::visit(*this, _expr.expression); } void operator()(yul::StackAssignment const&) {} void operator()(yul::Assignment const& _assignment) { - boost::apply_visitor(*this, *_assignment.value); + std::visit(*this, *_assignment.value); } void operator()(yul::VariableDeclaration const& _varDecl) { if (_varDecl.value) - boost::apply_visitor(*this, *_varDecl.value); + std::visit(*this, *_varDecl.value); } void operator()(yul::FunctionDefinition const& _funDef) { @@ -80,16 +82,16 @@ public: checkInstruction(_funCall.location, *fun->instruction); for (auto const& arg: _funCall.arguments) - boost::apply_visitor(*this, arg); + std::visit(*this, arg); } void operator()(yul::If const& _if) { - boost::apply_visitor(*this, *_if.condition); + std::visit(*this, *_if.condition); (*this)(_if.body); } void operator()(yul::Switch const& _switch) { - boost::apply_visitor(*this, *_switch.expression); + std::visit(*this, *_switch.expression); for (auto const& _case: _switch.cases) { if (_case.value) @@ -100,7 +102,7 @@ public: void operator()(yul::ForLoop const& _for) { (*this)(_for.pre); - boost::apply_visitor(*this, *_for.condition); + std::visit(*this, *_for.condition); (*this)(_for.body); (*this)(_for.post); } @@ -113,7 +115,7 @@ public: void operator()(yul::Block const& _block) { for (auto const& s: _block.statements) - boost::apply_visitor(*this, s); + std::visit(*this, s); } private: diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 60703f76e..90df0deb2 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -55,11 +55,6 @@ ASTNode::ASTNode(SourceLocation const& _location): { } -ASTNode::~ASTNode() -{ - delete m_annotation; -} - void ASTNode::resetID() { IDDispenser::reset(); @@ -68,14 +63,14 @@ void ASTNode::resetID() ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) - m_annotation = new ASTAnnotation(); + m_annotation = make_unique(); return *m_annotation; } SourceUnitAnnotation& SourceUnit::annotation() const { if (!m_annotation) - m_annotation = new SourceUnitAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -99,7 +94,7 @@ set SourceUnit::referencedSourceUnits(bool _recurse, set(); return dynamic_cast(*m_annotation); } @@ -168,7 +163,7 @@ vector const& ContractDefinition::interfaceEvents() cons if (!m_interfaceEvents) { set eventsSeen; - m_interfaceEvents.reset(new vector()); + m_interfaceEvents = make_unique>(); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) for (EventDefinition const* e: contract->events()) { @@ -193,7 +188,7 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter if (!m_interfaceFunctionList) { set signaturesSeen; - m_interfaceFunctionList.reset(new vector, FunctionTypePointer>>()); + m_interfaceFunctionList = make_unique, FunctionTypePointer>>>(); for (ContractDefinition const* contract: annotation().linearizedBaseContracts) { vector functions; @@ -225,7 +220,7 @@ vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) { - m_inheritableMembers.reset(new vector()); + m_inheritableMembers = make_unique>(); auto addInheritableMember = [&](Declaration const* _decl) { solAssert(_decl, "addInheritableMember got a nullpointer."); @@ -259,14 +254,14 @@ TypePointer ContractDefinition::type() const ContractDefinitionAnnotation& ContractDefinition::annotation() const { if (!m_annotation) - m_annotation = new ContractDefinitionAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } TypeNameAnnotation& TypeName::annotation() const { if (!m_annotation) - m_annotation = new TypeNameAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -278,7 +273,7 @@ TypePointer StructDefinition::type() const TypeDeclarationAnnotation& StructDefinition::annotation() const { if (!m_annotation) - m_annotation = new TypeDeclarationAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -297,7 +292,7 @@ TypePointer EnumDefinition::type() const TypeDeclarationAnnotation& EnumDefinition::annotation() const { if (!m_annotation) - m_annotation = new TypeDeclarationAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -357,7 +352,7 @@ string FunctionDefinition::externalSignature() const FunctionDefinitionAnnotation& FunctionDefinition::annotation() const { if (!m_annotation) - m_annotation = new FunctionDefinitionAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -369,7 +364,7 @@ TypePointer ModifierDefinition::type() const ModifierDefinitionAnnotation& ModifierDefinition::annotation() const { if (!m_annotation) - m_annotation = new ModifierDefinitionAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -389,14 +384,14 @@ FunctionTypePointer EventDefinition::functionType(bool _internal) const EventDefinitionAnnotation& EventDefinition::annotation() const { if (!m_annotation) - m_annotation = new EventDefinitionAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const { if (!m_annotation) - m_annotation = new UserDefinedTypeNameAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } @@ -604,63 +599,63 @@ FunctionTypePointer VariableDeclaration::functionType(bool _internal) const VariableDeclarationAnnotation& VariableDeclaration::annotation() const { if (!m_annotation) - m_annotation = new VariableDeclarationAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } StatementAnnotation& Statement::annotation() const { if (!m_annotation) - m_annotation = new StatementAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } InlineAssemblyAnnotation& InlineAssembly::annotation() const { if (!m_annotation) - m_annotation = new InlineAssemblyAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } ReturnAnnotation& Return::annotation() const { if (!m_annotation) - m_annotation = new ReturnAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } ExpressionAnnotation& Expression::annotation() const { if (!m_annotation) - m_annotation = new ExpressionAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } MemberAccessAnnotation& MemberAccess::annotation() const { if (!m_annotation) - m_annotation = new MemberAccessAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } BinaryOperationAnnotation& BinaryOperation::annotation() const { if (!m_annotation) - m_annotation = new BinaryOperationAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } FunctionCallAnnotation& FunctionCall::annotation() const { if (!m_annotation) - m_annotation = new FunctionCallAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } IdentifierAnnotation& Identifier::annotation() const { if (!m_annotation) - m_annotation = new IdentifierAnnotation(); + m_annotation = make_unique(); return dynamic_cast(*m_annotation); } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index b86b28bc6..dbde1c9f9 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -67,7 +67,7 @@ public: using SourceLocation = langutil::SourceLocation; explicit ASTNode(SourceLocation const& _location); - virtual ~ASTNode(); + virtual ~ASTNode() {} /// @returns an identifier of this AST node that is unique for a single compilation run. size_t id() const { return m_id; } @@ -111,7 +111,7 @@ public: protected: size_t const m_id = 0; /// Annotation - is specialised in derived classes, is created upon request (because of polymorphism). - mutable ASTAnnotation* m_annotation = nullptr; + mutable std::unique_ptr m_annotation; private: SourceLocation m_location; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 0162fc1c1..4d686b6f1 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -801,6 +801,7 @@ string ASTJsonConverter::literalTokenKind(Token _token) case dev::solidity::Token::Number: return "number"; case dev::solidity::Token::StringLiteral: + case dev::solidity::Token::HexStringLiteral: return "string"; case dev::solidity::Token::TrueLiteral: case dev::solidity::Token::FalseLiteral: diff --git a/libsolidity/ast/ASTUtils.cpp b/libsolidity/ast/ASTUtils.cpp new file mode 100644 index 000000000..991083dee --- /dev/null +++ b/libsolidity/ast/ASTUtils.cpp @@ -0,0 +1,42 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include +#include + +namespace dev +{ +namespace solidity +{ + +VariableDeclaration const* rootVariableDeclaration(VariableDeclaration const& _varDecl) +{ + solAssert(_varDecl.isConstant(), "Constant variable expected"); + + VariableDeclaration const* rootDecl = &_varDecl; + Identifier const* identifier; + while ((identifier = dynamic_cast(rootDecl->value().get()))) + { + auto referencedVarDecl = dynamic_cast(identifier->annotation().referencedDeclaration); + solAssert(referencedVarDecl && referencedVarDecl->isConstant(), "Identifier is not referencing a variable declaration"); + rootDecl = referencedVarDecl; + } + return rootDecl; +} + +} +} diff --git a/libsolidity/ast/ASTUtils.h b/libsolidity/ast/ASTUtils.h new file mode 100644 index 000000000..67841f1ce --- /dev/null +++ b/libsolidity/ast/ASTUtils.h @@ -0,0 +1,30 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +namespace dev +{ +namespace solidity +{ + +/// Find the topmost referenced variable declaration when the given variable +/// declaration value is an identifier. Works only for constant variable declarations. +VariableDeclaration const* rootVariableDeclaration(VariableDeclaration const& _varDecl); + +} +} diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 6e94c011b..1962df9d5 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -331,6 +331,7 @@ TypePointer TypeProvider::forLiteral(Literal const& _literal) case Token::Number: return rationalNumber(_literal); case Token::StringLiteral: + case Token::HexStringLiteral: return stringLiteral(_literal.value()); default: return nullptr; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f5e315829..d1c579d2b 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -329,7 +329,7 @@ MemberList const& Type::members(ContractDefinition const* _currentScope) const MemberList::MemberMap members = nativeMembers(_currentScope); if (_currentScope) members += boundFunctions(*this, *_currentScope); - m_members[_currentScope] = unique_ptr(new MemberList(move(members))); + m_members[_currentScope] = make_unique(move(members)); } return *m_members[_currentScope]; } @@ -1433,6 +1433,9 @@ Type const* ContractType::encodingType() const BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const { + if (m_super) + return false; + if (*this == _convertTo) return true; if (_convertTo.category() == Category::Contract) @@ -1450,8 +1453,12 @@ BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const { + if (m_super) + return false; + if (auto const* addressType = dynamic_cast(&_convertTo)) return isPayable() || (addressType->stateMutability() < StateMutability::Payable); + return isImplicitlyConvertibleTo(_convertTo); } @@ -2962,6 +2969,20 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con ); return members; } + case Kind::DelegateCall: + { + auto const* functionDefinition = dynamic_cast(m_declaration); + solAssert(functionDefinition, ""); + solAssert(functionDefinition->visibility() != Declaration::Visibility::Private, ""); + if (functionDefinition->visibility() != Declaration::Visibility::Internal) + { + auto const* contract = dynamic_cast(m_declaration->scope()); + solAssert(contract, ""); + solAssert(contract->isLibrary(), ""); + return {{"selector", TypeProvider::fixedBytes(4)}}; + } + return {}; + } default: return MemberList::MemberMap(); } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 03dc40c5b..224d55449 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -975,7 +976,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(i * 32); - wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); + wordParams[i]["wordValue"] = formatAsStringOrNumber(value.substr(32 * i, 32)); } templ("word", wordParams); return templ.render(); @@ -990,7 +991,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( } )"); templ("functionName", functionName); - templ("wordValue", "0x" + h256(value, h256::AlignLeft).hex()); + templ("wordValue", formatAsStringOrNumber(value)); return templ.render(); } }); diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index d6045ee20..3ec0124e3 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -438,7 +438,7 @@ void CompilerContext::appendInlineAssembly( parserResult = std::move(obj.code); #ifdef SOL_OUTPUT_ASM - cout << "After optimizer: " << endl; + cout << "After optimizer:" << endl; cout << yul::AsmPrinter()(*parserResult) << endl; #endif } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index a275a6e03..5333cfddb 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -711,6 +711,9 @@ void CompilerUtils::convertType( Type::Category stackTypeCategory = _typeOnStack.category(); Type::Category targetTypeCategory = _targetType.category(); + if (auto contrType = dynamic_cast(&_typeOnStack)) + solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\""); + bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer; if (chopSignBitsPending) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a9c3990f8..4ad432bf8 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -631,6 +632,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { if (variable->isConstant()) { + variable = rootVariableDeclaration(*variable); u256 value; if (variable->value()->annotation().type->category() == Type::Category::RationalNumber) { diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 62ae2dd63..9a7b45412 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -345,7 +345,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) if (_tuple.components().size() == 1) m_currentLValue = move(lvalues[0]); else - m_currentLValue.reset(new TupleObject(m_context, move(lvalues))); + m_currentLValue = make_unique(m_context, move(lvalues)); } } return false; diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 645d67067..07754dedc 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -136,7 +136,7 @@ template void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments const&... _arguments) { solAssert(!m_currentLValue, "Current LValue not reset before trying to set new one."); - std::unique_ptr<_LValueType> lvalue(new _LValueType(m_context, _arguments...)); + std::unique_ptr<_LValueType> lvalue = std::make_unique<_LValueType>(m_context, _arguments...); if (_expression.annotation().lValueRequested) m_currentLValue = move(lvalue); else diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 8910ad7e7..1699bf9d3 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -23,6 +23,8 @@ #include #include #include + +#include #include #include @@ -1756,7 +1758,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const for (size_t i = 0; i < words; ++i) { wordParams[i]["offset"] = to_string(32 + i * 32); - wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex(); + wordParams[i]["wordValue"] = formatAsStringOrNumber(data.substr(32 * i, 32)); } templ("word", wordParams); return templ.render(); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 5dcb811cc..b2d47292f 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -833,9 +833,9 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) yul::Statement modified = bodyCopier(_inlineAsm.operations()); - solAssert(modified.type() == typeid(yul::Block), ""); + solAssert(holds_alternative(modified), ""); - m_code << yul::AsmPrinter()(boost::get(std::move(modified))) << "\n"; + m_code << yul::AsmPrinter()(std::get(std::move(modified))) << "\n"; return false; } diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp index dc1f611b6..cea8e3d4f 100644 --- a/libsolidity/formal/BMC.cpp +++ b/libsolidity/formal/BMC.cpp @@ -27,19 +27,25 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; -BMC::BMC(smt::EncodingContext& _context, ErrorReporter& _errorReporter, map const& _smtlib2Responses): +BMC::BMC( + smt::EncodingContext& _context, + ErrorReporter& _errorReporter, + map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers +): SMTEncoder(_context), m_outerErrorReporter(_errorReporter), - m_interface(make_shared(_smtlib2Responses)) + m_interface(make_shared(_smtlib2Responses, _enabledSolvers)) { #if defined (HAVE_Z3) || defined (HAVE_CVC4) - if (!_smtlib2Responses.empty()) - m_errorReporter.warning( - "SMT-LIB2 query responses were given in the auxiliary input, " - "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." - "These responses will be ignored." - "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." - ); + if (_enabledSolvers.some()) + if (!_smtlib2Responses.empty()) + m_errorReporter.warning( + "SMT-LIB2 query responses were given in the auxiliary input, " + "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." + "These responses will be ignored." + "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." + ); #endif } @@ -110,11 +116,6 @@ bool BMC::visit(ContractDefinition const& _contract) { initContract(_contract); - /// Check targets created by state variable initialization. - smt::Expression constraints = m_context.assertions(); - checkVerificationTargets(constraints); - m_verificationTargets.clear(); - SMTEncoder::visit(_contract); return false; @@ -122,6 +123,17 @@ bool BMC::visit(ContractDefinition const& _contract) void BMC::endVisit(ContractDefinition const& _contract) { + if (auto constructor = _contract.constructor()) + constructor->accept(*this); + else + { + inlineConstructorHierarchy(_contract); + /// Check targets created by state variable initialization. + smt::Expression constraints = m_context.assertions(); + checkVerificationTargets(constraints); + m_verificationTargets.clear(); + } + SMTEncoder::endVisit(_contract); } @@ -132,10 +144,14 @@ bool BMC::visit(FunctionDefinition const& _function) solAssert(m_currentContract, ""); auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; if (find(hierarchy.begin(), hierarchy.end(), contract) == hierarchy.end()) - initializeStateVariables(*contract); + createStateVariables(*contract); if (m_callStack.empty()) + { reset(); + initFunction(_function); + resetStateVariables(); + } /// Already visits the children. SMTEncoder::visit(_function); @@ -447,10 +463,6 @@ void BMC::inlineFunctionCall(FunctionCall const& _funCall) // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) // is that there we don't have `_funCall`. pushCallStack({funDef, &_funCall}); - // If an internal function is called to initialize - // a state variable. - if (m_callStack.empty()) - initFunction(*funDef); funDef->accept(*this); } diff --git a/libsolidity/formal/BMC.h b/libsolidity/formal/BMC.h index 432050e78..01ef36369 100644 --- a/libsolidity/formal/BMC.h +++ b/libsolidity/formal/BMC.h @@ -53,7 +53,12 @@ namespace solidity class BMC: public SMTEncoder { public: - BMC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + BMC( + smt::EncodingContext& _context, + langutil::ErrorReporter& _errorReporter, + std::map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers + ); void analyze(SourceUnit const& _sources, std::set _safeAssertions); diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index a917faf82..5e9495192 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -35,17 +35,23 @@ using namespace dev::solidity; CHC::CHC( smt::EncodingContext& _context, ErrorReporter& _errorReporter, - map const& _smtlib2Responses + map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers ): SMTEncoder(_context), #ifdef HAVE_Z3 - m_interface(make_shared()), + m_interface( + _enabledSolvers.z3 ? + dynamic_pointer_cast(make_shared()) : + dynamic_pointer_cast(make_shared(_smtlib2Responses)) + ), #else m_interface(make_shared(_smtlib2Responses)), #endif m_outerErrorReporter(_errorReporter) { (void)_smtlib2Responses; + (void)_enabledSolvers; } void CHC::analyze(SourceUnit const& _source) @@ -65,6 +71,15 @@ void CHC::analyze(SourceUnit const& _source) m_context.setAssertionAccumulation(false); m_variableUsage.setFunctionInlining(false); + auto boolSort = make_shared(smt::Kind::Bool); + auto genesisSort = make_shared( + vector(), + boolSort + ); + m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis"); + auto genesis = (*m_genesisPredicate)({}); + addRule(genesis, genesis.name); + _source.accept(*this); } @@ -94,10 +109,10 @@ bool CHC::visit(ContractDefinition const& _contract) else m_stateSorts.push_back(smt::smtSort(*var->type())); - clearIndices(); + clearIndices(&_contract); - string interfaceName = "interface_" + _contract.name() + "_" + to_string(_contract.id()); - m_interfacePredicate = createSymbolicBlock(interfaceSort(), interfaceName); + string suffix = _contract.name() + "_" + to_string(_contract.id()); + m_interfacePredicate = createSymbolicBlock(interfaceSort(), "interface_" + suffix); // TODO create static instances for Bool/Int sorts in SolverInterface. auto boolSort = make_shared(smt::Kind::Bool); @@ -105,27 +120,11 @@ bool CHC::visit(ContractDefinition const& _contract) vector(), boolSort ); - m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error"); - // If the contract has a constructor it is handled as a function. - // Otherwise we zero-initialize all state vars. - if (!_contract.constructor()) - { - string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id()); - m_constructorPredicate = createSymbolicBlock(constructorSort(), constructorName); - smt::Expression constructorPred = (*m_constructorPredicate)({}); - addRule(constructorPred, constructorName); - - for (auto const& var: m_stateVariables) - { - auto const& symbVar = m_context.variable(*var); - symbVar->increaseIndex(); - m_interface->declareVariable(symbVar->currentName(), symbVar->sort()); - m_context.setZeroValue(*symbVar); - } - - connectBlocks(constructorPred, interface()); - } + m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix); + m_constructorPredicate = createSymbolicBlock(constructorSort(), "implicit_constructor_" + to_string(_contract.id())); + auto stateExprs = currentStateVariables(); + setCurrentBlock(*m_interfacePredicate, &stateExprs); SMTEncoder::visit(_contract); return false; @@ -136,6 +135,23 @@ void CHC::endVisit(ContractDefinition const& _contract) if (!shouldVisit(_contract)) return; + for (auto const& var: m_stateVariables) + { + solAssert(m_context.knownVariable(*var), ""); + m_context.setZeroValue(*var); + } + auto genesisPred = (*m_genesisPredicate)({}); + auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables()); + connectBlocks(genesisPred, implicitConstructor); + m_currentBlock = implicitConstructor; + + if (auto constructor = _contract.constructor()) + constructor->accept(*this); + else + inlineConstructorHierarchy(_contract); + + connectBlocks(m_currentBlock, interface()); + for (unsigned i = 0; i < m_verificationTargets.size(); ++i) { auto const& target = m_verificationTargets.at(i); @@ -152,6 +168,16 @@ bool CHC::visit(FunctionDefinition const& _function) if (!shouldVisit(_function)) return false; + // This is the case for base constructor inlining. + if (m_currentFunction) + { + solAssert(m_currentFunction->isConstructor(), ""); + solAssert(_function.isConstructor(), ""); + solAssert(_function.scope() != m_currentContract, ""); + SMTEncoder::visit(_function); + return false; + } + solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented"); m_currentFunction = &_function; @@ -163,20 +189,11 @@ bool CHC::visit(FunctionDefinition const& _function) auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables()); auto bodyPred = predicate(*bodyBlock); - // Store the constraints related to variable initialization. - smt::Expression const& initAssertions = m_context.assertions(); - m_context.pushSolver(); - - connectBlocks(interface(), functionPred); + connectBlocks(m_currentBlock, functionPred); connectBlocks(functionPred, bodyPred); - m_context.popSolver(); - setCurrentBlock(*bodyBlock); - // We need to re-add the constraints that were created for initialization of variables. - m_context.addAssertion(initAssertions); - SMTEncoder::visit(*m_currentFunction); return false; @@ -187,10 +204,39 @@ void CHC::endVisit(FunctionDefinition const& _function) if (!shouldVisit(_function)) return; - connectBlocks(m_currentBlock, interface()); + // This is the case for base constructor inlining. + if (m_currentFunction != &_function) + { + solAssert(m_currentFunction && m_currentFunction->isConstructor(), ""); + solAssert(_function.isConstructor(), ""); + solAssert(_function.scope() != m_currentContract, ""); + } + else + { + // We create an extra exit block for constructors that simply + // connects to the interface in case an explicit constructor + // exists in the hierarchy. + // It is not connected directly here, as normal functions are, + // because of the case where there are only implicit constructors. + // This is done in endVisit(ContractDefinition). + if (_function.isConstructor()) + { + auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id())); + connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables())); + clearIndices(m_currentContract, m_currentFunction); + auto stateExprs = currentStateVariables(); + setCurrentBlock(*constructorExit, &stateExprs); + } + else + { + connectBlocks(m_currentBlock, interface()); + clearIndices(m_currentContract, m_currentFunction); + auto stateExprs = currentStateVariables(); + setCurrentBlock(*m_interfacePredicate, &stateExprs); + } + m_currentFunction = nullptr; + } - solAssert(&_function == m_currentFunction, ""); - m_currentFunction = nullptr; SMTEncoder::endVisit(_function); } @@ -445,7 +491,6 @@ void CHC::reset() m_verificationTargets.clear(); m_safeAssertions.clear(); m_unknownFunctionCallSeen = false; - m_blockCounter = 0; m_breakDest = nullptr; m_continueDest = nullptr; } @@ -470,28 +515,31 @@ bool CHC::shouldVisit(FunctionDefinition const& _function) const { if ( _function.isPublic() && - _function.isImplemented() && - !_function.isConstructor() + _function.isImplemented() ) return true; return false; } -void CHC::setCurrentBlock(smt::SymbolicFunctionVariable const& _block) +void CHC::setCurrentBlock( + smt::SymbolicFunctionVariable const& _block, + vector const* _arguments +) { m_context.popSolver(); - clearIndices(); + solAssert(m_currentContract, ""); + clearIndices(m_currentContract, m_currentFunction); m_context.pushSolver(); - m_currentBlock = predicate(_block); + if (_arguments) + m_currentBlock = predicate(_block, *_arguments); + else + m_currentBlock = predicate(_block); } smt::SortPointer CHC::constructorSort() { - solAssert(m_currentContract, ""); - auto boolSort = make_shared(smt::Kind::Bool); - if (!m_currentContract->constructor()) - return make_shared(vector{}, boolSort); - return sort(*m_currentContract->constructor()); + // TODO this will change once we support function calls. + return interfaceSort(); } smt::SortPointer CHC::interfaceSort() @@ -556,19 +604,6 @@ unique_ptr CHC::createSymbolicBlock(smt::SortPoin return block; } -smt::Expression CHC::constructor() -{ - solAssert(m_currentContract, ""); - - if (!m_currentContract->constructor()) - return (*m_constructorPredicate)({}); - - vector paramExprs; - for (auto const& var: m_currentContract->constructor()->parameters()) - paramExprs.push_back(m_context.variable(*var)->currentValue()); - return (*m_constructorPredicate)(paramExprs); -} - smt::Expression CHC::interface() { vector paramExprs; @@ -613,37 +648,31 @@ void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to addRule(edge, _from.name + "_to_" + _to.name); } +vector CHC::currentStateVariables() +{ + solAssert(m_currentContract, ""); + vector exprs; + for (auto const& var: m_stateVariables) + exprs.push_back(m_context.variable(*var)->currentValue()); + return exprs; +} + vector CHC::currentFunctionVariables() { - solAssert(m_currentFunction, ""); vector paramExprs; - for (auto const& var: m_stateVariables) - paramExprs.push_back(m_context.variable(*var)->currentValue()); - for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) - paramExprs.push_back(m_context.variable(*var)->currentValue()); - return paramExprs; + if (m_currentFunction) + for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return currentStateVariables() + paramExprs; } vector CHC::currentBlockVariables() { - solAssert(m_currentFunction, ""); vector paramExprs; - for (auto const& var: m_currentFunction->localVariables()) - paramExprs.push_back(m_context.variable(*var)->currentValue()); - return currentFunctionVariables() + paramExprs; -} - -void CHC::clearIndices() -{ - for (auto const& var: m_stateVariables) - m_context.variable(*var)->resetIndex(); if (m_currentFunction) - { - for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) - m_context.variable(*var)->resetIndex(); for (auto const& var: m_currentFunction->localVariables()) - m_context.variable(*var)->resetIndex(); - } + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return currentFunctionVariables() + paramExprs; } string CHC::predicateName(ASTNode const* _node) @@ -674,7 +703,6 @@ smt::Expression CHC::predicate( return _block(_arguments); } - void CHC::addRule(smt::Expression const& _rule, string const& _ruleName) { m_interface->addRule(_rule, _ruleName); diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h index a9fb9f2b7..a5ab9e06b 100644 --- a/libsolidity/formal/CHC.h +++ b/libsolidity/formal/CHC.h @@ -47,7 +47,8 @@ public: CHC( smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, - std::map const& _smtlib2Responses + std::map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers ); void analyze(SourceUnit const& _sources); @@ -83,7 +84,7 @@ private: void eraseKnowledge(); bool shouldVisit(ContractDefinition const& _contract) const; bool shouldVisit(FunctionDefinition const& _function) const; - void setCurrentBlock(smt::SymbolicFunctionVariable const& _block); + void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector const* _arguments = nullptr); //@} /// Sort helpers. @@ -99,8 +100,6 @@ private: /// @returns a new block of given _sort and _name. std::unique_ptr createSymbolicBlock(smt::SortPointer _sort, std::string const& _name); - /// Constructor predicate over current variables. - smt::Expression constructor(); /// Interface predicate over current variables. smt::Expression interface(); /// Error predicate over current variables. @@ -116,17 +115,16 @@ private: void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true)); + /// @returns the current symbolic values of the current state variables. + std::vector currentStateVariables(); + /// @returns the current symbolic values of the current function's /// input and output parameters. std::vector currentFunctionVariables(); - /// @returns the samve as currentFunctionVariables plus + /// @returns the same as currentFunctionVariables plus /// local variables. std::vector currentBlockVariables(); - /// Sets the SSA indices of the variables in scope to 0. - /// Used when starting a new block. - void clearIndices(); - /// @returns the predicate name for a given node. std::string predicateName(ASTNode const* _node); /// @returns a predicate application over the current scoped variables. @@ -152,8 +150,11 @@ private: /// Predicates. //@{ - /// Constructor predicate. - /// Default constructor sets state vars to 0. + /// Genesis predicate. + std::unique_ptr m_genesisPredicate; + + /// Implicit constructor predicate. + /// Explicit constructors are handled as functions. std::unique_ptr m_constructorPredicate; /// Artificial Interface predicate. diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 377df32d2..03c12cf08 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -35,7 +35,7 @@ void CVC4Interface::reset() m_variables.clear(); m_solver.reset(); m_solver.setOption("produce-models", true); - m_solver.setTimeLimit(queryTimeout); + m_solver.setResourceLimit(resourceLimit); } void CVC4Interface::push() diff --git a/libsolidity/formal/CVC4Interface.h b/libsolidity/formal/CVC4Interface.h index 28596bdcf..76318fdee 100644 --- a/libsolidity/formal/CVC4Interface.h +++ b/libsolidity/formal/CVC4Interface.h @@ -63,6 +63,12 @@ private: CVC4::ExprManager m_context; CVC4::SmtEngine m_solver; std::map m_variables; + + // CVC4 "basic resources" limit. + // This is used to make the runs more deterministic and platform/machine independent. + // The tests start failing for CVC4 with less than 6000, + // so using double that. + static int const resourceLimit = 12000; }; } diff --git a/libsolidity/formal/ModelChecker.cpp b/libsolidity/formal/ModelChecker.cpp index c061c8416..508509805 100644 --- a/libsolidity/formal/ModelChecker.cpp +++ b/libsolidity/formal/ModelChecker.cpp @@ -22,9 +22,13 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; -ModelChecker::ModelChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): - m_bmc(m_context, _errorReporter, _smtlib2Responses), - m_chc(m_context, _errorReporter, _smtlib2Responses), +ModelChecker::ModelChecker( + ErrorReporter& _errorReporter, + map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers +): + m_bmc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers), + m_chc(m_context, _errorReporter, _smtlib2Responses, _enabledSolvers), m_context() { } diff --git a/libsolidity/formal/ModelChecker.h b/libsolidity/formal/ModelChecker.h index 349ed76d4..d7ec7c1b3 100644 --- a/libsolidity/formal/ModelChecker.h +++ b/libsolidity/formal/ModelChecker.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -43,7 +44,13 @@ namespace solidity class ModelChecker { public: - ModelChecker(langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + /// @param _enabledSolvers represents a runtime choice of which SMT solvers + /// should be used, even if all are available. The default choice is to use all. + ModelChecker( + langutil::ErrorReporter& _errorReporter, + std::map const& _smtlib2Responses, + smt::SMTSolverChoice _enabledSolvers = smt::SMTSolverChoice::All() + ); void analyze(SourceUnit const& _sources); diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 98d6519fb..0f04f412b 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -22,6 +22,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -39,11 +40,28 @@ bool SMTEncoder::visit(ContractDefinition const& _contract) solAssert(m_currentContract, ""); for (auto const& node: _contract.subNodes()) - if (!dynamic_pointer_cast(node)) + if ( + !dynamic_pointer_cast(node) && + !dynamic_pointer_cast(node) + ) node->accept(*this); vector resolvedFunctions = _contract.definedFunctions(); for (auto const& base: _contract.annotation().linearizedBaseContracts) + { + // Look for all the constructor invocations bottom up. + if (auto const& constructor = base->constructor()) + for (auto const& invocation: constructor->modifiers()) + { + auto refDecl = invocation->name()->annotation().referencedDeclaration; + if (auto const& baseContract = dynamic_cast(refDecl)) + { + solAssert(!m_baseConstructorCalls.count(baseContract), ""); + m_baseConstructorCalls[baseContract] = invocation.get(); + } + } + + // Check for function overrides. for (auto const& baseFunction: base->definedFunctions()) { if (baseFunction->isConstructor()) @@ -62,9 +80,18 @@ bool SMTEncoder::visit(ContractDefinition const& _contract) if (!overridden) resolvedFunctions.push_back(baseFunction); } + } + // Functions are visited first since they might be used + // for state variable initialization which is part of + // the constructor. + // Constructors are visited as part of the constructor + // hierarchy inlining. for (auto const& function: resolvedFunctions) - function->accept(*this); + if (!function->isConstructor()) + function->accept(*this); + + // Constructors need to be handled by the engines separately. return false; } @@ -73,13 +100,16 @@ void SMTEncoder::endVisit(ContractDefinition const& _contract) { m_context.resetAllVariables(); + m_baseConstructorCalls.clear(); + solAssert(m_currentContract == &_contract, ""); m_currentContract = nullptr; } void SMTEncoder::endVisit(VariableDeclaration const& _varDecl) { - if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) + // State variables are handled by the constructor. + if (_varDecl.isLocalVariable() &&_varDecl.value()) assignment(_varDecl, *_varDecl.value()); } @@ -90,25 +120,22 @@ bool SMTEncoder::visit(ModifierDefinition const&) bool SMTEncoder::visit(FunctionDefinition const& _function) { - // Not visited by a function call - if (m_callStack.empty()) - initFunction(_function); - m_modifierDepthStack.push_back(-1); + if (_function.isConstructor()) - { - m_errorReporter.warning( - _function.location(), - "Assertion checker does not yet support constructors." - ); - } - else - { - _function.parameterList().accept(*this); - if (_function.returnParameterList()) - _function.returnParameterList()->accept(*this); - visitFunctionOrModifier(); - } + inlineConstructorHierarchy(dynamic_cast(*_function.scope())); + + // Base constructors' parameters should be set by explicit calls, + // but the most derived one needs to be initialized. + if (_function.scope() == m_currentContract) + initializeLocalVariables(_function); + + _function.parameterList().accept(*this); + if (_function.returnParameterList()) + _function.returnParameterList()->accept(*this); + + visitFunctionOrModifier(); + return false; } @@ -130,27 +157,87 @@ void SMTEncoder::visitFunctionOrModifier() solAssert(m_modifierDepthStack.back() < int(function.modifiers().size()), ""); ASTPointer const& modifierInvocation = function.modifiers()[m_modifierDepthStack.back()]; solAssert(modifierInvocation, ""); - modifierInvocation->accept(*this); - auto const& modifierDef = dynamic_cast( - *modifierInvocation->name()->annotation().referencedDeclaration - ); - vector modifierArgsExpr; - if (auto const* arguments = modifierInvocation->arguments()) - { - auto const& modifierParams = modifierDef.parameters(); - solAssert(modifierParams.size() == arguments->size(), ""); - for (unsigned i = 0; i < arguments->size(); ++i) - modifierArgsExpr.push_back(expr(*arguments->at(i), modifierParams.at(i)->type())); - } - initializeFunctionCallParameters(modifierDef, modifierArgsExpr); - pushCallStack({&modifierDef, modifierInvocation.get()}); - modifierDef.body().accept(*this); - popCallStack(); + auto refDecl = modifierInvocation->name()->annotation().referencedDeclaration; + if (dynamic_cast(refDecl)) + visitFunctionOrModifier(); + else if (auto modifierDef = dynamic_cast(refDecl)) + inlineModifierInvocation(modifierInvocation.get(), modifierDef); + else + solAssert(false, ""); } --m_modifierDepthStack.back(); } +void SMTEncoder::inlineModifierInvocation(ModifierInvocation const* _invocation, CallableDeclaration const* _definition) +{ + solAssert(_invocation, ""); + _invocation->accept(*this); + + vector args; + if (auto const* arguments = _invocation->arguments()) + { + auto const& modifierParams = _definition->parameters(); + solAssert(modifierParams.size() == arguments->size(), ""); + for (unsigned i = 0; i < arguments->size(); ++i) + args.push_back(expr(*arguments->at(i), modifierParams.at(i)->type())); + } + + initializeFunctionCallParameters(*_definition, args); + + pushCallStack({_definition, _invocation}); + if (auto modifier = dynamic_cast(_definition)) + { + modifier->body().accept(*this); + popCallStack(); + } + else if (auto function = dynamic_cast(_definition)) + { + if (function->isImplemented()) + function->accept(*this); + // Functions are popped from the callstack in endVisit(FunctionDefinition) + } +} + +void SMTEncoder::inlineConstructorHierarchy(ContractDefinition const& _contract) +{ + auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; + auto it = find(begin(hierarchy), end(hierarchy), &_contract); + solAssert(it != end(hierarchy), ""); + + auto nextBase = it + 1; + // Initialize the base contracts here as long as their constructors are implicit, + // stop when the first explicit constructor is found. + while (nextBase != end(hierarchy)) + { + if (auto baseConstructor = (*nextBase)->constructor()) + { + createLocalVariables(*baseConstructor); + // If any subcontract explicitly called baseConstructor, use those arguments. + if (m_baseConstructorCalls.count(*nextBase)) + inlineModifierInvocation(m_baseConstructorCalls.at(*nextBase), baseConstructor); + else if (baseConstructor->isImplemented()) + { + // The first constructor found is handled like a function + // and its pushed into the callstack there. + // This if avoids duplication in the callstack. + if (!m_callStack.empty()) + pushCallStack({baseConstructor, nullptr}); + baseConstructor->accept(*this); + // popped by endVisit(FunctionDefinition) + } + break; + } + else + { + initializeStateVariables(**nextBase); + ++nextBase; + } + } + + initializeStateVariables(_contract); +} + bool SMTEncoder::visit(PlaceholderStatement const&) { solAssert(!m_callStack.empty(), ""); @@ -208,17 +295,14 @@ void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl) solAssert(symbTuple, ""); auto const& components = symbTuple->components(); auto const& declarations = _varDecl.declarations(); - if (!components.empty()) - { - solAssert(components.size() == declarations.size(), ""); - for (unsigned i = 0; i < declarations.size(); ++i) - if ( - components.at(i) && - declarations.at(i) && - m_context.knownVariable(*declarations.at(i)) - ) - assignment(*declarations.at(i), components.at(i)->currentValue(declarations.at(i)->type())); - } + solAssert(components.size() == declarations.size(), ""); + for (unsigned i = 0; i < declarations.size(); ++i) + if ( + components.at(i) && + declarations.at(i) && + m_context.knownVariable(*declarations.at(i)) + ) + assignment(*declarations.at(i), components.at(i)->currentValue(declarations.at(i)->type())); } } else if (m_context.knownVariable(*_varDecl.declarations().front())) @@ -320,24 +404,23 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple) { auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_tuple)); solAssert(symbTuple, ""); - if (symbTuple->components().empty()) + auto const& symbComponents = symbTuple->components(); + auto const& tupleComponents = _tuple.components(); + solAssert(symbComponents.size() == _tuple.components().size(), ""); + for (unsigned i = 0; i < symbComponents.size(); ++i) { - vector> components; - for (auto const& component: _tuple.components()) - if (component) - { - if (auto varDecl = identifierToVariable(*component)) - components.push_back(m_context.variable(*varDecl)); - else - { - solAssert(m_context.knownExpression(*component), ""); - components.push_back(m_context.expression(*component)); - } - } + auto sComponent = symbComponents.at(i); + auto tComponent = tupleComponents.at(i); + if (sComponent && tComponent) + { + if (auto varDecl = identifierToVariable(*tComponent)) + m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl)); else - components.push_back(nullptr); - solAssert(components.size() == _tuple.components().size(), ""); - symbTuple->setComponents(move(components)); + { + solAssert(m_context.knownExpression(*tComponent), ""); + m_context.addAssertion(sComponent->currentValue() == expr(*tComponent)); + } + } } } else @@ -541,25 +624,39 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) } } +bool SMTEncoder::visit(ModifierInvocation const& _node) +{ + if (auto const* args = _node.arguments()) + for (auto const& arg: *args) + if (arg) + arg->accept(*this); + return false; +} + void SMTEncoder::initContract(ContractDefinition const& _contract) { solAssert(m_currentContract == nullptr, ""); m_currentContract = &_contract; - initializeStateVariables(_contract); + m_context.reset(); + m_context.pushSolver(); + createStateVariables(_contract); + clearIndices(m_currentContract, nullptr); } void SMTEncoder::initFunction(FunctionDefinition const& _function) { solAssert(m_callStack.empty(), ""); + solAssert(m_currentContract, ""); m_context.reset(); m_context.pushSolver(); m_pathConditions.clear(); pushCallStack({&_function, nullptr}); m_uninterpretedTerms.clear(); - resetStateVariables(); - initializeLocalVariables(_function); + createStateVariables(*m_currentContract); + createLocalVariables(_function); m_arrayAssignmentHappened = false; + clearIndices(m_currentContract, &_function); } void SMTEncoder::visitAssert(FunctionCall const& _funCall) @@ -609,12 +706,20 @@ void SMTEncoder::endVisit(Identifier const& _identifier) defineExpr(_identifier, m_context.thisAddress()); m_uninterpretedTerms.insert(&_identifier); } - else if (smt::isSupportedType(_identifier.annotation().type->category())) - // TODO: handle MagicVariableDeclaration here - m_errorReporter.warning( - _identifier.location(), - "Assertion checker does not yet support the type of this variable." - ); + else + createExpr(_identifier); +} + +void SMTEncoder::endVisit(ElementaryTypeNameExpression const& _typeName) +{ + auto const& typeType = dynamic_cast(*_typeName.annotation().type); + auto result = smt::newSymbolicVariable( + *TypeProvider::uint256(), + typeType.actualType()->toString(false), + m_context + ); + solAssert(!result.first && result.second, ""); + m_context.createExpression(_typeName, result.second); } void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall) @@ -764,8 +869,11 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) { createExpr(_indexAccess); + if (_indexAccess.annotation().type->category() == Type::Category::TypeType) + return; + shared_ptr array; - if (auto const& id = dynamic_cast(&_indexAccess.baseExpression())) + if (auto const* id = dynamic_cast(&_indexAccess.baseExpression())) { auto varDecl = identifierToVariable(*id); solAssert(varDecl, ""); @@ -780,7 +888,7 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) return; } } - else if (auto const& innerAccess = dynamic_cast(&_indexAccess.baseExpression())) + else if (auto const* innerAccess = dynamic_cast(&_indexAccess.baseExpression())) { solAssert(m_context.knownExpression(*innerAccess), ""); array = m_context.expression(*innerAccess); @@ -794,12 +902,6 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess) return; } - if (!_indexAccess.indexExpression()) - { - solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, ""); - return; - } - solAssert(array, ""); defineExpr(_indexAccess, smt::Expression::select( array->currentValue(), @@ -1226,26 +1328,61 @@ void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _fu } } -void SMTEncoder::initializeStateVariables(ContractDefinition const& _contract) +void SMTEncoder::createStateVariables(ContractDefinition const& _contract) { for (auto var: _contract.stateVariablesIncludingInherited()) createVariable(*var); } -void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function) +void SMTEncoder::initializeStateVariables(ContractDefinition const& _contract) +{ + for (auto var: _contract.stateVariables()) + { + solAssert(m_context.knownVariable(*var), ""); + m_context.setZeroValue(*var); + } + + for (auto var: _contract.stateVariables()) + if (var->value()) + { + var->value()->accept(*this); + assignment(*var, *var->value()); + } +} + +void SMTEncoder::createLocalVariables(FunctionDefinition const& _function) { for (auto const& variable: _function.localVariables()) - if (createVariable(*variable)) - m_context.setZeroValue(*variable); + createVariable(*variable); for (auto const& param: _function.parameters()) - if (createVariable(*param)) - m_context.setUnknownValue(*param); + createVariable(*param); if (_function.returnParameterList()) for (auto const& retParam: _function.returnParameters()) - if (createVariable(*retParam)) - m_context.setZeroValue(*retParam); + createVariable(*retParam); +} + +void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function) +{ + for (auto const& variable: _function.localVariables()) + { + solAssert(m_context.knownVariable(*variable), ""); + m_context.setZeroValue(*variable); + } + + for (auto const& param: _function.parameters()) + { + solAssert(m_context.knownVariable(*param), ""); + m_context.setUnknownValue(*param); + } + + if (_function.returnParameterList()) + for (auto const& retParam: _function.returnParameters()) + { + solAssert(m_context.knownVariable(*retParam), ""); + m_context.setZeroValue(*retParam); + } } void SMTEncoder::resetStateVariables() @@ -1425,6 +1562,20 @@ void SMTEncoder::resetVariableIndices(VariableIndices const& _indices) m_context.variable(*var.first)->index() = var.second; } +void SMTEncoder::clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function) +{ + solAssert(_contract, ""); + for (auto var: _contract->stateVariablesIncludingInherited()) + m_context.variable(*var)->resetIndex(); + if (_function) + { + for (auto const& var: _function->parameters() + _function->returnParameters()) + m_context.variable(*var)->resetIndex(); + for (auto const& var: _function->localVariables()) + m_context.variable(*var)->resetIndex(); + } +} + Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess) { Expression const* base = &_indexAccess.baseExpression(); @@ -1503,15 +1654,18 @@ void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall) { auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_funCall)); solAssert(symbTuple, ""); - if (symbTuple->components().empty()) + auto const& symbComponents = symbTuple->components(); + solAssert(symbComponents.size() == returnParams.size(), ""); + for (unsigned i = 0; i < symbComponents.size(); ++i) { - vector> components; - for (auto param: returnParams) + auto sComponent = symbComponents.at(i); + auto param = returnParams.at(i); + solAssert(param, ""); + if (sComponent) { solAssert(m_context.knownVariable(*param), ""); - components.push_back(m_context.variable(*param)); + m_context.addAssertion(sComponent->currentValue() == currentValue(*param)); } - symbTuple->setComponents(move(components)); } } else if (returnParams.size() == 1) diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 37a7cea24..8931e8dcf 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -81,7 +82,9 @@ protected: bool visit(BinaryOperation const& _node) override; void endVisit(BinaryOperation const& _node) override; void endVisit(FunctionCall const& _node) override; + bool visit(ModifierInvocation const& _node) override; void endVisit(Identifier const& _node) override; + void endVisit(ElementaryTypeNameExpression const& _node) override; void endVisit(Literal const& _node) override; void endVisit(Return const& _node) override; bool visit(MemberAccess const& _node) override; @@ -119,6 +122,12 @@ protected: /// visit depth. void visitFunctionOrModifier(); + /// Inlines a modifier or base constructor call. + void inlineModifierInvocation(ModifierInvocation const* _invocation, CallableDeclaration const* _definition); + + /// Inlines the constructor hierarchy into a single constructor. + void inlineConstructorHierarchy(ContractDefinition const& _contract); + /// Defines a new global variable or function. void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); @@ -158,7 +167,9 @@ protected: using CallStackEntry = std::pair; + void createStateVariables(ContractDefinition const& _contract); void initializeStateVariables(ContractDefinition const& _contract); + void createLocalVariables(FunctionDefinition const& _function); void initializeLocalVariables(FunctionDefinition const& _function); void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector const& _callArgs); void resetStateVariables(); @@ -206,6 +217,9 @@ protected: VariableIndices copyVariableIndices(); /// Resets the variable indices. void resetVariableIndices(VariableIndices const& _indices); + /// Used when starting a new block. + void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr); + /// @returns variables that are touched in _node's subtree. std::set touchedVariables(ASTNode const& _node); @@ -250,6 +264,8 @@ protected: /// Needs to be a stack because of function calls. std::vector m_modifierDepthStack; + std::map m_baseConstructorCalls; + ContractDefinition const* m_currentContract = nullptr; /// Stores the context of the encoding. diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index d47c01f31..9b9795e9a 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -30,15 +30,21 @@ using namespace dev; using namespace dev::solidity; using namespace dev::solidity::smt; -SMTPortfolio::SMTPortfolio(map const& _smtlib2Responses) +SMTPortfolio::SMTPortfolio( + map const& _smtlib2Responses, + SMTSolverChoice _enabledSolvers +) { m_solvers.emplace_back(make_unique(_smtlib2Responses)); #ifdef HAVE_Z3 - m_solvers.emplace_back(make_unique()); + if (_enabledSolvers.z3) + m_solvers.emplace_back(make_unique()); #endif #ifdef HAVE_CVC4 - m_solvers.emplace_back(make_unique()); + if (_enabledSolvers.cvc4) + m_solvers.emplace_back(make_unique()); #endif + (void)_enabledSolvers; } void SMTPortfolio::reset() diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index d922affd2..41472c187 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -42,7 +42,10 @@ namespace smt class SMTPortfolio: public SolverInterface, public boost::noncopyable { public: - SMTPortfolio(std::map const& _smtlib2Responses); + SMTPortfolio( + std::map const& _smtlib2Responses, + SMTSolverChoice _enabledSolvers + ); void reset() override; diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 2889af25a..81f6ea497 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -36,6 +36,21 @@ namespace solidity namespace smt { +struct SMTSolverChoice +{ + bool cvc4 = false; + bool z3 = false; + + static constexpr SMTSolverChoice All() { return {true, true}; } + static constexpr SMTSolverChoice CVC4() { return {true, false}; } + static constexpr SMTSolverChoice Z3() { return {false, true}; } + static constexpr SMTSolverChoice None() { return {false, false}; } + + bool none() { return !some(); } + bool some() { return cvc4 || z3; } + bool all() { return cvc4 && z3; } +}; + enum class CheckResult { SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR @@ -359,10 +374,6 @@ public: /// @returns how many SMT solvers this interface has. virtual unsigned solvers() { return 1; } - -protected: - // SMT query timeout in milliseconds. - static int const queryTimeout = 10000; }; } diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 591158a25..0fe05e723 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -63,7 +63,7 @@ SortPointer smtSort(solidity::Type const& _type) { auto mapType = dynamic_cast(&_type); solAssert(mapType, ""); - return make_shared(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); + return make_shared(smtSortAbstractFunction(*mapType->keyType()), smtSortAbstractFunction(*mapType->valueType())); } else if (isStringLiteral(_type.category())) { @@ -77,7 +77,7 @@ SortPointer smtSort(solidity::Type const& _type) solAssert(isArray(_type.category()), ""); auto arrayType = dynamic_cast(&_type); solAssert(arrayType, ""); - return make_shared(make_shared(Kind::Int), smtSort(*arrayType->baseType())); + return make_shared(make_shared(Kind::Int), smtSortAbstractFunction(*arrayType->baseType())); } } default: @@ -94,6 +94,13 @@ vector smtSort(vector const& _types) return sorts; } +SortPointer smtSortAbstractFunction(solidity::Type const& _type) +{ + if (isFunction(_type.category())) + return make_shared(Kind::Int); + return smtSort(_type); +} + Kind smtKind(solidity::Type::Category _category) { if (isNumber(_category)) diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 30a341431..2db2807ac 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -32,6 +32,9 @@ namespace smt /// Returns the SMT sort that models the Solidity type _type. SortPointer smtSort(solidity::Type const& _type); std::vector smtSort(std::vector const& _types); +/// If _type has type Function, abstract it to Integer. +/// Otherwise return smtSort(_type). +SortPointer smtSortAbstractFunction(solidity::Type const& _type); /// Returns the SMT kind that models the Solidity type type category _category. Kind smtKind(solidity::Type::Category _category); diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index 3a814ec7d..d0163d381 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -248,12 +248,16 @@ SymbolicTupleVariable::SymbolicTupleVariable( SymbolicVariable(_type, _type, move(_uniqueName), _context) { solAssert(isTuple(m_type->category()), ""); -} - -void SymbolicTupleVariable::setComponents(vector> _components) -{ - solAssert(m_components.empty(), ""); - auto const& tupleType = dynamic_cast(m_type); - solAssert(_components.size() == tupleType->components().size(), ""); - m_components = move(_components); + auto const& tupleType = dynamic_cast(*m_type); + auto const& componentsTypes = tupleType.components(); + for (unsigned i = 0; i < componentsTypes.size(); ++i) + if (componentsTypes.at(i)) + { + string componentName = m_uniqueName + "_component_" + to_string(i); + auto result = smt::newSymbolicVariable(*componentsTypes.at(i), componentName, m_context); + solAssert(result.second, ""); + m_components.emplace_back(move(result.second)); + } + else + m_components.emplace_back(nullptr); } diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index 9ae637490..4d9fcc2f7 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -250,8 +250,6 @@ public: return m_components; } - void setComponents(std::vector> _components); - private: std::vector> m_components; }; diff --git a/libsolidity/formal/Z3CHCInterface.cpp b/libsolidity/formal/Z3CHCInterface.cpp index 87d0db0c4..d4c53f850 100644 --- a/libsolidity/formal/Z3CHCInterface.cpp +++ b/libsolidity/formal/Z3CHCInterface.cpp @@ -29,10 +29,9 @@ Z3CHCInterface::Z3CHCInterface(): m_context(m_z3Interface->context()), m_solver(*m_context) { - // This needs to be set globally. + // These need to be set globally. z3::set_param("rewriter.pull_cheap_ite", true); - // This needs to be set in the context. - m_context->set("timeout", queryTimeout); + z3::set_param("rlimit", Z3Interface::resourceLimit); // Spacer options. // These needs to be set in the solver. diff --git a/libsolidity/formal/Z3CHCInterface.h b/libsolidity/formal/Z3CHCInterface.h index 3acab97c3..858e4f40f 100644 --- a/libsolidity/formal/Z3CHCInterface.h +++ b/libsolidity/formal/Z3CHCInterface.h @@ -54,9 +54,6 @@ private: z3::context* m_context; // Horn solver. z3::fixedpoint m_solver; - - // SMT query timeout in milliseconds. - static int const queryTimeout = 10000; }; } diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index aa20eb533..74b3cfe2b 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -27,10 +27,9 @@ using namespace dev::solidity::smt; Z3Interface::Z3Interface(): m_solver(m_context) { - // This needs to be set globally. + // These need to be set globally. z3::set_param("rewriter.pull_cheap_ite", true); - // This needs to be set in the context. - m_context.set("timeout", queryTimeout); + z3::set_param("rlimit", resourceLimit); } void Z3Interface::reset() diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h index 38734dd00..df5db8d26 100644 --- a/libsolidity/formal/Z3Interface.h +++ b/libsolidity/formal/Z3Interface.h @@ -50,6 +50,12 @@ public: z3::context* context() { return &m_context; } + // Z3 "basic resources" limit. + // This is used to make the runs more deterministic and platform/machine independent. + // The tests start failing for Z3 with less than 20000000, + // so using double that. + static int const resourceLimit = 40000000; + private: void declareFunction(std::string const& _name, Sort const& _sort); diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 57cae9374..e605c6b48 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -512,7 +512,7 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const if (!c.sourceMapping) { if (auto items = assemblyItems(_contractName)) - c.sourceMapping.reset(new string(computeSourceMapping(*items))); + c.sourceMapping = make_unique(computeSourceMapping(*items)); } return c.sourceMapping.get(); } @@ -526,7 +526,7 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c if (!c.runtimeSourceMapping) { if (auto items = runtimeAssemblyItems(_contractName)) - c.runtimeSourceMapping.reset(new string(computeSourceMapping(*items))); + c.runtimeSourceMapping = make_unique(computeSourceMapping(*items)); } return c.runtimeSourceMapping.get(); } @@ -663,7 +663,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const // caches the result if (!_contract.abi) - _contract.abi.reset(new Json::Value(ABI::generate(*_contract.contract))); + _contract.abi = make_unique(ABI::generate(*_contract.contract)); return *_contract.abi; } @@ -685,7 +685,7 @@ Json::Value const& CompilerStack::storageLayout(Contract const& _contract) const // caches the result if (!_contract.storageLayout) - _contract.storageLayout.reset(new Json::Value(StorageLayout().generate(*_contract.contract))); + _contract.storageLayout = make_unique(StorageLayout().generate(*_contract.contract)); return *_contract.storageLayout; } @@ -707,7 +707,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const // caches the result if (!_contract.userDocumentation) - _contract.userDocumentation.reset(new Json::Value(Natspec::userDocumentation(*_contract.contract))); + _contract.userDocumentation = make_unique(Natspec::userDocumentation(*_contract.contract)); return *_contract.userDocumentation; } @@ -729,7 +729,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const // caches the result if (!_contract.devDocumentation) - _contract.devDocumentation.reset(new Json::Value(Natspec::devDocumentation(*_contract.contract))); + _contract.devDocumentation = make_unique(Natspec::devDocumentation(*_contract.contract)); return *_contract.devDocumentation; } @@ -762,7 +762,7 @@ string const& CompilerStack::metadata(Contract const& _contract) const // cache the result if (!_contract.metadata) - _contract.metadata.reset(new string(createMetadata(_contract))); + _contract.metadata = make_unique(createMetadata(_contract)); return *_contract.metadata; } @@ -1060,25 +1060,17 @@ void CompilerStack::generateEWasm(ContractDefinition const& _contract) return; // Re-parse the Yul IR in EVM dialect - yul::AssemblyStack evmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); - evmStack.parseAndAnalyze("", compiledContract.yulIROptimized); + yul::AssemblyStack stack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); + stack.parseAndAnalyze("", compiledContract.yulIROptimized); - // Turn into eWasm dialect - yul::Object ewasmObject = yul::EVMToEWasmTranslator( - yul::EVMDialect::strictAssemblyForEVMObjects(m_evmVersion) - ).run(*evmStack.parserResult()); + stack.optimize(); + stack.translate(yul::AssemblyStack::Language::EWasm); + stack.optimize(); - // Re-inject into an assembly stack for the eWasm dialect - yul::AssemblyStack ewasmStack(m_evmVersion, yul::AssemblyStack::Language::EWasm, m_optimiserSettings); - // TODO this is a hack for now - provide as structured AST! - ewasmStack.parseAndAnalyze("", "{}"); - *ewasmStack.parserResult() = move(ewasmObject); - ewasmStack.optimize(); - - //cout << yul::AsmPrinter{}(*ewasmStack.parserResult()->code) << endl; + //cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl; // Turn into eWasm text representation. - auto result = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm); + auto result = stack.assemble(yul::AssemblyStack::Machine::eWasm); compiledContract.eWasm = std::move(result.assembly); compiledContract.eWasmObject = std::move(*result.bytecode); } @@ -1410,7 +1402,7 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const if (eth::AssemblyItems const* items = assemblyItems(_contractName)) { Gas executionGas = gasEstimator.functionalEstimation(*items); - Gas codeDepositGas{eth::GasMeter::dataGas(runtimeObject(_contractName).bytecode, false)}; + Gas codeDepositGas{eth::GasMeter::dataGas(runtimeObject(_contractName).bytecode, false, m_evmVersion)}; Json::Value creation(Json::objectValue); creation["codeDepositCost"] = gasToJson(codeDepositGas); diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 921e90231..8e226a7df 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -817,6 +818,16 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting "Unimplemented feature (" + _exception.lineInfo() + ")" )); } + catch (yul::YulException const& _exception) + { + errors.append(formatErrorWithException( + _exception, + false, + "YulException", + "general", + "Yul exception" + )); + } catch (Exception const& _exception) { errors.append(formatError( diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index f1145ee83..07f0ef5f7 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -350,7 +350,7 @@ ASTPointer Parser::parseInheritanceSpecifier() if (m_scanner->currentToken() == Token::LParen) { m_scanner->next(); - arguments.reset(new vector>(parseFunctionCallListArguments())); + arguments = make_unique>>(parseFunctionCallListArguments()); nodeFactory.markEndPosition(); expectToken(Token::RParen); } @@ -811,7 +811,7 @@ ASTPointer Parser::parseModifierInvocation() if (m_scanner->currentToken() == Token::LParen) { m_scanner->next(); - arguments.reset(new vector>(parseFunctionCallListArguments())); + arguments = make_unique>>(parseFunctionCallListArguments()); nodeFactory.markEndPosition(); expectToken(Token::RParen); } @@ -1614,9 +1614,22 @@ ASTPointer Parser::parsePrimaryExpression() } break; case Token::StringLiteral: + case Token::HexStringLiteral: + { + string literal = m_scanner->currentLiteral(); + Token firstToken = m_scanner->currentToken(); + while (m_scanner->peekNextToken() == firstToken) + { + m_scanner->next(); + literal += m_scanner->currentLiteral(); + } nodeFactory.markEndPosition(); - expression = nodeFactory.createNode(token, getLiteralAndAdvance()); + m_scanner->next(); + if (m_scanner->currentToken() == Token::Illegal) + fatalParserError(to_string(m_scanner->currentError())); + expression = nodeFactory.createNode(token, make_shared(literal)); break; + } case Token::Identifier: nodeFactory.markEndPosition(); expression = nodeFactory.createNode(getLiteralAndAdvance()); diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 9c62737d8..9ea6cf2bc 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -60,12 +60,12 @@ bool AsmAnalyzer::analyze(Block const& _block) success = (*this)(_block); if (!success) - solAssert(m_errorReporter.hasErrors(), "No success but no error."); + yulAssert(m_errorReporter.hasErrors(), "No success but no error."); } catch (FatalError const&) { // This FatalError con occur if the errorReporter has too many errors. - solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); + yulAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); } return success && !m_errorReporter.hasErrors(); } @@ -83,13 +83,13 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, {}, _object.dataNames() ).analyze(*_object.code); - solAssert(success && errorList.empty(), "Invalid assembly/yul code."); + yulAssert(success && errorList.empty(), "Invalid assembly/yul code."); return analysisInfo; } bool AsmAnalyzer::operator()(Label const& _label) { - solAssert(!_label.name.empty(), ""); + yulAssert(!_label.name.empty(), ""); checkLooseFeature( _label.location, "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead." @@ -134,8 +134,8 @@ bool AsmAnalyzer::operator()(Literal const& _literal) } else if (_literal.kind == LiteralKind::Boolean) { - solAssert(m_dialect.flavour == AsmFlavour::Yul, ""); - solAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); + yulAssert(m_dialect.flavour == AsmFlavour::Yul, ""); + yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); } m_info.stackHeightInfo[&_literal] = m_stackHeight; return true; @@ -143,10 +143,10 @@ bool AsmAnalyzer::operator()(Literal const& _literal) bool AsmAnalyzer::operator()(Identifier const& _identifier) { - solAssert(!_identifier.name.empty(), ""); + yulAssert(!_identifier.name.empty(), ""); size_t numErrorsBefore = m_errorReporter.errors().size(); bool success = true; - if (m_currentScope->lookup(_identifier.name, Scope::Visitor( + if (m_currentScope->lookup(_identifier.name, GenericVisitor{ [&](Scope::Variable const& _var) { if (!m_activeVariables.count(&_var)) @@ -171,7 +171,7 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) ); success = false; } - ))) + })) { } else @@ -197,14 +197,14 @@ bool AsmAnalyzer::operator()(Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(m_dialect.flavour != AsmFlavour::Yul, ""); + yulAssert(m_dialect.flavour != AsmFlavour::Yul, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) success = false; // Parser already checks that the number of arguments is correct. auto const& info = instructionInfo(_instr.instruction); - solAssert(info.args == int(_instr.arguments.size()), ""); + yulAssert(info.args == int(_instr.arguments.size()), ""); m_stackHeight += info.ret - info.args; m_info.stackHeightInfo[&_instr] = m_stackHeight; warnOnInstructions(_instr.instruction, _instr.location); @@ -214,7 +214,7 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) bool AsmAnalyzer::operator()(ExpressionStatement const& _statement) { int initialStackHeight = m_stackHeight; - bool success = boost::apply_visitor(*this, _statement.expression); + bool success = std::visit(*this, _statement.expression); if (m_stackHeight != initialStackHeight && (m_dialect.flavour != AsmFlavour::Loose || m_errorTypeForLoose)) { Error::Type errorType = m_dialect.flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; @@ -245,11 +245,11 @@ bool AsmAnalyzer::operator()(StackAssignment const& _assignment) bool AsmAnalyzer::operator()(Assignment const& _assignment) { - solAssert(_assignment.value, ""); + yulAssert(_assignment.value, ""); int const expectedItems = _assignment.variableNames.size(); - solAssert(expectedItems >= 1, ""); + yulAssert(expectedItems >= 1, ""); int const stackHeight = m_stackHeight; - bool success = boost::apply_visitor(*this, *_assignment.value); + bool success = std::visit(*this, *_assignment.value); if ((m_stackHeight - stackHeight) != expectedItems) { m_errorReporter.declarationError( @@ -276,7 +276,7 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) if (_varDecl.value) { int const stackHeight = m_stackHeight; - success = boost::apply_visitor(*this, *_varDecl.value); + success = std::visit(*this, *_varDecl.value); int numValues = m_stackHeight - stackHeight; if (numValues != numVariables) { @@ -298,7 +298,7 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) for (auto const& variable: _varDecl.variables) { expectValidType(variable.type.str(), variable.location); - m_activeVariables.insert(&boost::get(m_currentScope->identifiers.at(variable.name))); + m_activeVariables.insert(&std::get(m_currentScope->identifiers.at(variable.name))); } m_info.stackHeightInfo[&_varDecl] = m_stackHeight; return success; @@ -306,14 +306,14 @@ bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef) { - solAssert(!_funDef.name.empty(), ""); + yulAssert(!_funDef.name.empty(), ""); Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); - solAssert(virtualBlock, ""); + yulAssert(virtualBlock, ""); Scope& varScope = scope(virtualBlock); for (auto const& var: _funDef.parameters + _funDef.returnVariables) { expectValidType(var.type.str(), var.location); - m_activeVariables.insert(&boost::get(varScope.identifiers.at(var.name))); + m_activeVariables.insert(&std::get(varScope.identifiers.at(var.name))); } int const stackHeight = m_stackHeight; @@ -328,7 +328,7 @@ bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef) bool AsmAnalyzer::operator()(FunctionCall const& _funCall) { - solAssert(!_funCall.functionName.name.empty(), ""); + yulAssert(!_funCall.functionName.name.empty(), ""); bool success = true; size_t parameters = 0; size_t returns = 0; @@ -341,7 +341,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) if (f->literalArguments) needsLiteralArguments = true; } - else if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( + else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ [&](Scope::Variable const&) { m_errorReporter.typeError( @@ -364,7 +364,7 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) parameters = _fun.arguments.size(); returns = _fun.returns.size(); } - ))) + })) { m_errorReporter.declarationError(_funCall.functionName.location, "Function not found."); success = false; @@ -388,15 +388,15 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) success = false; else if (needsLiteralArguments) { - if (arg.type() != typeid(Literal)) + if (!holds_alternative(arg)) m_errorReporter.typeError( _funCall.functionName.location, "Function expects direct literals as arguments." ); - else if (!m_dataNames.count(boost::get(arg).value)) + else if (!m_dataNames.count(std::get(arg).value)) m_errorReporter.typeError( _funCall.functionName.location, - "Unknown data object \"" + boost::get(arg).value.str() + "\"." + "Unknown data object \"" + std::get(arg).value.str() + "\"." ); } } @@ -426,7 +426,7 @@ bool AsmAnalyzer::operator()(If const& _if) bool AsmAnalyzer::operator()(Switch const& _switch) { - solAssert(_switch.expression, ""); + yulAssert(_switch.expression, ""); bool success = true; @@ -498,7 +498,7 @@ bool AsmAnalyzer::operator()(Switch const& _switch) bool AsmAnalyzer::operator()(ForLoop const& _for) { - solAssert(_for.condition, ""); + yulAssert(_for.condition, ""); Scope* outerScope = m_currentScope; @@ -556,7 +556,7 @@ bool AsmAnalyzer::operator()(Block const& _block) int const initialStackHeight = m_stackHeight; for (auto const& s: _block.statements) - if (!boost::apply_visitor(*this, s)) + if (!std::visit(*this, s)) success = false; m_stackHeight -= scope(&_block).numberOfVariables(); @@ -585,7 +585,7 @@ bool AsmAnalyzer::expectExpression(Expression const& _expr) { bool success = true; int const initialHeight = m_stackHeight; - if (!boost::apply_visitor(*this, _expr)) + if (!std::visit(*this, _expr)) success = false; if (!expectDeposit(1, initialHeight, locationOf(_expr))) success = false; @@ -609,19 +609,19 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con bool AsmAnalyzer::checkAssignment(Identifier const& _variable, size_t _valueSize) { - solAssert(!_variable.name.empty(), ""); + yulAssert(!_variable.name.empty(), ""); bool success = true; size_t numErrorsBefore = m_errorReporter.errors().size(); size_t variableSize(-1); if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name)) { // Check that it is a variable - if (var->type() != typeid(Scope::Variable)) + if (!holds_alternative(*var)) { m_errorReporter.typeError(_variable.location, "Assignment requires variable."); success = false; } - else if (!m_activeVariables.count(&boost::get(*var))) + else if (!m_activeVariables.count(&std::get(*var))) { m_errorReporter.declarationError( _variable.location, @@ -665,9 +665,9 @@ bool AsmAnalyzer::checkAssignment(Identifier const& _variable, size_t _valueSize Scope& AsmAnalyzer::scope(Block const* _block) { - solAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present."); + yulAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present."); auto scopePtr = m_info.scopes.at(_block); - solAssert(scopePtr, "Scope requested but not present."); + yulAssert(scopePtr, "Scope requested but not present."); return *scopePtr; } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) @@ -686,10 +686,10 @@ void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocatio { // We assume that returndatacopy, returndatasize and staticcall are either all available // or all not available. - solAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); + yulAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); // Similarly we assume bitwise shifting and create2 go together. - solAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); - solAssert(m_dialect.flavour != AsmFlavour::Yul, ""); + yulAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); + yulAssert(m_dialect.flavour != AsmFlavour::Yul, ""); auto errorForVM = [=](string const& vmKindMessage) { m_errorReporter.typeError( @@ -768,7 +768,7 @@ void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocatio void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) { if (m_dialect.flavour != AsmFlavour::Loose) - solAssert(false, _description); + yulAssert(false, _description); else if (m_errorTypeForLoose) m_errorReporter.error(*m_errorTypeForLoose, _location, _description); } diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 14337d323..40d9a1d51 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -30,8 +30,6 @@ #include #include -#include - #include #include #include @@ -53,7 +51,7 @@ struct AsmAnalysisInfo; * references and performs other checks. * If all these checks pass, code generation should not throw errors. */ -class AsmAnalyzer: public boost::static_visitor +class AsmAnalyzer { public: explicit AsmAnalyzer( diff --git a/libyul/AsmAnalysisInfo.h b/libyul/AsmAnalysisInfo.h index 08a35ade0..1201399a2 100644 --- a/libyul/AsmAnalysisInfo.h +++ b/libyul/AsmAnalysisInfo.h @@ -22,8 +22,6 @@ #include -#include - #include #include #include diff --git a/libyul/AsmData.h b/libyul/AsmData.h index cedff1106..33bf48090 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -28,7 +28,6 @@ #include #include -#include #include #include @@ -83,7 +82,7 @@ struct Break { langutil::SourceLocation location; }; /// Continue statement (valid within for loop) struct Continue { langutil::SourceLocation location; }; -struct LocationExtractor: boost::static_visitor +struct LocationExtractor { template langutil::SourceLocation operator()(T const& _node) const { @@ -94,7 +93,7 @@ struct LocationExtractor: boost::static_visitor /// Extracts the source location from an inline assembly node. template inline langutil::SourceLocation locationOf(T const& _node) { - return boost::apply_visitor(LocationExtractor(), _node); + return std::visit(LocationExtractor(), _node); } } diff --git a/libyul/AsmDataForward.h b/libyul/AsmDataForward.h index d01b1dffa..29c9db3f3 100644 --- a/libyul/AsmDataForward.h +++ b/libyul/AsmDataForward.h @@ -22,7 +22,7 @@ #pragma once -#include +#include namespace yul { @@ -48,7 +48,7 @@ struct Block; struct TypedName; -using Expression = boost::variant; -using Statement = boost::variant; +using Expression = std::variant; +using Statement = std::variant; } diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index ae3056f21..5de7417d2 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -52,7 +53,7 @@ shared_ptr Parser::parse(std::shared_ptr const& _scanner, bool _ } catch (FatalError const&) { - solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); + yulAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); } return nullptr; @@ -184,7 +185,7 @@ Statement Parser::parseStatement() while (true) { - if (elementary.type() != typeid(Identifier)) + if (!holds_alternative(elementary)) { auto const token = currentToken() == Token::Comma ? "," : ":="; @@ -196,7 +197,7 @@ Statement Parser::parseStatement() ); } - auto const& identifier = boost::get(elementary); + auto const& identifier = std::get(elementary); if (m_dialect.builtin(identifier.name)) fatalParserError("Cannot assign to builtin function \"" + identifier.name.str() + "\"."); @@ -212,22 +213,22 @@ Statement Parser::parseStatement() } Assignment assignment = - createWithLocation(boost::get(elementary).location); + createWithLocation(std::get(elementary).location); assignment.variableNames = std::move(variableNames); expectToken(Token::AssemblyAssign); - assignment.value.reset(new Expression(parseExpression())); + assignment.value = make_unique(parseExpression()); assignment.location.end = locationOf(*assignment.value).end; return Statement{std::move(assignment)}; } case Token::Colon: { - if (elementary.type() != typeid(Identifier)) + if (!holds_alternative(elementary)) fatalParserError("Label name must precede \":\"."); - Identifier const& identifier = boost::get(elementary); + Identifier const& identifier = std::get(elementary); advance(); @@ -245,20 +246,20 @@ Statement Parser::parseStatement() break; } - if (elementary.type() == typeid(Identifier)) + if (holds_alternative(elementary)) { - Identifier& identifier = boost::get(elementary); + Identifier& identifier = std::get(elementary); return ExpressionStatement{identifier.location, { move(identifier) }}; } - else if (elementary.type() == typeid(Literal)) + else if (holds_alternative(elementary)) { - Expression expr = boost::get(elementary); + Expression expr = std::get(elementary); return ExpressionStatement{locationOf(expr), expr}; } else { - solAssert(elementary.type() == typeid(Instruction), "Invalid elementary operation."); - return boost::get(elementary); + yulAssert(holds_alternative(elementary), "Invalid elementary operation."); + return std::get(elementary); } } @@ -272,12 +273,12 @@ Case Parser::parseCase() { advance(); ElementaryOperation literal = parseElementaryOperation(); - if (literal.type() != typeid(Literal)) + if (!holds_alternative(literal)) fatalParserError("Literal expected."); - _case.value = make_unique(boost::get(std::move(literal))); + _case.value = make_unique(std::get(std::move(literal))); } else - solAssert(false, "Case or default case expected."); + yulAssert(false, "Case or default case expected."); _case.body = parseBlock(); _case.location.end = _case.body.location.end; return _case; @@ -311,12 +312,12 @@ Expression Parser::parseExpression() RecursionGuard recursionGuard(*this); ElementaryOperation operation = parseElementaryOperation(); - if (operation.type() == typeid(FunctionCall)) + if (holds_alternative(operation)) return parseCall(std::move(operation)); - else if (operation.type() == typeid(Instruction)) + else if (holds_alternative(operation)) { - solAssert(m_dialect.flavour == AsmFlavour::Loose, ""); - Instruction const& instr = boost::get(operation); + yulAssert(m_dialect.flavour == AsmFlavour::Loose, ""); + Instruction const& instr = std::get(operation); // Disallow instructions returning multiple values (and DUP/SWAP) as expression. if ( instructionInfo(instr.instruction).ret != 1 || @@ -345,19 +346,19 @@ Expression Parser::parseExpression() } if (currentToken() == Token::LParen) return parseCall(std::move(operation)); - else if (operation.type() == typeid(Instruction)) + else if (holds_alternative(operation)) { // Instructions not taking arguments are allowed as expressions. - solAssert(m_dialect.flavour == AsmFlavour::Loose, ""); - Instruction& instr = boost::get(operation); + yulAssert(m_dialect.flavour == AsmFlavour::Loose, ""); + Instruction& instr = std::get(operation); return FunctionalInstruction{std::move(instr.location), instr.instruction, {}}; } - else if (operation.type() == typeid(Identifier)) - return boost::get(operation); + else if (holds_alternative(operation)) + return std::get(operation); else { - solAssert(operation.type() == typeid(Literal), ""); - return boost::get(operation); + yulAssert(holds_alternative(operation), ""); + return std::get(operation); } } @@ -537,10 +538,10 @@ FunctionDefinition Parser::parseFunctionDefinition() Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) { RecursionGuard recursionGuard(*this); - if (_initialOp.type() == typeid(Instruction)) + if (holds_alternative(_initialOp)) { - solAssert(m_dialect.flavour != AsmFlavour::Yul, "Instructions are invalid in Yul"); - Instruction& instruction = boost::get(_initialOp); + yulAssert(m_dialect.flavour != AsmFlavour::Yul, "Instructions are invalid in Yul"); + Instruction& instruction = std::get(_initialOp); FunctionalInstruction ret; ret.instruction = instruction.instruction; ret.location = std::move(instruction.location); @@ -591,16 +592,16 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) expectToken(Token::RParen); return ret; } - else if (_initialOp.type() == typeid(Identifier) || _initialOp.type() == typeid(FunctionCall)) + else if (holds_alternative(_initialOp) || holds_alternative(_initialOp)) { FunctionCall ret; - if (_initialOp.type() == typeid(Identifier)) + if (holds_alternative(_initialOp)) { - ret.functionName = std::move(boost::get(_initialOp)); + ret.functionName = std::move(std::get(_initialOp)); ret.location = ret.functionName.location; } else - ret = std::move(boost::get(_initialOp)); + ret = std::move(std::get(_initialOp)); expectToken(Token::LParen); if (currentToken() != Token::RParen) { diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h index 6f88a211e..80b8edc26 100644 --- a/libyul/AsmParser.h +++ b/libyul/AsmParser.h @@ -30,9 +30,9 @@ #include #include +#include #include - namespace yul { @@ -56,7 +56,7 @@ public: static std::map const& instructions(); protected: - using ElementaryOperation = boost::variant; + using ElementaryOperation = std::variant; /// Creates an inline assembly node with the given source location. template T createWithLocation(langutil::SourceLocation const& _loc = {}) const diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index 71151a0cf..50e354f0a 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include @@ -41,8 +41,8 @@ using namespace yul; string AsmPrinter::operator()(yul::Instruction const& _instruction) const { - solAssert(!m_yul, ""); - solAssert(isValidInstruction(_instruction.instruction), "Invalid instruction"); + yulAssert(!m_yul, ""); + yulAssert(isValidInstruction(_instruction.instruction), "Invalid instruction"); return boost::to_lower_copy(instructionInfo(_instruction.instruction).name); } @@ -51,10 +51,10 @@ string AsmPrinter::operator()(Literal const& _literal) const switch (_literal.kind) { case LiteralKind::Number: - solAssert(isValidDecimal(_literal.value.str()) || isValidHex(_literal.value.str()), "Invalid number literal"); + yulAssert(isValidDecimal(_literal.value.str()) || isValidHex(_literal.value.str()), "Invalid number literal"); return _literal.value.str() + appendTypeName(_literal.type); case LiteralKind::Boolean: - solAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "Invalid bool literal."); + yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "Invalid bool literal."); return ((_literal.value == "true"_yulstring) ? "true" : "false") + appendTypeName(_literal.type); case LiteralKind::String: break; @@ -91,49 +91,50 @@ string AsmPrinter::operator()(Literal const& _literal) const string AsmPrinter::operator()(Identifier const& _identifier) const { - solAssert(!_identifier.name.empty(), "Invalid identifier."); + yulAssert(!_identifier.name.empty(), "Invalid identifier."); return _identifier.name.str(); } string AsmPrinter::operator()(FunctionalInstruction const& _functionalInstruction) const { - solAssert(!m_yul, ""); - solAssert(isValidInstruction(_functionalInstruction.instruction), "Invalid instruction"); + yulAssert(!m_yul, ""); + yulAssert(isValidInstruction(_functionalInstruction.instruction), "Invalid instruction"); return boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) + "(" + boost::algorithm::join( - _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), - ", ") + + _functionalInstruction.arguments | boost::adaptors::transformed([&](auto&& _node) { return std::visit(*this, _node); }), + ", " + ) + ")"; } string AsmPrinter::operator()(ExpressionStatement const& _statement) const { - return boost::apply_visitor(*this, _statement.expression); + return std::visit(*this, _statement.expression); } string AsmPrinter::operator()(Label const& _label) const { - solAssert(!m_yul, ""); - solAssert(!_label.name.empty(), "Invalid label."); + yulAssert(!m_yul, ""); + yulAssert(!_label.name.empty(), "Invalid label."); return _label.name.str() + ":"; } string AsmPrinter::operator()(StackAssignment const& _assignment) const { - solAssert(!m_yul, ""); - solAssert(!_assignment.variableName.name.empty(), "Invalid variable name."); + yulAssert(!m_yul, ""); + yulAssert(!_assignment.variableName.name.empty(), "Invalid variable name."); return "=: " + (*this)(_assignment.variableName); } string AsmPrinter::operator()(Assignment const& _assignment) const { - solAssert(_assignment.variableNames.size() >= 1, ""); + yulAssert(_assignment.variableNames.size() >= 1, ""); string variables = (*this)(_assignment.variableNames.front()); for (size_t i = 1; i < _assignment.variableNames.size(); ++i) variables += ", " + (*this)(_assignment.variableNames[i]); - return variables + " := " + boost::apply_visitor(*this, *_assignment.value); + return variables + " := " + std::visit(*this, *_assignment.value); } string AsmPrinter::operator()(VariableDeclaration const& _variableDeclaration) const @@ -148,14 +149,14 @@ string AsmPrinter::operator()(VariableDeclaration const& _variableDeclaration) c if (_variableDeclaration.value) { out += " := "; - out += boost::apply_visitor(*this, *_variableDeclaration.value); + out += std::visit(*this, *_variableDeclaration.value); } return out; } string AsmPrinter::operator()(FunctionDefinition const& _functionDefinition) const { - solAssert(!_functionDefinition.name.empty(), "Invalid function name."); + yulAssert(!_functionDefinition.name.empty(), "Invalid function name."); string out = "function " + _functionDefinition.name.str() + "("; out += boost::algorithm::join( _functionDefinition.parameters | boost::adaptors::transformed( @@ -183,25 +184,25 @@ string AsmPrinter::operator()(FunctionCall const& _functionCall) const return (*this)(_functionCall.functionName) + "(" + boost::algorithm::join( - _functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)), + _functionCall.arguments | boost::adaptors::transformed([&](auto&& _node) { return std::visit(*this, _node); }), ", " ) + ")"; } string AsmPrinter::operator()(If const& _if) const { - solAssert(_if.condition, "Invalid if condition."); + yulAssert(_if.condition, "Invalid if condition."); string body = (*this)(_if.body); char delim = '\n'; if (body.find('\n') == string::npos) delim = ' '; - return "if " + boost::apply_visitor(*this, *_if.condition) + delim + (*this)(_if.body); + return "if " + std::visit(*this, *_if.condition) + delim + (*this)(_if.body); } string AsmPrinter::operator()(Switch const& _switch) const { - solAssert(_switch.expression, "Invalid expression pointer."); - string out = "switch " + boost::apply_visitor(*this, *_switch.expression); + yulAssert(_switch.expression, "Invalid expression pointer."); + string out = "switch " + std::visit(*this, *_switch.expression); for (auto const& _case: _switch.cases) { if (!_case.value) @@ -215,9 +216,9 @@ string AsmPrinter::operator()(Switch const& _switch) const string AsmPrinter::operator()(ForLoop const& _forLoop) const { - solAssert(_forLoop.condition, "Invalid for loop condition."); + yulAssert(_forLoop.condition, "Invalid for loop condition."); string pre = (*this)(_forLoop.pre); - string condition = boost::apply_visitor(*this, *_forLoop.condition); + string condition = std::visit(*this, *_forLoop.condition); string post = (*this)(_forLoop.post); char delim = '\n'; if ( @@ -246,7 +247,7 @@ string AsmPrinter::operator()(Block const& _block) const if (_block.statements.empty()) return "{ }"; string body = boost::algorithm::join( - _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)), + _block.statements | boost::adaptors::transformed([&](auto&& _node) { return std::visit(*this, _node); }), "\n" ); if (body.size() < 30 && body.find('\n') == string::npos) @@ -260,7 +261,7 @@ string AsmPrinter::operator()(Block const& _block) const string AsmPrinter::formatTypedName(TypedName _variable) const { - solAssert(!_variable.name.empty(), "Invalid variable name."); + yulAssert(!_variable.name.empty(), "Invalid variable name."); return _variable.name.str() + appendTypeName(_variable.type); } diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h index 82fed24f2..cf4fcecb2 100644 --- a/libyul/AsmPrinter.h +++ b/libyul/AsmPrinter.h @@ -26,12 +26,10 @@ #include -#include - namespace yul { -class AsmPrinter: public boost::static_visitor +class AsmPrinter { public: explicit AsmPrinter(bool _yul = false): m_yul(_yul) {} diff --git a/libyul/AsmScope.cpp b/libyul/AsmScope.cpp index 252361d27..b10130e57 100644 --- a/libyul/AsmScope.cpp +++ b/libyul/AsmScope.cpp @@ -58,7 +58,7 @@ Scope::Identifier* Scope::lookup(YulString _name) auto id = s->identifiers.find(_name); if (id != s->identifiers.end()) { - if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable)) + if (crossedFunctionBoundary && holds_alternative(id->second)) return nullptr; else return &id->second; @@ -84,7 +84,7 @@ size_t Scope::numberOfVariables() const { size_t count = 0; for (auto const& identifier: identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) + if (holds_alternative(identifier.second)) count++; return count; } diff --git a/libyul/AsmScope.h b/libyul/AsmScope.h index 2c7af4ef1..9e3a74d03 100644 --- a/libyul/AsmScope.h +++ b/libyul/AsmScope.h @@ -26,11 +26,10 @@ #include -#include - #include #include #include +#include namespace yul { @@ -48,9 +47,7 @@ struct Scope std::vector returns; }; - using Identifier = boost::variant; - using Visitor = dev::GenericVisitor; - using NonconstVisitor = dev::GenericVisitor; + using Identifier = std::variant; bool registerVariable(YulString _name, YulType const& _type); bool registerLabel(YulString _name); @@ -74,7 +71,7 @@ struct Scope { if (Identifier* id = lookup(_name)) { - boost::apply_visitor(_visitor, *id); + std::visit(_visitor, *id); return true; } else diff --git a/libyul/AsmScopeFiller.cpp b/libyul/AsmScopeFiller.cpp index 1706b147d..fb23a4628 100644 --- a/libyul/AsmScopeFiller.cpp +++ b/libyul/AsmScopeFiller.cpp @@ -23,9 +23,9 @@ #include #include #include +#include #include -#include #include @@ -47,7 +47,7 @@ ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter): bool ScopeFiller::operator()(ExpressionStatement const& _expr) { - return boost::apply_visitor(*this, _expr.expression); + return std::visit(*this, _expr.expression); } bool ScopeFiller::operator()(Label const& _item) @@ -88,7 +88,7 @@ bool ScopeFiller::operator()(FunctionDefinition const& _funDef) if (!(*this)(_funDef.body)) success = false; - solAssert(m_currentScope == &varScope, ""); + yulAssert(m_currentScope == &varScope, ""); m_currentScope = m_currentScope->superScope; return success; @@ -116,7 +116,7 @@ bool ScopeFiller::operator()(ForLoop const& _forLoop) if (!(*this)(_forLoop.pre)) success = false; m_currentScope = &scope(&_forLoop.pre); - if (!boost::apply_visitor(*this, *_forLoop.condition)) + if (!std::visit(*this, *_forLoop.condition)) success = false; if (!(*this)(_forLoop.body)) success = false; @@ -137,11 +137,11 @@ bool ScopeFiller::operator()(Block const& _block) // First visit all functions to make them create // an entry in the scope according to their visibility. for (auto const& s: _block.statements) - if (s.type() == typeid(FunctionDefinition)) - if (!registerFunction(boost::get(s))) + if (holds_alternative(s)) + if (!registerFunction(std::get(s))) success = false; for (auto const& s: _block.statements) - if (!boost::apply_visitor(*this, s)) + if (!std::visit(*this, s)) success = false; m_currentScope = m_currentScope->superScope; diff --git a/libyul/AsmScopeFiller.h b/libyul/AsmScopeFiller.h index 220538bb2..f019889c5 100644 --- a/libyul/AsmScopeFiller.h +++ b/libyul/AsmScopeFiller.h @@ -22,8 +22,6 @@ #include -#include - #include #include @@ -44,7 +42,7 @@ struct AsmAnalysisInfo; * Fills scopes with identifiers and checks for name clashes. * Does not resolve references. */ -class ScopeFiller: public boost::static_visitor +class ScopeFiller { public: ScopeFiller(AsmAnalysisInfo& _info, langutil::ErrorReporter& _errorReporter); diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index 87df95f37..5ac28f63e 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -62,7 +63,7 @@ Dialect const& languageToDialect(AssemblyStack::Language _language, EVMVersion _ case AssemblyStack::Language::EWasm: return WasmDialect::instance(); } - solAssert(false, ""); + yulAssert(false, ""); return Dialect::yul(); } @@ -71,7 +72,7 @@ Dialect const& languageToDialect(AssemblyStack::Language _language, EVMVersion _ Scanner const& AssemblyStack::scanner() const { - solAssert(m_scanner, ""); + yulAssert(m_scanner, ""); return *m_scanner; } @@ -83,8 +84,8 @@ bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string m_parserResult = ObjectParser(m_errorReporter, languageToDialect(m_language, m_evmVersion)).parse(m_scanner, false); if (!m_errorReporter.errors().empty()) return false; - solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); + yulAssert(m_parserResult, ""); + yulAssert(m_parserResult->code, ""); return analyzeParsed(); } @@ -94,24 +95,41 @@ void AssemblyStack::optimize() if (!m_optimiserSettings.runYulOptimiser) return; - solAssert(m_analysisSuccessful, "Analysis was not successful."); + yulAssert(m_analysisSuccessful, "Analysis was not successful."); m_analysisSuccessful = false; - solAssert(m_parserResult, ""); + yulAssert(m_parserResult, ""); optimize(*m_parserResult, true); - solAssert(analyzeParsed(), "Invalid source code after optimization."); + yulAssert(analyzeParsed(), "Invalid source code after optimization."); +} + +void AssemblyStack::translate(AssemblyStack::Language _targetLanguage) +{ + if (m_language == _targetLanguage) + return; + + solAssert( + m_language == Language::StrictAssembly && _targetLanguage == Language::EWasm, + "Invalid language combination" + ); + + *m_parserResult = EVMToEWasmTranslator( + languageToDialect(m_language, m_evmVersion) + ).run(*parserResult()); + + m_language = _targetLanguage; } bool AssemblyStack::analyzeParsed() { - solAssert(m_parserResult, ""); + yulAssert(m_parserResult, ""); m_analysisSuccessful = analyzeParsed(*m_parserResult); return m_analysisSuccessful; } bool AssemblyStack::analyzeParsed(Object& _object) { - solAssert(_object.code, ""); + yulAssert(_object.code, ""); _object.analysisInfo = make_shared(); AsmAnalyzer analyzer( @@ -141,15 +159,15 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o else if (m_language == AssemblyStack::Language::Yul) dialect = &EVMDialect::yulForEVM(m_evmVersion); else - solAssert(false, "Invalid language."); + yulAssert(false, "Invalid language."); EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _evm15, _optimize); } void AssemblyStack::optimize(Object& _object, bool _isCreation) { - solAssert(_object.code, ""); - solAssert(_object.analysisInfo, ""); + yulAssert(_object.code, ""); + yulAssert(_object.analysisInfo, ""); for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) optimize(*subObject, false); @@ -168,10 +186,10 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation) MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const { - solAssert(m_analysisSuccessful, ""); - solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); - solAssert(m_parserResult->analysisInfo, ""); + yulAssert(m_analysisSuccessful, ""); + yulAssert(m_parserResult, ""); + yulAssert(m_parserResult->code, ""); + yulAssert(m_parserResult->analysisInfo, ""); switch (_machine) { @@ -196,7 +214,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const } case Machine::eWasm: { - solAssert(m_language == Language::EWasm, ""); + yulAssert(m_language == Language::EWasm, ""); Dialect const& dialect = languageToDialect(m_language, EVMVersion{}); MachineAssemblyObject object; @@ -213,15 +231,15 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const string AssemblyStack::print() const { - solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); + yulAssert(m_parserResult, ""); + yulAssert(m_parserResult->code, ""); return m_parserResult->toString(m_language == Language::Yul) + "\n"; } shared_ptr AssemblyStack::parserResult() const { - solAssert(m_analysisSuccessful, "Analysis was not successful."); - solAssert(m_parserResult, ""); - solAssert(m_parserResult->code, ""); + yulAssert(m_analysisSuccessful, "Analysis was not successful."); + yulAssert(m_parserResult, ""); + yulAssert(m_parserResult->code, ""); return m_parserResult; } diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 8367ce786..a6dcd0b37 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -81,6 +81,9 @@ public: /// If the settings (see constructor) disabled the optimizer, nothing is done here. void optimize(); + /// Translate the source to a different language / dialect. + void translate(Language _targetLanguage); + /// Run the assembly step (should only be called after parseAndAnalyze). MachineAssemblyObject assemble(Machine _machine) const; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index f71171597..da45a2941 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -110,6 +110,8 @@ add_library(yul optimiser/KnowledgeBase.h optimiser/LoadResolver.cpp optimiser/LoadResolver.h + optimiser/LoopInvariantCodeMotion.cpp + optimiser/LoopInvariantCodeMotion.h optimiser/MainFunction.cpp optimiser/MainFunction.h optimiser/Metrics.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 7c6fe9d78..228515b9b 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -41,7 +41,7 @@ map CompilabilityChecker::run( if (_dialect.flavour == AsmFlavour::Yul) return {}; - solAssert(_dialect.flavour == AsmFlavour::Strict, ""); + yulAssert(_dialect.flavour == AsmFlavour::Strict, ""); if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) { @@ -69,7 +69,7 @@ map CompilabilityChecker::run( } catch (StackTooDeepError const&) { - solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); + yulAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); } std::map functions; diff --git a/libyul/Exceptions.h b/libyul/Exceptions.h index 3e1e4155b..7ce1a8066 100644 --- a/libyul/Exceptions.h +++ b/libyul/Exceptions.h @@ -33,6 +33,6 @@ struct YulAssertion: virtual YulException {}; /// Assertion that throws an YulAssertion containing the given description if it is not met. #define yulAssert(CONDITION, DESCRIPTION) \ - assertThrow(CONDITION, ::yul::YulException, DESCRIPTION) + assertThrow(CONDITION, ::yul::YulAssertion, DESCRIPTION) } diff --git a/libyul/ObjectParser.cpp b/libyul/ObjectParser.cpp index 5f1eadef6..683b8022b 100644 --- a/libyul/ObjectParser.cpp +++ b/libyul/ObjectParser.cpp @@ -112,7 +112,7 @@ shared_ptr ObjectParser::parseBlock() void ObjectParser::parseData(Object& _containingObject) { - solAssert( + yulAssert( currentToken() == Token::Identifier && currentLiteral() == "data", "parseData called on wrong input." ); @@ -120,7 +120,10 @@ void ObjectParser::parseData(Object& _containingObject) YulString name = parseUniqueName(&_containingObject); - expectToken(Token::StringLiteral, false); + if (currentToken() == Token::HexStringLiteral) + expectToken(Token::HexStringLiteral, false); + else + expectToken(Token::StringLiteral, false); addNamedSubObject(_containingObject, name, make_shared(name, asBytes(currentLiteral()))); advance(); } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 999d215a3..9022dadad 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -118,19 +118,19 @@ void EthAssemblyAdapter::appendJumpToIf(LabelID _labelId) void EthAssemblyAdapter::appendBeginsub(LabelID, int) { // TODO we could emulate that, though - solAssert(false, "BEGINSUB not implemented for EVM 1.0"); + yulAssert(false, "BEGINSUB not implemented for EVM 1.0"); } void EthAssemblyAdapter::appendJumpsub(LabelID, int, int) { // TODO we could emulate that, though - solAssert(false, "JUMPSUB not implemented for EVM 1.0"); + yulAssert(false, "JUMPSUB not implemented for EVM 1.0"); } void EthAssemblyAdapter::appendReturnsub(int, int) { // TODO we could emulate that, though - solAssert(false, "RETURNSUB not implemented for EVM 1.0"); + yulAssert(false, "RETURNSUB not implemented for EVM 1.0"); } void EthAssemblyAdapter::appendAssemblySize() @@ -174,7 +174,7 @@ AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(eth::AssemblyItem const& _tag) { u256 id = _tag.data(); - solAssert(id <= std::numeric_limits::max(), "Tag id too large."); + yulAssert(id <= std::numeric_limits::max(), "Tag id too large."); return LabelID(id); } @@ -207,11 +207,11 @@ void CodeGenerator::assemble( } catch (StackTooDeepError const& _e) { - BOOST_THROW_EXCEPTION( - InternalCompilerError() << errinfo_comment( - "Stack too deep when compiling inline assembly" + - (_e.comment() ? ": " + *_e.comment() : ".") - )); + yulAssert( + false, + "Stack too deep when compiling inline assembly" + + (_e.comment() ? ": " + *_e.comment() : ".") + ); } - solAssert(transform.stackErrors().empty(), "Stack errors present but not thrown."); + yulAssert(transform.stackErrors().empty(), "Stack errors present but not thrown."); } diff --git a/libyul/backends/evm/ConstantOptimiser.cpp b/libyul/backends/evm/ConstantOptimiser.cpp index f8b4854d8..74fe7667e 100644 --- a/libyul/backends/evm/ConstantOptimiser.cpp +++ b/libyul/backends/evm/ConstantOptimiser.cpp @@ -27,6 +27,8 @@ #include +#include + using namespace std; using namespace dev; using namespace yul; @@ -35,13 +37,13 @@ using Representation = ConstantOptimiser::Representation; namespace { -struct MiniEVMInterpreter: boost::static_visitor +struct MiniEVMInterpreter { explicit MiniEVMInterpreter(EVMDialect const& _dialect): m_dialect(_dialect) {} u256 eval(Expression const& _expr) { - return boost::apply_visitor(*this, _expr); + return std::visit(*this, _expr); } u256 eval(dev::eth::Instruction _instr, vector const& _arguments) @@ -92,9 +94,9 @@ struct MiniEVMInterpreter: boost::static_visitor void ConstantOptimiser::visit(Expression& _e) { - if (_e.type() == typeid(Literal)) + if (holds_alternative(_e)) { - Literal const& literal = boost::get(_e); + Literal const& literal = std::get(_e); if (literal.kind != LiteralKind::Number) return; @@ -115,7 +117,7 @@ Expression const* RepresentationFinder::tryFindRepresentation(dev::u256 const& _ return nullptr; Representation const& repr = findRepresentation(_value); - if (repr.expression->type() == typeid(Literal)) + if (holds_alternative(*repr.expression)) return nullptr; else return repr.expression.get(); diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index 65d22cbd8..6b268e67d 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -19,11 +19,10 @@ */ #include +#include #include -#include - using namespace std; using namespace dev; using namespace dev::eth; @@ -65,7 +64,7 @@ void EVMAssembly::appendLabel(LabelID _labelId) void EVMAssembly::appendLabelReference(LabelID _labelId) { - solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); + yulAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); // @TODO we now always use labelReferenceSize for all labels, it could be shortened // for some of them. appendInstruction(dev::eth::pushInstruction(labelReferenceSize)); @@ -81,7 +80,7 @@ EVMAssembly::LabelID EVMAssembly::newLabelId() AbstractAssembly::LabelID EVMAssembly::namedLabel(string const& _name) { - solAssert(!_name.empty(), ""); + yulAssert(!_name.empty(), ""); if (!m_namedLabels.count(_name)) m_namedLabels[_name] = newLabelId(); return m_namedLabels[_name]; @@ -89,12 +88,12 @@ AbstractAssembly::LabelID EVMAssembly::namedLabel(string const& _name) void EVMAssembly::appendLinkerSymbol(string const&) { - solAssert(false, "Linker symbols not yet implemented."); + yulAssert(false, "Linker symbols not yet implemented."); } void EVMAssembly::appendJump(int _stackDiffAfter) { - solAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); + yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); appendInstruction(dev::eth::Instruction::JUMP); m_stackHeight += _stackDiffAfter; } @@ -131,8 +130,8 @@ void EVMAssembly::appendJumpToIf(LabelID _labelId) void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments) { - solAssert(m_evm15, "BEGINSUB used for EVM 1.0"); - solAssert(_arguments >= 0, ""); + yulAssert(m_evm15, "BEGINSUB used for EVM 1.0"); + yulAssert(_arguments >= 0, ""); setLabelToCurrentPosition(_labelId); m_bytecode.push_back(uint8_t(dev::eth::Instruction::BEGINSUB)); m_stackHeight += _arguments; @@ -140,8 +139,8 @@ void EVMAssembly::appendBeginsub(LabelID _labelId, int _arguments) void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns) { - solAssert(m_evm15, "JUMPSUB used for EVM 1.0"); - solAssert(_arguments >= 0 && _returns >= 0, ""); + yulAssert(m_evm15, "JUMPSUB used for EVM 1.0"); + yulAssert(_arguments >= 0 && _returns >= 0, ""); m_bytecode.push_back(uint8_t(dev::eth::Instruction::JUMPSUB)); appendLabelReferenceInternal(_labelId); m_stackHeight += _returns - _arguments; @@ -149,8 +148,8 @@ void EVMAssembly::appendJumpsub(LabelID _labelId, int _arguments, int _returns) void EVMAssembly::appendReturnsub(int _returns, int _stackDiffAfter) { - solAssert(m_evm15, "RETURNSUB used for EVM 1.0"); - solAssert(_returns >= 0, ""); + yulAssert(m_evm15, "RETURNSUB used for EVM 1.0"); + yulAssert(_returns >= 0, ""); m_bytecode.push_back(uint8_t(dev::eth::Instruction::RETURNSUB)); m_stackHeight += _stackDiffAfter - _returns; } @@ -164,9 +163,9 @@ eth::LinkerObject EVMAssembly::finalize() for (auto const& ref: m_labelReferences) { size_t referencePos = ref.first; - solAssert(m_labelPositions.count(ref.second), ""); + yulAssert(m_labelPositions.count(ref.second), ""); size_t labelPos = m_labelPositions.at(ref.second); - solAssert(labelPos != size_t(-1), "Undefined but allocated label used."); + yulAssert(labelPos != size_t(-1), "Undefined but allocated label used."); updateReference(referencePos, labelReferenceSize, u256(labelPos)); } @@ -177,8 +176,8 @@ eth::LinkerObject EVMAssembly::finalize() void EVMAssembly::setLabelToCurrentPosition(LabelID _labelId) { - solAssert(m_labelPositions.count(_labelId), "Label not found."); - solAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set."); + yulAssert(m_labelPositions.count(_labelId), "Label not found."); + yulAssert(m_labelPositions[_labelId] == size_t(-1), "Label already set."); m_labelPositions[_labelId] = m_bytecode.size(); } @@ -197,29 +196,29 @@ void EVMAssembly::appendAssemblySize() pair, AbstractAssembly::SubID> EVMAssembly::createSubAssembly() { - solAssert(false, "Sub assemblies not implemented."); + yulAssert(false, "Sub assemblies not implemented."); return {}; } void EVMAssembly::appendDataOffset(AbstractAssembly::SubID) { - solAssert(false, "Data not implemented."); + yulAssert(false, "Data not implemented."); } void EVMAssembly::appendDataSize(AbstractAssembly::SubID) { - solAssert(false, "Data not implemented."); + yulAssert(false, "Data not implemented."); } AbstractAssembly::SubID EVMAssembly::appendData(bytes const&) { - solAssert(false, "Data not implemented."); + yulAssert(false, "Data not implemented."); } void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) { - solAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); - solAssert(value < (u256(1) << (8 * size)), ""); + yulAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); + yulAssert(value < (u256(1) << (8 * size)), ""); for (size_t i = 0; i < size; i++) m_bytecode[pos + i] = uint8_t((value >> (8 * (size - i - 1))) & 0xff); } diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index d3493b573..f3d76b002 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -29,6 +29,8 @@ #include +#include + using namespace std; using namespace dev; using namespace yul; @@ -42,9 +44,9 @@ void VariableReferenceCounter::operator()(FunctionDefinition const& _function) { Scope* originalScope = m_scope; - solAssert(m_info.virtualBlocks.at(&_function), ""); + yulAssert(m_info.virtualBlocks.at(&_function), ""); m_scope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); - solAssert(m_scope, "Variable scope does not exist."); + yulAssert(m_scope, "Variable scope does not exist."); for (auto const& v: _function.returnVariables) increaseRefIfFound(v.name); @@ -80,14 +82,14 @@ void VariableReferenceCounter::operator()(Block const& _block) void VariableReferenceCounter::increaseRefIfFound(YulString _variableName) { - m_scope->lookup(_variableName, Scope::Visitor( + m_scope->lookup(_variableName, GenericVisitor{ [=](Scope::Variable const& _var) { ++m_context.variableReferences[&_var]; }, [=](Scope::Label const&) { }, [=](Scope::Function const&) { } - )); + }); } CodeTransform::CodeTransform( @@ -129,7 +131,7 @@ void CodeTransform::decreaseReference(YulString, Scope::Variable const& _var) return; unsigned& ref = m_context->variableReferences.at(&_var); - solAssert(ref >= 1, ""); + yulAssert(ref >= 1, ""); --ref; if (ref == 0) m_variablesScheduledForDeletion.insert(&_var); @@ -146,16 +148,16 @@ void CodeTransform::freeUnusedVariables() return; for (auto const& identifier: m_scope->identifiers) - if (identifier.second.type() == typeid(Scope::Variable)) + if (holds_alternative(identifier.second)) { - Scope::Variable const& var = boost::get(identifier.second); + Scope::Variable const& var = std::get(identifier.second); if (m_variablesScheduledForDeletion.count(&var)) deleteVariable(var); } while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1)) { - solAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), ""); + yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), ""); m_assembly.appendInstruction(dev::eth::Instruction::POP); --m_stackAdjustment; } @@ -163,8 +165,8 @@ void CodeTransform::freeUnusedVariables() void CodeTransform::deleteVariable(Scope::Variable const& _var) { - solAssert(m_allowStackOpt, ""); - solAssert(m_context->variableStackHeights.count(&_var) > 0, ""); + yulAssert(m_allowStackOpt, ""); + yulAssert(m_context->variableStackHeights.count(&_var) > 0, ""); m_unusedStackSlots.insert(m_context->variableStackHeights[&_var]); m_context->variableStackHeights.erase(&_var); m_context->variableReferences.erase(&_var); @@ -173,13 +175,13 @@ void CodeTransform::deleteVariable(Scope::Variable const& _var) void CodeTransform::operator()(VariableDeclaration const& _varDecl) { - solAssert(m_scope, ""); + yulAssert(m_scope, ""); int const numVariables = _varDecl.variables.size(); int height = m_assembly.stackHeight(); if (_varDecl.value) { - boost::apply_visitor(*this, *_varDecl.value); + std::visit(*this, *_varDecl.value); expectDeposit(numVariables, height); } else @@ -193,7 +195,7 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl) for (int varIndex = numVariables - 1; varIndex >= 0; --varIndex) { YulString varName = _varDecl.variables[varIndex].name; - auto& var = boost::get(m_scope->identifiers.at(varName)); + auto& var = std::get(m_scope->identifiers.at(varName)); m_context->variableStackHeights[&var] = height + varIndex; if (!m_allowStackOpt) continue; @@ -242,7 +244,7 @@ void CodeTransform::stackError(StackTooDeepError _error, int _targetStackHeight) void CodeTransform::operator()(Assignment const& _assignment) { int height = m_assembly.stackHeight(); - boost::apply_visitor(*this, *_assignment.value); + std::visit(*this, *_assignment.value); expectDeposit(_assignment.variableNames.size(), height); m_assembly.setSourceLocation(_assignment.location); @@ -252,7 +254,7 @@ void CodeTransform::operator()(Assignment const& _assignment) void CodeTransform::operator()(StackAssignment const& _assignment) { - solAssert(!m_allowStackOpt, ""); + yulAssert(!m_allowStackOpt, ""); m_assembly.setSourceLocation(_assignment.location); generateAssignment(_assignment.variableName); checkStackHeight(&_assignment); @@ -261,24 +263,24 @@ void CodeTransform::operator()(StackAssignment const& _assignment) void CodeTransform::operator()(ExpressionStatement const& _statement) { m_assembly.setSourceLocation(_statement.location); - boost::apply_visitor(*this, _statement.expression); + std::visit(*this, _statement.expression); checkStackHeight(&_statement); } void CodeTransform::operator()(Label const& _label) { - solAssert(!m_allowStackOpt, ""); + yulAssert(!m_allowStackOpt, ""); m_assembly.setSourceLocation(_label.location); - solAssert(m_scope, ""); - solAssert(m_scope->identifiers.count(_label.name), ""); - Scope::Label& label = boost::get(m_scope->identifiers.at(_label.name)); + yulAssert(m_scope, ""); + yulAssert(m_scope->identifiers.count(_label.name), ""); + Scope::Label& label = std::get(m_scope->identifiers.at(_label.name)); m_assembly.appendLabel(labelID(label)); checkStackHeight(&_label); } void CodeTransform::operator()(FunctionCall const& _call) { - solAssert(m_scope, ""); + yulAssert(m_scope, ""); if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name)) { @@ -300,13 +302,13 @@ void CodeTransform::operator()(FunctionCall const& _call) } Scope::Function* function = nullptr; - solAssert(m_scope->lookup(_call.functionName.name, Scope::NonconstVisitor( - [=](Scope::Variable&) { solAssert(false, "Expected function name."); }, - [=](Scope::Label&) { solAssert(false, "Expected function name."); }, + yulAssert(m_scope->lookup(_call.functionName.name, GenericVisitor{ + [=](Scope::Variable&) { yulAssert(false, "Expected function name."); }, + [=](Scope::Label&) { yulAssert(false, "Expected function name."); }, [&](Scope::Function& _function) { function = &_function; } - )), "Function name not found."); - solAssert(function, ""); - solAssert(function->arguments.size() == _call.arguments.size(), ""); + }), "Function name not found."); + yulAssert(function, ""); + yulAssert(function->arguments.size() == _call.arguments.size(), ""); for (auto const& arg: _call.arguments | boost::adaptors::reversed) visitExpression(arg); m_assembly.setSourceLocation(_call.location); @@ -332,15 +334,15 @@ void CodeTransform::operator()(FunctionalInstruction const& _instruction) bool const isJumpI = _instruction.instruction == dev::eth::Instruction::JUMPI; if (isJumpI) { - solAssert(_instruction.arguments.size() == 2, ""); + yulAssert(_instruction.arguments.size() == 2, ""); visitExpression(_instruction.arguments.at(1)); } else { - solAssert(_instruction.arguments.size() == 1, ""); + yulAssert(_instruction.arguments.size() == 1, ""); } m_assembly.setSourceLocation(_instruction.location); - auto label = labelFromIdentifier(boost::get(_instruction.arguments.at(0))); + auto label = labelFromIdentifier(std::get(_instruction.arguments.at(0))); if (isJumpI) m_assembly.appendJumpToIf(label); else @@ -360,8 +362,8 @@ void CodeTransform::operator()(Identifier const& _identifier) { m_assembly.setSourceLocation(_identifier.location); // First search internals, then externals. - solAssert(m_scope, ""); - if (m_scope->lookup(_identifier.name, Scope::NonconstVisitor( + yulAssert(m_scope, ""); + if (m_scope->lookup(_identifier.name, GenericVisitor{ [=](Scope::Variable& _var) { // TODO: opportunity for optimization: Do not DUP if this is the last reference @@ -379,13 +381,13 @@ void CodeTransform::operator()(Identifier const& _identifier) }, [=](Scope::Function&) { - solAssert(false, "Function not removed during desugaring."); + yulAssert(false, "Function not removed during desugaring."); } - ))) + })) { return; } - solAssert( + yulAssert( m_identifierAccess.generateCode, "Identifier not found and no external access available." ); @@ -403,9 +405,9 @@ void CodeTransform::operator()(Literal const& _literal) void CodeTransform::operator()(yul::Instruction const& _instruction) { - solAssert(!m_allowStackOpt, ""); - solAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5"); - solAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5"); + yulAssert(!m_allowStackOpt, ""); + yulAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMP, "Bare JUMP instruction used for EVM1.5"); + yulAssert(!m_evm15 || _instruction.instruction != dev::eth::Instruction::JUMPI, "Bare JUMPI instruction used for EVM1.5"); m_assembly.setSourceLocation(_instruction.location); m_assembly.appendInstruction(_instruction.instruction); checkStackHeight(&_instruction); @@ -440,7 +442,7 @@ void CodeTransform::operator()(Switch const& _switch) m_assembly.setSourceLocation(c.location); AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId(); caseBodies[&c] = bodyLabel; - solAssert(m_assembly.stackHeight() == expressionHeight + 1, ""); + yulAssert(m_assembly.stackHeight() == expressionHeight + 1, ""); m_assembly.appendInstruction(dev::eth::dupInstruction(2)); m_assembly.appendInstruction(dev::eth::Instruction::EQ); m_assembly.appendJumpToIf(bodyLabel); @@ -474,18 +476,18 @@ void CodeTransform::operator()(Switch const& _switch) void CodeTransform::operator()(FunctionDefinition const& _function) { - solAssert(m_scope, ""); - solAssert(m_scope->identifiers.count(_function.name), ""); - Scope::Function& function = boost::get(m_scope->identifiers.at(_function.name)); + yulAssert(m_scope, ""); + yulAssert(m_scope->identifiers.count(_function.name), ""); + Scope::Function& function = std::get(m_scope->identifiers.at(_function.name)); int const localStackAdjustment = m_evm15 ? 0 : 1; int height = localStackAdjustment; - solAssert(m_info.scopes.at(&_function.body), ""); + yulAssert(m_info.scopes.at(&_function.body), ""); Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); - solAssert(varScope, ""); + yulAssert(varScope, ""); for (auto const& v: _function.parameters | boost::adaptors::reversed) { - auto& var = boost::get(varScope->identifiers.at(v.name)); + auto& var = std::get(varScope->identifiers.at(v.name)); m_context->variableStackHeights[&var] = height++; } @@ -503,7 +505,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) for (auto const& v: _function.returnVariables) { - auto& var = boost::get(varScope->identifiers.at(v.name)); + auto& var = std::get(varScope->identifiers.at(v.name)); m_context->variableStackHeights[&var] = height++; // Preset stack slots for return variables to zero. m_assembly.appendConstant(u256(0)); @@ -580,7 +582,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function) swap(stackLayout[stackLayout.back()], stackLayout.back()); } for (int i = 0; size_t(i) < stackLayout.size(); ++i) - solAssert(i == stackLayout[i], "Error reshuffling stack."); + yulAssert(i == stackLayout[i], "Error reshuffling stack."); } } if (m_evm15) @@ -680,16 +682,16 @@ void CodeTransform::operator()(Block const& _block) AbstractAssembly::LabelID CodeTransform::labelFromIdentifier(Identifier const& _identifier) { AbstractAssembly::LabelID label = AbstractAssembly::LabelID(-1); - if (!m_scope->lookup(_identifier.name, Scope::NonconstVisitor( - [=](Scope::Variable&) { solAssert(false, "Expected label"); }, + if (!m_scope->lookup(_identifier.name, GenericVisitor{ + [=](Scope::Variable&) { yulAssert(false, "Expected label"); }, [&](Scope::Label& _label) { label = labelID(_label); }, - [=](Scope::Function&) { solAssert(false, "Expected label"); } - ))) + [=](Scope::Function&) { yulAssert(false, "Expected label"); } + })) { - solAssert(false, "Identifier not found."); + yulAssert(false, "Identifier not found."); } return label; } @@ -717,7 +719,7 @@ AbstractAssembly::LabelID CodeTransform::functionEntryID(YulString _name, Scope: void CodeTransform::visitExpression(Expression const& _expression) { int height = m_assembly.stackHeight(); - boost::apply_visitor(*this, _expression); + std::visit(*this, _expression); expectDeposit(1, height); } @@ -728,7 +730,7 @@ void CodeTransform::visitStatements(vector const& _statements) for (auto const& statement: _statements) { freeUnusedVariables(); - auto const* functionDefinition = boost::get(&statement); + auto const* functionDefinition = std::get_if(&statement); if (functionDefinition && !jumpTarget) { m_assembly.setSourceLocation(locationOf(statement)); @@ -741,7 +743,7 @@ void CodeTransform::visitStatements(vector const& _statements) jumpTarget = std::nullopt; } - boost::apply_visitor(*this, statement); + std::visit(*this, statement); } // we may have a leftover jumpTarget if (jumpTarget) @@ -757,15 +759,15 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight freeUnusedVariables(); // pop variables - solAssert(m_info.scopes.at(&_block).get() == m_scope, ""); + yulAssert(m_info.scopes.at(&_block).get() == m_scope, ""); for (auto const& id: m_scope->identifiers) - if (id.second.type() == typeid(Scope::Variable)) + if (holds_alternative(id.second)) { - Scope::Variable const& var = boost::get(id.second); + Scope::Variable const& var = std::get(id.second); if (m_allowStackOpt) { - solAssert(!m_context->variableStackHeights.count(&var), ""); - solAssert(!m_context->variableReferences.count(&var), ""); + yulAssert(!m_context->variableStackHeights.count(&var), ""); + yulAssert(!m_context->variableReferences.count(&var), ""); m_stackAdjustment++; } else @@ -773,23 +775,23 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight } int deposit = m_assembly.stackHeight() - blockStartStackHeight; - solAssert(deposit == 0, "Invalid stack height at end of block: " + to_string(deposit)); + yulAssert(deposit == 0, "Invalid stack height at end of block: " + to_string(deposit)); checkStackHeight(&_block); } void CodeTransform::generateMultiAssignment(vector const& _variableNames) { - solAssert(m_scope, ""); + yulAssert(m_scope, ""); for (auto const& variableName: _variableNames | boost::adaptors::reversed) generateAssignment(variableName); } void CodeTransform::generateAssignment(Identifier const& _variableName) { - solAssert(m_scope, ""); + yulAssert(m_scope, ""); if (auto var = m_scope->lookup(_variableName.name)) { - Scope::Variable const& _var = boost::get(*var); + Scope::Variable const& _var = std::get(*var); if (int heightDiff = variableHeightDiff(_var, _variableName.name, true)) m_assembly.appendInstruction(dev::eth::swapInstruction(heightDiff - 1)); m_assembly.appendInstruction(dev::eth::Instruction::POP); @@ -797,7 +799,7 @@ void CodeTransform::generateAssignment(Identifier const& _variableName) } else { - solAssert( + yulAssert( m_identifierAccess.generateCode, "Identifier not found and no external access available." ); @@ -807,9 +809,9 @@ void CodeTransform::generateAssignment(Identifier const& _variableName) int CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString _varName, bool _forSwap) { - solAssert(m_context->variableStackHeights.count(&_var), ""); + yulAssert(m_context->variableStackHeights.count(&_var), ""); int heightDiff = m_assembly.stackHeight() - m_context->variableStackHeights[&_var]; - solAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable."); + yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable."); int limit = _forSwap ? 17 : 16; if (heightDiff > limit) { @@ -828,15 +830,15 @@ int CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulString _va void CodeTransform::expectDeposit(int _deposit, int _oldHeight) const { - solAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit."); + yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit."); } void CodeTransform::checkStackHeight(void const* _astElement) const { - solAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); + yulAssert(m_info.stackHeightInfo.count(_astElement), "Stack height for AST element not found."); int stackHeightInAnalysis = m_info.stackHeightInfo.at(_astElement); int stackHeightInCodegen = m_assembly.stackHeight() - m_stackAdjustment; - solAssert( + yulAssert( stackHeightInAnalysis == stackHeightInCodegen, "Stack height mismatch between analysis and code generation phase: Analysis: " + to_string(stackHeightInAnalysis) + diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index e44953807..fde041686 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -27,8 +27,6 @@ #include #include -#include - #include #include @@ -107,7 +105,7 @@ private: Scope* m_scope = nullptr; }; -class CodeTransform: public boost::static_visitor<> +class CodeTransform { public: /// Create the code transformer. diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index a9cbd2cbc..0da3e8d68 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -113,7 +113,7 @@ map createBuiltins(langutil::EVMVersion _evmVe yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); Expression const& arg = _call.arguments.front(); - YulString dataName = boost::get(arg).value; + YulString dataName = std::get(arg).value; if (_context.currentObject->name == dataName) _assembly.appendAssemblySize(); else @@ -134,7 +134,7 @@ map createBuiltins(langutil::EVMVersion _evmVe yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); Expression const& arg = _call.arguments.front(); - YulString dataName = boost::get(arg).value; + YulString dataName = std::get(arg).value; if (_context.currentObject->name == dataName) _assembly.appendConstant(0); else diff --git a/libyul/backends/evm/EVMMetrics.cpp b/libyul/backends/evm/EVMMetrics.cpp index 5211e5132..204014589 100644 --- a/libyul/backends/evm/EVMMetrics.cpp +++ b/libyul/backends/evm/EVMMetrics.cpp @@ -96,7 +96,7 @@ void GasMeterVisitor::operator()(Literal const& _lit) m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); m_dataGas += singleByteDataGas() + - size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); + size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation, m_dialect.evmVersion())); } void GasMeterVisitor::operator()(Identifier const&) @@ -108,7 +108,7 @@ void GasMeterVisitor::operator()(Identifier const&) size_t GasMeterVisitor::singleByteDataGas() const { if (m_isCreation) - return dev::eth::GasCosts::txDataNonZeroGas; + return dev::eth::GasCosts::txDataNonZeroGas(m_dialect.evmVersion()); else return dev::eth::GasCosts::createDataGas; } diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 4618d4e54..e2a34ef20 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -19,11 +19,10 @@ */ #include +#include #include -#include - using namespace std; using namespace dev; using namespace langutil; @@ -47,7 +46,7 @@ void NoOutputAssembly::appendLabel(LabelID) void NoOutputAssembly::appendLabelReference(LabelID) { - solAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); + yulAssert(!m_evm15, "Cannot use plain label references in EMV1.5 mode."); appendInstruction(dev::eth::pushInstruction(1)); } @@ -63,12 +62,12 @@ AbstractAssembly::LabelID NoOutputAssembly::namedLabel(string const&) void NoOutputAssembly::appendLinkerSymbol(string const&) { - solAssert(false, "Linker symbols not yet implemented."); + yulAssert(false, "Linker symbols not yet implemented."); } void NoOutputAssembly::appendJump(int _stackDiffAfter) { - solAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); + yulAssert(!m_evm15, "Plain JUMP used for EVM 1.5"); appendInstruction(dev::eth::Instruction::JUMP); m_stackHeight += _stackDiffAfter; } @@ -97,22 +96,22 @@ void NoOutputAssembly::appendJumpToIf(LabelID _labelId) void NoOutputAssembly::appendBeginsub(LabelID, int _arguments) { - solAssert(m_evm15, "BEGINSUB used for EVM 1.0"); - solAssert(_arguments >= 0, ""); + yulAssert(m_evm15, "BEGINSUB used for EVM 1.0"); + yulAssert(_arguments >= 0, ""); m_stackHeight += _arguments; } void NoOutputAssembly::appendJumpsub(LabelID, int _arguments, int _returns) { - solAssert(m_evm15, "JUMPSUB used for EVM 1.0"); - solAssert(_arguments >= 0 && _returns >= 0, ""); + yulAssert(m_evm15, "JUMPSUB used for EVM 1.0"); + yulAssert(_arguments >= 0 && _returns >= 0, ""); m_stackHeight += _returns - _arguments; } void NoOutputAssembly::appendReturnsub(int _returns, int _stackDiffAfter) { - solAssert(m_evm15, "RETURNSUB used for EVM 1.0"); - solAssert(_returns >= 0, ""); + yulAssert(m_evm15, "RETURNSUB used for EVM 1.0"); + yulAssert(_returns >= 0, ""); m_stackHeight += _stackDiffAfter - _returns; } @@ -123,7 +122,7 @@ void NoOutputAssembly::appendAssemblySize() pair, AbstractAssembly::SubID> NoOutputAssembly::createSubAssembly() { - solAssert(false, "Sub assemblies not implemented."); + yulAssert(false, "Sub assemblies not implemented."); return {}; } diff --git a/libyul/backends/wasm/BinaryTransform.cpp b/libyul/backends/wasm/BinaryTransform.cpp index 18f02ec02..563d4d569 100644 --- a/libyul/backends/wasm/BinaryTransform.cpp +++ b/libyul/backends/wasm/BinaryTransform.cpp @@ -298,12 +298,12 @@ bytes BinaryTransform::operator()(BuiltinCall const& _call) // they are references to object names that should not end up in the code. if (_call.functionName == "dataoffset") { - string name = boost::get(_call.arguments.at(0)).value; + string name = std::get(_call.arguments.at(0)).value; return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).first); } else if (_call.functionName == "datasize") { - string name = boost::get(_call.arguments.at(0)).value; + string name = std::get(_call.arguments.at(0)).value; return toBytes(Opcode::I64Const) + lebEncodeSigned(m_subModulePosAndSize.at(name).second); } @@ -333,7 +333,7 @@ bytes BinaryTransform::operator()(FunctionCall const& _call) bytes BinaryTransform::operator()(LocalAssignment const& _assignment) { return - boost::apply_visitor(*this, *_assignment.value) + + std::visit(*this, *_assignment.value) + toBytes(Opcode::LocalSet) + lebEncode(m_locals.at(_assignment.variableName)); } @@ -341,7 +341,7 @@ bytes BinaryTransform::operator()(LocalAssignment const& _assignment) bytes BinaryTransform::operator()(GlobalAssignment const& _assignment) { return - boost::apply_visitor(*this, *_assignment.value) + + std::visit(*this, *_assignment.value) + toBytes(Opcode::GlobalSet) + lebEncode(m_globals.at(_assignment.variableName)); } @@ -349,7 +349,7 @@ bytes BinaryTransform::operator()(GlobalAssignment const& _assignment) bytes BinaryTransform::operator()(If const& _if) { bytes result = - boost::apply_visitor(*this, *_if.condition) + + std::visit(*this, *_if.condition) + toBytes(Opcode::If) + toBytes(ValueType::Void); @@ -559,7 +559,7 @@ bytes BinaryTransform::visit(vector const& _expressions) { bytes result; for (auto const& expr: _expressions) - result += boost::apply_visitor(*this, expr); + result += std::visit(*this, expr); return result; } @@ -567,7 +567,7 @@ bytes BinaryTransform::visitReversed(vector const& _expressions) { bytes result; for (auto const& expr: _expressions | boost::adaptors::reversed) - result += boost::apply_visitor(*this, expr); + result += std::visit(*this, expr); return result; } diff --git a/libyul/backends/wasm/BinaryTransform.h b/libyul/backends/wasm/BinaryTransform.h index 35c611b21..fb1057489 100644 --- a/libyul/backends/wasm/BinaryTransform.h +++ b/libyul/backends/wasm/BinaryTransform.h @@ -35,7 +35,7 @@ namespace wasm /** * Web assembly to binary transform. */ -class BinaryTransform: public boost::static_visitor +class BinaryTransform { public: static dev::bytes run(Module const& _module); diff --git a/libyul/backends/wasm/EVMToEWasmTranslator.cpp b/libyul/backends/wasm/EVMToEWasmTranslator.cpp index f4de4bc84..6d4688c9a 100644 --- a/libyul/backends/wasm/EVMToEWasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEWasmTranslator.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -694,7 +695,7 @@ Object EVMToEWasmTranslator::run(Object const& _object) if (!m_polyfill) parsePolyfill(); - Block ast = boost::get(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code)); + Block ast = std::get(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code)); set reservedIdentifiers; NameDispenser nameDispenser{m_dialect, ast, reservedIdentifiers}; OptimiserStepContext context{m_dialect, nameDispenser, reservedIdentifiers}; @@ -702,6 +703,7 @@ Object EVMToEWasmTranslator::run(Object const& _object) FunctionHoister::run(context, ast); FunctionGrouper::run(context, ast); MainFunction{}(ast); + ForLoopConditionIntoBody::run(context, ast); ExpressionSplitter::run(context, ast); WordSizeTransform::run(m_dialect, ast, nameDispenser); @@ -752,6 +754,6 @@ void EVMToEWasmTranslator::parsePolyfill() m_polyfillFunctions.clear(); for (auto const& statement: m_polyfill->statements) - m_polyfillFunctions.insert(boost::get(statement).name); + m_polyfillFunctions.insert(std::get(statement).name); } diff --git a/libyul/backends/wasm/EWasmAST.h b/libyul/backends/wasm/EWasmAST.h index 677668ade..8e5bc9ea4 100644 --- a/libyul/backends/wasm/EWasmAST.h +++ b/libyul/backends/wasm/EWasmAST.h @@ -20,7 +20,7 @@ #pragma once -#include +#include #include #include #include @@ -44,7 +44,7 @@ struct If; struct Loop; struct Break; struct BreakIf; -using Expression = boost::variant< +using Expression = std::variant< Literal, StringLiteral, LocalVariable, GlobalVariable, FunctionCall, BuiltinCall, LocalAssignment, GlobalAssignment, Block, If, Loop, Break, BreakIf diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index 277df0df2..3503feef2 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -45,11 +45,11 @@ wasm::Module EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& for (auto const& statement: _ast.statements) { yulAssert( - statement.type() == typeid(yul::FunctionDefinition), + holds_alternative(statement), "Expected only function definitions at the highest level." ); - if (statement.type() == typeid(yul::FunctionDefinition)) - module.functions.emplace_back(transform.translateFunction(boost::get(statement))); + if (holds_alternative(statement)) + module.functions.emplace_back(transform.translateFunction(std::get(statement))); } for (auto& imp: transform.m_functionsToImport) @@ -157,7 +157,7 @@ wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call) { vector literals; for (auto const& arg: _call.arguments) - literals.emplace_back(wasm::StringLiteral{boost::get(arg).value.str()}); + literals.emplace_back(wasm::StringLiteral{std::get(arg).value.str()}); return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)}; } else @@ -303,12 +303,12 @@ wasm::Expression EWasmCodeTransform::operator()(Block const& _block) unique_ptr EWasmCodeTransform::visit(yul::Expression const& _expression) { - return make_unique(boost::apply_visitor(*this, _expression)); + return make_unique(std::visit(*this, _expression)); } wasm::Expression EWasmCodeTransform::visitReturnByValue(yul::Expression const& _expression) { - return boost::apply_visitor(*this, _expression); + return std::visit(*this, _expression); } vector EWasmCodeTransform::visit(vector const& _expressions) @@ -321,7 +321,7 @@ vector EWasmCodeTransform::visit(vector const wasm::Expression EWasmCodeTransform::visit(yul::Statement const& _statement) { - return boost::apply_visitor(*this, _statement); + return std::visit(*this, _statement); } vector EWasmCodeTransform::visit(vector const& _statements) diff --git a/libyul/backends/wasm/EWasmCodeTransform.h b/libyul/backends/wasm/EWasmCodeTransform.h index cb9927d20..7b4a248c9 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.h +++ b/libyul/backends/wasm/EWasmCodeTransform.h @@ -32,7 +32,7 @@ namespace yul { struct AsmAnalysisInfo; -class EWasmCodeTransform: public boost::static_visitor +class EWasmCodeTransform { public: static wasm::Module run(Dialect const& _dialect, yul::Block const& _ast); diff --git a/libyul/backends/wasm/EWasmToText.cpp b/libyul/backends/wasm/EWasmToText.cpp index 3971be1f6..cc8e4711d 100644 --- a/libyul/backends/wasm/EWasmToText.cpp +++ b/libyul/backends/wasm/EWasmToText.cpp @@ -171,7 +171,7 @@ string EWasmToText::transform(wasm::FunctionDefinition const& _function) string EWasmToText::visit(wasm::Expression const& _expression) { - return boost::apply_visitor(*this, _expression); + return std::visit(*this, _expression); } string EWasmToText::joinTransformed(vector const& _expressions, char _separator) diff --git a/libyul/backends/wasm/EWasmToText.h b/libyul/backends/wasm/EWasmToText.h index 6163fd80b..51b27c9c9 100644 --- a/libyul/backends/wasm/EWasmToText.h +++ b/libyul/backends/wasm/EWasmToText.h @@ -28,7 +28,7 @@ namespace yul { struct AsmAnalysisInfo; -class EWasmToText: public boost::static_visitor +class EWasmToText { public: std::string run(wasm::Module const& _module); diff --git a/libyul/backends/wasm/WordSizeTransform.cpp b/libyul/backends/wasm/WordSizeTransform.cpp index 81c80da99..0394252b5 100644 --- a/libyul/backends/wasm/WordSizeTransform.cpp +++ b/libyul/backends/wasm/WordSizeTransform.cpp @@ -25,6 +25,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -84,13 +85,13 @@ void WordSizeTransform::operator()(Block& _block) _block.statements, [&](Statement& _s) -> std::optional> { - if (_s.type() == typeid(VariableDeclaration)) + if (holds_alternative(_s)) { - VariableDeclaration& varDecl = boost::get(_s); + VariableDeclaration& varDecl = std::get(_s); // Special handling for datasize and dataoffset - they will only need one variable. - if (varDecl.value && varDecl.value->type() == typeid(FunctionCall)) - if (BuiltinFunction const* f = m_inputDialect.builtin(boost::get(*varDecl.value).functionName.name)) + if (varDecl.value && holds_alternative(*varDecl.value)) + if (BuiltinFunction const* f = m_inputDialect.builtin(std::get(*varDecl.value).functionName.name)) if (f->literalArguments) { yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); @@ -113,8 +114,8 @@ void WordSizeTransform::operator()(Block& _block) if ( !varDecl.value || - varDecl.value->type() == typeid(FunctionalInstruction) || - varDecl.value->type() == typeid(FunctionCall) + holds_alternative(*varDecl.value) || + holds_alternative(*varDecl.value) ) { if (varDecl.value) visit(*varDecl.value); @@ -122,8 +123,8 @@ void WordSizeTransform::operator()(Block& _block) return std::nullopt; } else if ( - varDecl.value->type() == typeid(Identifier) || - varDecl.value->type() == typeid(Literal) + holds_alternative(*varDecl.value) || + holds_alternative(*varDecl.value) ) { yulAssert(varDecl.variables.size() == 1, ""); @@ -143,14 +144,14 @@ void WordSizeTransform::operator()(Block& _block) else yulAssert(false, ""); } - else if (_s.type() == typeid(Assignment)) + else if (holds_alternative(_s)) { - Assignment& assignment = boost::get(_s); + Assignment& assignment = std::get(_s); yulAssert(assignment.value, ""); // Special handling for datasize and dataoffset - they will only need one variable. - if (assignment.value->type() == typeid(FunctionCall)) - if (BuiltinFunction const* f = m_inputDialect.builtin(boost::get(*assignment.value).functionName.name)) + if (holds_alternative(*assignment.value)) + if (BuiltinFunction const* f = m_inputDialect.builtin(std::get(*assignment.value).functionName.name)) if (f->literalArguments) { yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); @@ -172,8 +173,8 @@ void WordSizeTransform::operator()(Block& _block) } if ( - assignment.value->type() == typeid(FunctionalInstruction) || - assignment.value->type() == typeid(FunctionCall) + holds_alternative(*assignment.value) || + holds_alternative(*assignment.value) ) { if (assignment.value) visit(*assignment.value); @@ -181,8 +182,8 @@ void WordSizeTransform::operator()(Block& _block) return std::nullopt; } else if ( - assignment.value->type() == typeid(Identifier) || - assignment.value->type() == typeid(Literal) + holds_alternative(*assignment.value) || + holds_alternative(*assignment.value) ) { yulAssert(assignment.variableNames.size() == 1, ""); @@ -202,8 +203,8 @@ void WordSizeTransform::operator()(Block& _block) else yulAssert(false, ""); } - else if (_s.type() == typeid(Switch)) - return handleSwitch(boost::get(_s)); + else if (holds_alternative(_s)) + return handleSwitch(std::get(_s)); else visit(_s); return std::nullopt; @@ -342,7 +343,7 @@ std::vector WordSizeTransform::handleSwitch(Switch& _switch) } vector splitExpressions; for (auto const& expr: expandValue(*_switch.expression)) - splitExpressions.emplace_back(boost::get(*expr).name); + splitExpressions.emplace_back(std::get(*expr).name); ret += handleSwitchInternal( _switch.location, @@ -372,15 +373,15 @@ array WordSizeTransform::generateU64IdentifierNames(YulString cons array, 4> WordSizeTransform::expandValue(Expression const& _e) { array, 4> ret; - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - Identifier const& id = boost::get(_e); + Identifier const& id = std::get(_e); for (int i = 0; i < 4; i++) ret[i] = make_unique(Identifier{id.location, m_variableMapping.at(id.name)[i]}); } - else if (_e.type() == typeid(Literal)) + else if (holds_alternative(_e)) { - Literal const& lit = boost::get(_e); + Literal const& lit = std::get(_e); u256 val = valueOfLiteral(lit); for (int i = 3; i >= 0; i--) { diff --git a/libyul/backends/wasm/WordSizeTransform.h b/libyul/backends/wasm/WordSizeTransform.h index 21d586439..3bdfa631d 100644 --- a/libyul/backends/wasm/WordSizeTransform.h +++ b/libyul/backends/wasm/WordSizeTransform.h @@ -55,7 +55,7 @@ namespace yul * takes four u64 parameters and is supposed to return the logical disjunction * of them as a u64 value. If this name is already used somewhere, it is renamed. * - * Prerequisite: Disambiguator, ExpressionSplitter + * Prerequisite: Disambiguator, ForLoopConditionIntoBody, ExpressionSplitter */ class WordSizeTransform: public ASTModifier { diff --git a/libyul/optimiser/ASTCopier.cpp b/libyul/optimiser/ASTCopier.cpp index efe3ff854..53c14ad67 100644 --- a/libyul/optimiser/ASTCopier.cpp +++ b/libyul/optimiser/ASTCopier.cpp @@ -155,12 +155,12 @@ Statement ASTCopier::operator ()(Block const& _block) Expression ASTCopier::translate(Expression const& _expression) { - return _expression.apply_visitor(static_cast(*this)); + return std::visit(static_cast(*this), _expression); } Statement ASTCopier::translate(Statement const& _statement) { - return _statement.apply_visitor(static_cast(*this)); + return std::visit(static_cast(*this), _statement); } Block ASTCopier::translate(Block const& _block) diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index dab3e296a..a2840b7ff 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -24,8 +24,6 @@ #include -#include - #include #include #include @@ -34,7 +32,7 @@ namespace yul { -class ExpressionCopier: public boost::static_visitor +class ExpressionCopier { public: virtual ~ExpressionCopier() = default; @@ -44,7 +42,7 @@ public: virtual Expression operator()(FunctionCall const&) = 0; }; -class StatementCopier: public boost::static_visitor +class StatementCopier { public: virtual ~StatementCopier() = default; diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index 523cc8a92..88d5a02b7 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -94,12 +94,12 @@ void ASTWalker::operator()(Block const& _block) void ASTWalker::visit(Statement const& _st) { - boost::apply_visitor(*this, _st); + std::visit(*this, _st); } void ASTWalker::visit(Expression const& _e) { - boost::apply_visitor(*this, _e); + std::visit(*this, _e); } void ASTModifier::operator()(FunctionalInstruction& _instr) @@ -176,10 +176,10 @@ void ASTModifier::operator()(Block& _block) void ASTModifier::visit(Statement& _st) { - boost::apply_visitor(*this, _st); + std::visit(*this, _st); } void ASTModifier::visit(Expression& _e) { - boost::apply_visitor(*this, _e); + std::visit(*this, _e); } diff --git a/libyul/optimiser/ASTWalker.h b/libyul/optimiser/ASTWalker.h index 995f5fe21..47aab43f7 100644 --- a/libyul/optimiser/ASTWalker.h +++ b/libyul/optimiser/ASTWalker.h @@ -25,8 +25,6 @@ #include #include -#include - #include #include #include @@ -38,7 +36,7 @@ namespace yul /** * Generic AST walker. */ -class ASTWalker: public boost::static_visitor<> +class ASTWalker { public: virtual ~ASTWalker() = default; @@ -75,7 +73,7 @@ protected: /** * Generic AST modifier (i.e. non-const version of ASTWalker). */ -class ASTModifier: public boost::static_visitor<> +class ASTModifier { public: virtual ~ASTModifier() = default; diff --git a/libyul/optimiser/BlockFlattener.cpp b/libyul/optimiser/BlockFlattener.cpp index 48816c695..f93d6e082 100644 --- a/libyul/optimiser/BlockFlattener.cpp +++ b/libyul/optimiser/BlockFlattener.cpp @@ -32,8 +32,8 @@ void BlockFlattener::operator()(Block& _block) _block.statements, [](Statement& _s) -> std::optional> { - if (_s.type() == typeid(Block)) - return std::move(boost::get(_s).statements); + if (holds_alternative(_s)) + return std::move(std::get(_s).statements); else return {}; } diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp index 0c11464ee..b1dc62f97 100644 --- a/libyul/optimiser/CallGraphGenerator.cpp +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -29,7 +29,7 @@ using namespace std; using namespace dev; using namespace yul; -map> CallGraphGenerator::callGraph(Block const& _ast) +CallGraph CallGraphGenerator::callGraph(Block const& _ast) { CallGraphGenerator gen; gen(_ast); @@ -40,28 +40,33 @@ void CallGraphGenerator::operator()(FunctionalInstruction const& _functionalInst { string name = dev::eth::instructionInfo(_functionalInstruction.instruction).name; std::transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); - m_callGraph[m_currentFunction].insert(YulString{name}); + m_callGraph.functionCalls[m_currentFunction].insert(YulString{name}); ASTWalker::operator()(_functionalInstruction); } void CallGraphGenerator::operator()(FunctionCall const& _functionCall) { - m_callGraph[m_currentFunction].insert(_functionCall.functionName.name); + m_callGraph.functionCalls[m_currentFunction].insert(_functionCall.functionName.name); ASTWalker::operator()(_functionCall); } +void CallGraphGenerator::operator()(ForLoop const&) +{ + m_callGraph.functionsWithLoops.insert(m_currentFunction); +} + void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition) { YulString previousFunction = m_currentFunction; m_currentFunction = _functionDefinition.name; - yulAssert(m_callGraph.count(m_currentFunction) == 0, ""); - m_callGraph[m_currentFunction] = {}; + yulAssert(m_callGraph.functionCalls.count(m_currentFunction) == 0, ""); + m_callGraph.functionCalls[m_currentFunction] = {}; ASTWalker::operator()(_functionDefinition); m_currentFunction = previousFunction; } CallGraphGenerator::CallGraphGenerator() { - m_callGraph[YulString{}] = {}; + m_callGraph.functionCalls[YulString{}] = {}; } diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h index 4dc420900..fb7e8d52c 100644 --- a/libyul/optimiser/CallGraphGenerator.h +++ b/libyul/optimiser/CallGraphGenerator.h @@ -31,25 +31,34 @@ namespace yul { +struct CallGraph +{ + std::map> functionCalls; + std::set functionsWithLoops; +}; + /** * Specific AST walker that generates the call graph. * + * It also generates information about which functions contain for loops. + * * The outermost (non-function) context is denoted by the empty string. */ class CallGraphGenerator: public ASTWalker { public: - static std::map> callGraph(Block const& _ast); + static CallGraph callGraph(Block const& _ast); using ASTWalker::operator(); void operator()(FunctionalInstruction const& _functionalInstruction) override; void operator()(FunctionCall const& _functionCall) override; + void operator()(ForLoop const& _forLoop) override; void operator()(FunctionDefinition const& _functionDefinition) override; private: CallGraphGenerator(); - std::map> m_callGraph; + CallGraph m_callGraph; /// The name of the function we are currently visiting during traversal. YulString m_currentFunction; }; diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index 65e0bae02..5baea5eb2 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -56,8 +56,8 @@ void CommonSubexpressionEliminator::visit(Expression& _e) bool descend = true; // If this is a function call to a function that requires literal arguments, // do not try to simplify there. - if (_e.type() == typeid(FunctionCall)) - if (BuiltinFunction const* builtin = m_dialect.builtin(boost::get(_e).functionName.name)) + if (holds_alternative(_e)) + if (BuiltinFunction const* builtin = m_dialect.builtin(std::get(_e).functionName.name)) if (builtin->literalArguments) // We should not modify function arguments that have to be literals // Note that replacing the function call entirely is fine, @@ -72,16 +72,16 @@ void CommonSubexpressionEliminator::visit(Expression& _e) if (descend) DataFlowAnalyzer::visit(_e); - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - Identifier& identifier = boost::get(_e); + Identifier& identifier = std::get(_e); YulString name = identifier.name; if (m_value.count(name)) { assertThrow(m_value.at(name), OptimizerException, ""); - if (m_value.at(name)->type() == typeid(Identifier)) + if (holds_alternative(*m_value.at(name))) { - YulString value = boost::get(*m_value.at(name)).name; + YulString value = std::get(*m_value.at(name)).name; assertThrow(inScope(value), OptimizerException, ""); _e = Identifier{locationOf(_e), value}; } diff --git a/libyul/optimiser/ConditionalSimplifier.cpp b/libyul/optimiser/ConditionalSimplifier.cpp index d720ae88d..8d811233e 100644 --- a/libyul/optimiser/ConditionalSimplifier.cpp +++ b/libyul/optimiser/ConditionalSimplifier.cpp @@ -29,12 +29,12 @@ using namespace yul; void ConditionalSimplifier::operator()(Switch& _switch) { visit(*_switch.expression); - if (_switch.expression->type() != typeid(Identifier)) + if (!holds_alternative(*_switch.expression)) { ASTModifier::operator()(_switch); return; } - YulString expr = boost::get(*_switch.expression).name; + YulString expr = std::get(*_switch.expression).name; for (auto& _case: _switch.cases) { if (_case.value) @@ -59,17 +59,17 @@ void ConditionalSimplifier::operator()(Block& _block) [&](Statement& _s) -> std::optional> { visit(_s); - if (_s.type() == typeid(If)) + if (holds_alternative(_s)) { - If& _if = boost::get(_s); + If& _if = std::get(_s); if ( - _if.condition->type() == typeid(Identifier) && + holds_alternative(*_if.condition) && !_if.body.statements.empty() && TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) != TerminationFinder::ControlFlow::FlowOut ) { - YulString condition = boost::get(*_if.condition).name; + YulString condition = std::get(*_if.condition).name; langutil::SourceLocation location = _if.location; return make_vector( std::move(_s), diff --git a/libyul/optimiser/ConditionalUnsimplifier.cpp b/libyul/optimiser/ConditionalUnsimplifier.cpp index 224634017..a405205a3 100644 --- a/libyul/optimiser/ConditionalUnsimplifier.cpp +++ b/libyul/optimiser/ConditionalUnsimplifier.cpp @@ -29,12 +29,12 @@ using namespace yul; void ConditionalUnsimplifier::operator()(Switch& _switch) { visit(*_switch.expression); - if (_switch.expression->type() != typeid(Identifier)) + if (!holds_alternative(*_switch.expression)) { ASTModifier::operator()(_switch); return; } - YulString expr = boost::get(*_switch.expression).name; + YulString expr = std::get(*_switch.expression).name; for (auto& _case: _switch.cases) { if (_case.value) @@ -42,15 +42,15 @@ void ConditionalUnsimplifier::operator()(Switch& _switch) (*this)(*_case.value); if ( !_case.body.statements.empty() && - _case.body.statements.front().type() == typeid(Assignment) + holds_alternative(_case.body.statements.front()) ) { - Assignment const& assignment = boost::get(_case.body.statements.front()); + Assignment const& assignment = std::get(_case.body.statements.front()); if ( assignment.variableNames.size() == 1 && assignment.variableNames.front().name == expr && - assignment.value->type() == typeid(Literal) && - valueOfLiteral(boost::get(*assignment.value)) == valueOfLiteral(*_case.value) + holds_alternative(*assignment.value) && + valueOfLiteral(std::get(*assignment.value)) == valueOfLiteral(*_case.value) ) _case.body.statements.erase(_case.body.statements.begin()); } @@ -66,27 +66,27 @@ void ConditionalUnsimplifier::operator()(Block& _block) _block.statements, [&](Statement& _stmt1, Statement& _stmt2) -> std::optional> { - if (_stmt1.type() == typeid(If)) + if (holds_alternative(_stmt1)) { - If& _if = boost::get(_stmt1); + If& _if = std::get(_stmt1); if ( - _if.condition->type() == typeid(Identifier) && + holds_alternative(*_if.condition) && !_if.body.statements.empty() ) { - YulString condition = boost::get(*_if.condition).name; + YulString condition = std::get(*_if.condition).name; if ( - _stmt2.type() == typeid(Assignment) && + holds_alternative(_stmt2) && TerminationFinder(m_dialect).controlFlowKind(_if.body.statements.back()) != TerminationFinder::ControlFlow::FlowOut ) { - Assignment const& assignment = boost::get(_stmt2); + Assignment const& assignment = std::get(_stmt2); if ( assignment.variableNames.size() == 1 && assignment.variableNames.front().name == condition && - assignment.value->type() == typeid(Literal) && - valueOfLiteral(boost::get(*assignment.value)) == 0 + holds_alternative(*assignment.value) && + valueOfLiteral(std::get(*assignment.value)) == 0 ) return {make_vector(std::move(_stmt1))}; } diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index 99fa4b137..4ebb35871 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -138,9 +138,9 @@ void ControlFlowSimplifier::operator()(Block& _block) void ControlFlowSimplifier::visit(Statement& _st) { - if (_st.type() == typeid(ForLoop)) + if (holds_alternative(_st)) { - ForLoop& forLoop = boost::get(_st); + ForLoop& forLoop = std::get(_st); yulAssert(forLoop.pre.statements.empty(), ""); size_t outerBreak = m_numBreakStatements; @@ -180,7 +180,8 @@ void ControlFlowSimplifier::visit(Statement& _st) void ControlFlowSimplifier::simplify(std::vector& _statements) { - GenericFallbackReturnsVisitor const visitor( + GenericVisitor visitor{ + VisitorFallback{}, [&](If& _ifStmt) -> OptionalStatements { if (_ifStmt.body.statements.empty() && m_dialect.discardFunction()) { @@ -205,13 +206,12 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) return {}; } - ); - + }; iterateReplacing( _statements, [&](Statement& _stmt) -> OptionalStatements { - OptionalStatements result = boost::apply_visitor(visitor, _stmt); + OptionalStatements result = std::visit(visitor, _stmt); if (result) simplify(*result); else diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 6cf65ff25..9696e5e3f 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -32,6 +32,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -364,19 +365,19 @@ std::optional> DataFlowAnalyzer::isSimpleStore( _store == dev::eth::Instruction::SSTORE, "" ); - if (_statement.expression.type() == typeid(FunctionCall)) + if (holds_alternative(_statement.expression)) { - FunctionCall const& funCall = boost::get(_statement.expression); + FunctionCall const& funCall = std::get(_statement.expression); if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) if (auto const* builtin = dialect->builtin(funCall.functionName.name)) if (builtin->instruction == _store) if ( - funCall.arguments.at(0).type() == typeid(Identifier) && - funCall.arguments.at(1).type() == typeid(Identifier) + holds_alternative(funCall.arguments.at(0)) && + holds_alternative(funCall.arguments.at(1)) ) { - YulString key = boost::get(funCall.arguments.at(0)).name; - YulString value = boost::get(funCall.arguments.at(1)).name; + YulString key = std::get(funCall.arguments.at(0)).name; + YulString value = std::get(funCall.arguments.at(1)).name; return make_pair(key, value); } } diff --git a/libyul/optimiser/DeadCodeEliminator.cpp b/libyul/optimiser/DeadCodeEliminator.cpp index 95e1cf284..a2daf9452 100644 --- a/libyul/optimiser/DeadCodeEliminator.cpp +++ b/libyul/optimiser/DeadCodeEliminator.cpp @@ -56,7 +56,7 @@ void DeadCodeEliminator::operator()(Block& _block) remove_if( _block.statements.begin() + index + 1, _block.statements.end(), - [] (Statement const& _s) { return _s.type() != typeid(yul::FunctionDefinition); } + [] (Statement const& _s) { return !holds_alternative(_s); } ), _block.statements.end() ); diff --git a/libyul/optimiser/Disambiguator.h b/libyul/optimiser/Disambiguator.h index cd3ff49a2..14b7b7cc3 100644 --- a/libyul/optimiser/Disambiguator.h +++ b/libyul/optimiser/Disambiguator.h @@ -25,8 +25,6 @@ #include #include -#include - #include #include diff --git a/libyul/optimiser/ExpressionInliner.cpp b/libyul/optimiser/ExpressionInliner.cpp index 5b1064cc8..0288b92f2 100644 --- a/libyul/optimiser/ExpressionInliner.cpp +++ b/libyul/optimiser/ExpressionInliner.cpp @@ -49,9 +49,9 @@ void ExpressionInliner::operator()(FunctionDefinition& _fun) void ExpressionInliner::visit(Expression& _expression) { ASTModifier::visit(_expression); - if (_expression.type() == typeid(FunctionCall)) + if (holds_alternative(_expression)) { - FunctionCall& funCall = boost::get(_expression); + FunctionCall& funCall = std::get(_expression); if (!m_inlinableFunctions.count(funCall.functionName.name)) return; FunctionDefinition const& fun = *m_inlinableFunctions.at(funCall.functionName.name); @@ -74,6 +74,6 @@ void ExpressionInliner::visit(Expression& _expression) substitutions[paraName] = &arg; } - _expression = Substitution(substitutions).translate(*boost::get(fun.body.statements.front()).value); + _expression = Substitution(substitutions).translate(*std::get(fun.body.statements.front()).value); } } diff --git a/libyul/optimiser/ExpressionInliner.h b/libyul/optimiser/ExpressionInliner.h index 9404f3e73..b08df86c0 100644 --- a/libyul/optimiser/ExpressionInliner.h +++ b/libyul/optimiser/ExpressionInliner.h @@ -22,9 +22,7 @@ #include #include -#include #include - #include namespace yul diff --git a/libyul/optimiser/ExpressionJoiner.cpp b/libyul/optimiser/ExpressionJoiner.cpp index 911d44de0..c991b5b4a 100644 --- a/libyul/optimiser/ExpressionJoiner.cpp +++ b/libyul/optimiser/ExpressionJoiner.cpp @@ -66,12 +66,12 @@ void ExpressionJoiner::operator()(Block& _block) void ExpressionJoiner::visit(Expression& _e) { - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - Identifier const& identifier = boost::get(_e); + Identifier const& identifier = std::get(_e); if (isLatestStatementVarDeclJoinable(identifier)) { - VariableDeclaration& varDecl = boost::get(*latestStatement()); + VariableDeclaration& varDecl = std::get(*latestStatement()); _e = std::move(*varDecl.value); // Delete the variable declaration (also get the moved-from structure back into a sane state) @@ -101,7 +101,7 @@ void ExpressionJoiner::handleArguments(vector& _arguments) for (Expression const& arg: _arguments | boost::adaptors::reversed) { --i; - if (arg.type() != typeid(Identifier) && arg.type() != typeid(Literal)) + if (!holds_alternative(arg) && !holds_alternative(arg)) break; } // i points to the last element that is neither an identifier nor a literal, @@ -138,9 +138,9 @@ Statement* ExpressionJoiner::latestStatement() bool ExpressionJoiner::isLatestStatementVarDeclJoinable(Identifier const& _identifier) { Statement const* statement = latestStatement(); - if (!statement || statement->type() != typeid(VariableDeclaration)) + if (!statement || !holds_alternative(*statement)) return false; - VariableDeclaration const& varDecl = boost::get(*statement); + VariableDeclaration const& varDecl = std::get(*statement); if (varDecl.variables.size() != 1 || !varDecl.value) return false; assertThrow(varDecl.variables.size() == 1, OptimizerException, ""); diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index 86fb6206f..c0759126e 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -101,7 +101,7 @@ void ExpressionSplitter::operator()(Block& _block) void ExpressionSplitter::outlineExpression(Expression& _expr) { - if (_expr.type() == typeid(Identifier)) + if (holds_alternative(_expr)) return; visit(_expr); diff --git a/libyul/optimiser/ForLoopConditionIntoBody.cpp b/libyul/optimiser/ForLoopConditionIntoBody.cpp index a3c213a05..d232ecbe9 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.cpp +++ b/libyul/optimiser/ForLoopConditionIntoBody.cpp @@ -33,8 +33,8 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) { if ( m_dialect.booleanNegationFunction() && - _forLoop.condition->type() != typeid(Literal) && - _forLoop.condition->type() != typeid(Identifier) + !holds_alternative(*_forLoop.condition) && + !holds_alternative(*_forLoop.condition) ) { langutil::SourceLocation loc = locationOf(*_forLoop.condition); diff --git a/libyul/optimiser/ForLoopConditionOutOfBody.cpp b/libyul/optimiser/ForLoopConditionOutOfBody.cpp index e3d0f13ff..e7f93b8b6 100644 --- a/libyul/optimiser/ForLoopConditionOutOfBody.cpp +++ b/libyul/optimiser/ForLoopConditionOutOfBody.cpp @@ -36,17 +36,17 @@ void ForLoopConditionOutOfBody::operator()(ForLoop& _forLoop) if ( !m_dialect.booleanNegationFunction() || - _forLoop.condition->type() != typeid(Literal) || - valueOfLiteral(boost::get(*_forLoop.condition)) == u256(0) || + !holds_alternative(*_forLoop.condition) || + valueOfLiteral(std::get(*_forLoop.condition)) == u256(0) || _forLoop.body.statements.empty() || - _forLoop.body.statements.front().type() != typeid(If) + !holds_alternative(_forLoop.body.statements.front()) ) return; - If& firstStatement = boost::get(_forLoop.body.statements.front()); + If& firstStatement = std::get(_forLoop.body.statements.front()); if ( firstStatement.body.statements.empty() || - firstStatement.body.statements.front().type() != typeid(Break) + !holds_alternative(firstStatement.body.statements.front()) ) return; if (!SideEffectsCollector(m_dialect, *firstStatement.condition).movable()) @@ -56,10 +56,10 @@ void ForLoopConditionOutOfBody::operator()(ForLoop& _forLoop) langutil::SourceLocation location = locationOf(*firstStatement.condition); if ( - firstStatement.condition->type() == typeid(FunctionCall) && - boost::get(*firstStatement.condition).functionName.name == iszero + holds_alternative(*firstStatement.condition) && + std::get(*firstStatement.condition).functionName.name == iszero ) - _forLoop.condition = make_unique(std::move(boost::get(*firstStatement.condition).arguments.front())); + _forLoop.condition = make_unique(std::move(std::get(*firstStatement.condition).arguments.front())); else _forLoop.condition = make_unique(FunctionCall{ location, diff --git a/libyul/optimiser/ForLoopInitRewriter.cpp b/libyul/optimiser/ForLoopInitRewriter.cpp index a11215e50..ff2786d6d 100644 --- a/libyul/optimiser/ForLoopInitRewriter.cpp +++ b/libyul/optimiser/ForLoopInitRewriter.cpp @@ -29,9 +29,9 @@ void ForLoopInitRewriter::operator()(Block& _block) _block.statements, [&](Statement& _stmt) -> std::optional> { - if (_stmt.type() == typeid(ForLoop)) + if (holds_alternative(_stmt)) { - auto& forLoop = boost::get(_stmt); + auto& forLoop = std::get(_stmt); (*this)(forLoop.pre); (*this)(forLoop.body); (*this)(forLoop.post); diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 8b0af2691..75bbb49c3 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -50,7 +50,7 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): SSAValueTracker tracker; tracker(m_ast); for (auto const& ssaValue: tracker.values()) - if (ssaValue.second && ssaValue.second->type() == typeid(Literal)) + if (ssaValue.second && holds_alternative(*ssaValue.second)) m_constants.emplace(ssaValue.first); // Store size of global statements. @@ -58,9 +58,9 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): map references = ReferencesCounter::countReferences(m_ast); for (auto& statement: m_ast.statements) { - if (statement.type() != typeid(FunctionDefinition)) + if (!holds_alternative(statement)) continue; - FunctionDefinition& fun = boost::get(statement); + FunctionDefinition& fun = std::get(statement); m_functions[fun.name] = &fun; // Always inline functions that are only called once. if (references[fun.name] == 1) @@ -72,8 +72,8 @@ FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): void FullInliner::run() { for (auto& statement: m_ast.statements) - if (statement.type() == typeid(Block)) - handleBlock({}, boost::get(statement)); + if (holds_alternative(statement)) + handleBlock({}, std::get(statement)); // TODO it might be good to determine a visiting order: // first handle functions that are called from many places. @@ -112,9 +112,9 @@ bool FullInliner::shallInline(FunctionCall const& _funCall, YulString _callSite) // Constant arguments might provide a means for further optimization, so they cause a bonus. bool constantArg = false; for (auto const& argument: _funCall.arguments) - if (argument.type() == typeid(Literal) || ( - argument.type() == typeid(Identifier) && - m_constants.count(boost::get(argument).name) + if (holds_alternative(argument) || ( + holds_alternative(argument) && + m_constants.count(std::get(argument).name) )) { constantArg = true; @@ -157,17 +157,19 @@ void InlineModifier::operator()(Block& _block) std::optional> InlineModifier::tryInlineStatement(Statement& _statement) { // Only inline for expression statements, assignments and variable declarations. - Expression* e = boost::apply_visitor(GenericFallbackReturnsVisitor( + Expression* e = std::visit(GenericVisitor{ + VisitorFallback{}, [](ExpressionStatement& _s) { return &_s.expression; }, [](Assignment& _s) { return _s.value.get(); }, [](VariableDeclaration& _s) { return _s.value.get(); } - ), _statement); + }, _statement); if (e) { // Only inline direct function calls. - FunctionCall* funCall = boost::apply_visitor(GenericFallbackReturnsVisitor( + FunctionCall* funCall = std::visit(GenericVisitor{ + VisitorFallback{}, [](FunctionCall& _e) { return &_e; } - ), *e); + }, *e); if (funCall && m_driver.shallInline(*funCall, m_currentFunction)) return performInline(_statement, *funCall); } @@ -203,9 +205,10 @@ vector InlineModifier::performInline(Statement& _statement, FunctionC newVariable(var, nullptr); Statement newBody = BodyCopier(m_nameDispenser, variableReplacements)(function->body); - newStatements += std::move(boost::get(newBody).statements); + newStatements += std::move(std::get(newBody).statements); - boost::apply_visitor(GenericFallbackVisitor{ + std::visit(GenericVisitor{ + VisitorFallback<>{}, [&](Assignment& _assignment) { for (size_t i = 0; i < _assignment.variableNames.size(); ++i) diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index 746cc46db..21fdb3552 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -29,8 +29,6 @@ #include -#include - #include #include diff --git a/libyul/optimiser/FunctionGrouper.cpp b/libyul/optimiser/FunctionGrouper.cpp index a8d7982b4..b6a79265a 100644 --- a/libyul/optimiser/FunctionGrouper.cpp +++ b/libyul/optimiser/FunctionGrouper.cpp @@ -40,10 +40,10 @@ void FunctionGrouper::operator()(Block& _block) for (auto&& statement: _block.statements) { - if (statement.type() == typeid(FunctionDefinition)) + if (holds_alternative(statement)) reordered.emplace_back(std::move(statement)); else - boost::get(reordered.front()).statements.emplace_back(std::move(statement)); + std::get(reordered.front()).statements.emplace_back(std::move(statement)); } _block.statements = std::move(reordered); } @@ -52,10 +52,10 @@ bool FunctionGrouper::alreadyGrouped(Block const& _block) { if (_block.statements.empty()) return false; - if (_block.statements.front().type() != typeid(Block)) + if (!holds_alternative(_block.statements.front())) return false; for (size_t i = 1; i < _block.statements.size(); ++i) - if (_block.statements.at(i).type() != typeid(FunctionDefinition)) + if (!holds_alternative(_block.statements.at(i))) return false; return true; } diff --git a/libyul/optimiser/FunctionHoister.cpp b/libyul/optimiser/FunctionHoister.cpp index 6ed930fcc..d2981691d 100644 --- a/libyul/optimiser/FunctionHoister.cpp +++ b/libyul/optimiser/FunctionHoister.cpp @@ -36,8 +36,8 @@ void FunctionHoister::operator()(Block& _block) m_isTopLevel = false; for (auto&& statement: _block.statements) { - boost::apply_visitor(*this, statement); - if (statement.type() == typeid(FunctionDefinition)) + std::visit(*this, statement); + if (holds_alternative(statement)) { m_functions.emplace_back(std::move(statement)); statement = Block{_block.location, {}}; diff --git a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp index f72e5357c..33309009e 100644 --- a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp +++ b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp @@ -45,9 +45,9 @@ void InlinableExpressionFunctionFinder::operator()(FunctionDefinition const& _fu { YulString retVariable = _function.returnVariables.front().name; Statement const& bodyStatement = _function.body.statements.front(); - if (bodyStatement.type() == typeid(Assignment)) + if (holds_alternative(bodyStatement)) { - Assignment const& assignment = boost::get(bodyStatement); + Assignment const& assignment = std::get(bodyStatement); if (assignment.variableNames.size() == 1 && assignment.variableNames.front().name == retVariable) { // TODO: use code size metric here @@ -57,7 +57,7 @@ void InlinableExpressionFunctionFinder::operator()(FunctionDefinition const& _fu // function body. assertThrow(m_disallowedIdentifiers.empty() && !m_foundDisallowedIdentifier, OptimizerException, ""); m_disallowedIdentifiers = set{retVariable, _function.name}; - boost::apply_visitor(*this, *assignment.value); + std::visit(*this, *assignment.value); if (!m_foundDisallowedIdentifier) m_inlinableFunctions[_function.name] = &_function; m_disallowedIdentifiers.clear(); diff --git a/libyul/optimiser/KnowledgeBase.cpp b/libyul/optimiser/KnowledgeBase.cpp index 2c13ac05b..8d488688e 100644 --- a/libyul/optimiser/KnowledgeBase.cpp +++ b/libyul/optimiser/KnowledgeBase.cpp @@ -27,6 +27,9 @@ #include +#include + +using namespace std; using namespace yul; using namespace dev; @@ -37,12 +40,12 @@ bool KnowledgeBase::knownToBeDifferent(YulString _a, YulString _b) // If that fails, try `eq(_a, _b)`. Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); - if (expr1.type() == typeid(Literal)) - return valueOfLiteral(boost::get(expr1)) != 0; + if (holds_alternative(expr1)) + return valueOfLiteral(std::get(expr1)) != 0; Expression expr2 = simplify(FunctionCall{{}, {{}, "eq"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); - if (expr2.type() == typeid(Literal)) - return valueOfLiteral(boost::get(expr2)) == 0; + if (holds_alternative(expr2)) + return valueOfLiteral(std::get(expr2)) == 0; return false; } @@ -53,9 +56,9 @@ bool KnowledgeBase::knownToBeDifferentByAtLeast32(YulString _a, YulString _b) // current values to turn `sub(_a, _b)` into a constant whose absolute value is at least 32. Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); - if (expr1.type() == typeid(Literal)) + if (holds_alternative(expr1)) { - u256 val = valueOfLiteral(boost::get(expr1)); + u256 val = valueOfLiteral(std::get(expr1)); return val >= 32 && val <= u256(0) - 32; } @@ -74,11 +77,11 @@ Expression KnowledgeBase::simplify(Expression _expression) else --m_recursionCounter; - if (_expression.type() == typeid(FunctionCall)) - for (Expression& arg: boost::get(_expression).arguments) + if (holds_alternative(_expression)) + for (Expression& arg: std::get(_expression).arguments) arg = simplify(arg); - else if (_expression.type() == typeid(FunctionalInstruction)) - for (Expression& arg: boost::get(_expression).arguments) + else if (holds_alternative(_expression)) + for (Expression& arg: std::get(_expression).arguments) arg = simplify(arg); if (auto match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_variableValues)) diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp index 184214254..df625b513 100644 --- a/libyul/optimiser/LoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -48,16 +48,16 @@ void LoadResolver::visit(Expression& _e) if (!dynamic_cast(&m_dialect)) return; - if (_e.type() == typeid(FunctionCall)) + if (holds_alternative(_e)) { - FunctionCall const& funCall = boost::get(_e); + FunctionCall const& funCall = std::get(_e); if (auto const* builtin = dynamic_cast(m_dialect).builtin(funCall.functionName.name)) if (builtin->instruction) tryResolve(_e, *builtin->instruction, funCall.arguments); } - else if (_e.type() == typeid(FunctionalInstruction)) + else if (holds_alternative(_e)) { - FunctionalInstruction const& instruction = boost::get(_e); + FunctionalInstruction const& instruction = std::get(_e); tryResolve(_e, instruction.instruction, instruction.arguments); } } @@ -68,10 +68,10 @@ void LoadResolver::tryResolve( vector const& _arguments ) { - if (_arguments.empty() || _arguments.at(0).type() != typeid(Identifier)) + if (_arguments.empty() || !holds_alternative(_arguments.at(0))) return; - YulString key = boost::get(_arguments.at(0)).name; + YulString key = std::get(_arguments.at(0)).name; if ( _instruction == dev::eth::Instruction::SLOAD && m_storage.values.count(key) diff --git a/libyul/optimiser/LoopInvariantCodeMotion.cpp b/libyul/optimiser/LoopInvariantCodeMotion.cpp new file mode 100644 index 000000000..00375a71d --- /dev/null +++ b/libyul/optimiser/LoopInvariantCodeMotion.cpp @@ -0,0 +1,115 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast) +{ + map functionSideEffects = + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)); + + set ssaVars = SSAValueTracker::ssaVariables(_ast); + LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast); +} + +void LoopInvariantCodeMotion::operator()(Block& _block) +{ + iterateReplacing( + _block.statements, + [&](Statement& _s) -> optional> + { + visit(_s); + if (holds_alternative(_s)) + return rewriteLoop(get(_s)); + else + return {}; + } + ); +} + +bool LoopInvariantCodeMotion::canBePromoted( + VariableDeclaration const& _varDecl, + set const& _varsDefinedInCurrentScope +) const +{ + // A declaration can be promoted iff + // 1. Its LHS is a SSA variable + // 2. Its RHS only references SSA variables declared outside of the current scope + // 3. Its RHS is movable + + for (auto const& var: _varDecl.variables) + if (!m_ssaVariables.count(var.name)) + return false; + if (_varDecl.value) + { + for (auto const& ref: ReferencesCounter::countReferences(*_varDecl.value, ReferencesCounter::OnlyVariables)) + if (_varsDefinedInCurrentScope.count(ref.first) || !m_ssaVariables.count(ref.first)) + return false; + if (!SideEffectsCollector{m_dialect, *_varDecl.value, &m_functionSideEffects}.movable()) + return false; + } + return true; +} + +optional> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for) +{ + assertThrow(_for.pre.statements.empty(), OptimizerException, ""); + vector replacement; + for (Block* block: {&_for.post, &_for.body}) + { + set varsDefinedInScope; + iterateReplacing( + block->statements, + [&](Statement& _s) -> optional> + { + if (holds_alternative(_s)) + { + VariableDeclaration const& varDecl = std::get(_s); + if (canBePromoted(varDecl, varsDefinedInScope)) + { + replacement.emplace_back(std::move(_s)); + // Do not add the variables declared here to varsDefinedInScope because we are moving them. + return vector{}; + } + for (auto const& var: varDecl.variables) + varsDefinedInScope.insert(var.name); + } + return {}; + } + ); + } + if (replacement.empty()) + return {}; + else + { + replacement.emplace_back(std::move(_for)); + return { std::move(replacement) }; + } +} diff --git a/libyul/optimiser/LoopInvariantCodeMotion.h b/libyul/optimiser/LoopInvariantCodeMotion.h new file mode 100644 index 000000000..f5f250515 --- /dev/null +++ b/libyul/optimiser/LoopInvariantCodeMotion.h @@ -0,0 +1,67 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +#pragma once + +#include +#include +#include + +namespace yul +{ + +/** + * Loop-invariant code motion. + * + * This optimization moves movable SSA variable declarations outside the loop. + * + * Only statements at the top level in a loop's body or post block are considered, i.e variable + * declarations inside conditional branches will not be moved out of the loop. + * + * Requirements: + * - The Disambiguator, ForLoopInitRewriter and FunctionHoister must be run upfront. + * - Expression splitter and SSA transform should be run upfront to obtain better result. + */ + +class LoopInvariantCodeMotion: public ASTModifier +{ +public: + static constexpr char const* name{"LoopInvariantCodeMotion"}; + static void run(OptimiserStepContext& _context, Block& _ast); + + void operator()(Block& _block) override; + +private: + explicit LoopInvariantCodeMotion( + Dialect const& _dialect, + std::set const& _ssaVariables, + std::map const& _functionSideEffects + ): + m_dialect(_dialect), + m_ssaVariables(_ssaVariables), + m_functionSideEffects(_functionSideEffects) + { } + + /// @returns true if the given variable declaration can be moved to in front of the loop. + bool canBePromoted(VariableDeclaration const& _varDecl, std::set const& _varsDefinedInCurrentScope) const; + std::optional> rewriteLoop(ForLoop& _for); + + Dialect const& m_dialect; + std::set const& m_ssaVariables; + std::map const& m_functionSideEffects; +}; + +} diff --git a/libyul/optimiser/MainFunction.cpp b/libyul/optimiser/MainFunction.cpp index 7ece2c330..770e59259 100644 --- a/libyul/optimiser/MainFunction.cpp +++ b/libyul/optimiser/MainFunction.cpp @@ -35,13 +35,13 @@ using namespace yul; void MainFunction::operator()(Block& _block) { assertThrow(_block.statements.size() >= 1, OptimizerException, ""); - assertThrow(_block.statements[0].type() == typeid(Block), OptimizerException, ""); + assertThrow(holds_alternative(_block.statements[0]), OptimizerException, ""); for (size_t i = 1; i < _block.statements.size(); ++i) - assertThrow(_block.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); + assertThrow(holds_alternative(_block.statements.at(i)), OptimizerException, ""); /// @todo this should handle scopes properly and instead of an assertion it should rename the conflicting function assertThrow(NameCollector(_block).names().count("main"_yulstring) == 0, OptimizerException, ""); - Block& block = boost::get(_block.statements[0]); + Block& block = std::get(_block.statements[0]); FunctionDefinition main{ block.location, "main"_yulstring, diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index dfc8d98b5..bbd578ab0 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -65,23 +65,23 @@ size_t CodeSize::codeSizeIncludingFunctions(Block const& _block) void CodeSize::visit(Statement const& _statement) { - if (_statement.type() == typeid(FunctionDefinition) && m_ignoreFunctions) + if (holds_alternative(_statement) && m_ignoreFunctions) return; else if ( - _statement.type() == typeid(If) || - _statement.type() == typeid(Break) || - _statement.type() == typeid(Continue) + holds_alternative(_statement) || + holds_alternative(_statement) || + holds_alternative(_statement) ) m_size += 2; - else if (_statement.type() == typeid(ForLoop)) + else if (holds_alternative(_statement)) m_size += 3; - else if (_statement.type() == typeid(Switch)) - m_size += 1 + 2 * boost::get(_statement).cases.size(); + else if (holds_alternative(_statement)) + m_size += 1 + 2 * std::get(_statement).cases.size(); else if (!( - _statement.type() == typeid(Block) || - _statement.type() == typeid(ExpressionStatement) || - _statement.type() == typeid(Assignment) || - _statement.type() == typeid(VariableDeclaration) + holds_alternative(_statement) || + holds_alternative(_statement) || + holds_alternative(_statement) || + holds_alternative(_statement) )) ++m_size; @@ -90,7 +90,7 @@ void CodeSize::visit(Statement const& _statement) void CodeSize::visit(Expression const& _expression) { - if (_expression.type() != typeid(Identifier)) + if (!holds_alternative(_expression)) ++m_size; ASTWalker::visit(_expression); } diff --git a/libyul/optimiser/NameCollector.cpp b/libyul/optimiser/NameCollector.cpp index 04631a86a..dab2f290a 100644 --- a/libyul/optimiser/NameCollector.cpp +++ b/libyul/optimiser/NameCollector.cpp @@ -49,27 +49,28 @@ void ReferencesCounter::operator()(Identifier const& _identifier) void ReferencesCounter::operator()(FunctionCall const& _funCall) { - ++m_references[_funCall.functionName.name]; + if (m_countWhat == VariablesAndFunctions) + ++m_references[_funCall.functionName.name]; ASTWalker::operator()(_funCall); } -map ReferencesCounter::countReferences(Block const& _block) +map ReferencesCounter::countReferences(Block const& _block, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter(_block); return counter.references(); } -map ReferencesCounter::countReferences(FunctionDefinition const& _function) +map ReferencesCounter::countReferences(FunctionDefinition const& _function, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter(_function); return counter.references(); } -map ReferencesCounter::countReferences(Expression const& _expression) +map ReferencesCounter::countReferences(Expression const& _expression, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter.visit(_expression); return counter.references(); } diff --git a/libyul/optimiser/NameCollector.h b/libyul/optimiser/NameCollector.h index b6b4e1e6c..46debdcba 100644 --- a/libyul/optimiser/NameCollector.h +++ b/libyul/optimiser/NameCollector.h @@ -54,16 +54,23 @@ private: class ReferencesCounter: public ASTWalker { public: + enum CountWhat { VariablesAndFunctions, OnlyVariables }; + + explicit ReferencesCounter(CountWhat _countWhat = VariablesAndFunctions): + m_countWhat(_countWhat) + {} + using ASTWalker::operator (); virtual void operator()(Identifier const& _identifier); virtual void operator()(FunctionCall const& _funCall); - static std::map countReferences(Block const& _block); - static std::map countReferences(FunctionDefinition const& _function); - static std::map countReferences(Expression const& _expression); + static std::map countReferences(Block const& _block, CountWhat _countWhat = VariablesAndFunctions); + static std::map countReferences(FunctionDefinition const& _function, CountWhat _countWhat = VariablesAndFunctions); + static std::map countReferences(Expression const& _expression, CountWhat _countWhat = VariablesAndFunctions); std::map const& references() const { return m_references; } private: + CountWhat m_countWhat = CountWhat::VariablesAndFunctions; std::map m_references; }; diff --git a/libyul/optimiser/NameDisplacer.cpp b/libyul/optimiser/NameDisplacer.cpp index cc6273fb2..2c8db2b12 100644 --- a/libyul/optimiser/NameDisplacer.cpp +++ b/libyul/optimiser/NameDisplacer.cpp @@ -64,8 +64,8 @@ void NameDisplacer::operator()(Block& _block) // First replace all the names of function definitions // because of scoping. for (auto& st: _block.statements) - if (st.type() == typeid(FunctionDefinition)) - checkAndReplaceNew(boost::get(st).name); + if (holds_alternative(st)) + checkAndReplaceNew(std::get(st).name); ASTModifier::operator()(_block); } diff --git a/libyul/optimiser/OptimizerUtilities.cpp b/libyul/optimiser/OptimizerUtilities.cpp index f9571a4c2..47c43a285 100644 --- a/libyul/optimiser/OptimizerUtilities.cpp +++ b/libyul/optimiser/OptimizerUtilities.cpp @@ -33,7 +33,7 @@ using namespace yul; void yul::removeEmptyBlocks(Block& _block) { auto isEmptyBlock = [](Statement const& _st) -> bool { - return _st.type() == typeid(Block) && boost::get(_st).statements.empty(); + return holds_alternative(_st) && std::get(_st).statements.empty(); }; boost::range::remove_erase_if(_block.statements, isEmptyBlock); } diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 16383d307..477814e22 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -296,7 +296,7 @@ void RedundantAssignEliminator::finalize( void AssignmentRemover::operator()(Block& _block) { boost::range::remove_erase_if(_block.statements, [=](Statement const& _statement) -> bool { - return _statement.type() == typeid(Assignment) && m_toRemove.count(&boost::get(_statement)); + return holds_alternative(_statement) && m_toRemove.count(&std::get(_statement)); }); ASTModifier::operator()(_block); diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index c9088a431..1df07c52f 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -68,9 +68,9 @@ Rematerialiser::Rematerialiser( void Rematerialiser::visit(Expression& _e) { - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - Identifier& identifier = boost::get(_e); + Identifier& identifier = std::get(_e); YulString name = identifier.name; if (m_value.count(name)) { @@ -96,15 +96,15 @@ void Rematerialiser::visit(Expression& _e) void LiteralRematerialiser::visit(Expression& _e) { - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - Identifier& identifier = boost::get(_e); + Identifier& identifier = std::get(_e); YulString name = identifier.name; if (m_value.count(name)) { Expression const* value = m_value.at(name); assertThrow(value, OptimizerException, ""); - if (value->type() == typeid(Literal)) + if (holds_alternative(*value)) _e = *value; } } diff --git a/libyul/optimiser/SSAReverser.cpp b/libyul/optimiser/SSAReverser.cpp index 2a421f9d9..bca44097b 100644 --- a/libyul/optimiser/SSAReverser.cpp +++ b/libyul/optimiser/SSAReverser.cpp @@ -19,6 +19,8 @@ #include #include +#include + using namespace std; using namespace dev; using namespace yul; @@ -37,7 +39,7 @@ void SSAReverser::operator()(Block& _block) _block.statements, [&](Statement& _stmt1, Statement& _stmt2) -> std::optional> { - auto* varDecl = boost::get(&_stmt1); + auto* varDecl = std::get_if(&_stmt1); if (!varDecl || varDecl->variables.size() != 1 || !varDecl->value) return {}; @@ -48,9 +50,9 @@ void SSAReverser::operator()(Block& _block) // with // a := E // let a_1 := a - if (auto* assignment = boost::get(&_stmt2)) + if (auto* assignment = std::get_if(&_stmt2)) { - auto* identifier = boost::get(assignment->value.get()); + auto* identifier = std::get_if(assignment->value.get()); if ( assignment->variableNames.size() == 1 && identifier && @@ -81,9 +83,9 @@ void SSAReverser::operator()(Block& _block) // with // let a := E // let a_1 := a - else if (auto* varDecl2 = boost::get(&_stmt2)) + else if (auto* varDecl2 = std::get_if(&_stmt2)) { - auto* identifier = boost::get(varDecl2->value.get()); + auto* identifier = std::get_if(varDecl2->value.get()); if ( varDecl2->variables.size() == 1 && identifier && diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp index 1f5d47e20..0b464f043 100644 --- a/libyul/optimiser/SSATransform.cpp +++ b/libyul/optimiser/SSATransform.cpp @@ -60,9 +60,9 @@ void IntroduceSSA::operator()(Block& _block) _block.statements, [&](Statement& _s) -> std::optional> { - if (_s.type() == typeid(VariableDeclaration)) + if (holds_alternative(_s)) { - VariableDeclaration& varDecl = boost::get(_s); + VariableDeclaration& varDecl = std::get(_s); if (varDecl.value) visit(*varDecl.value); @@ -90,12 +90,12 @@ void IntroduceSSA::operator()(Block& _block) make_unique(Identifier{loc, newName}) }); } - boost::get(statements.front()).variables = std::move(newVariables); + std::get(statements.front()).variables = std::move(newVariables); return { std::move(statements) }; } - else if (_s.type() == typeid(Assignment)) + else if (holds_alternative(_s)) { - Assignment& assignment = boost::get(_s); + Assignment& assignment = std::get(_s); visit(*assignment.value); for (auto const& var: assignment.variableNames) assertThrow(m_variablesToReplace.count(var.name), OptimizerException, ""); @@ -117,7 +117,7 @@ void IntroduceSSA::operator()(Block& _block) make_unique(Identifier{loc, newName}) }); } - boost::get(statements.front()).variables = std::move(newVariables); + std::get(statements.front()).variables = std::move(newVariables); return { std::move(statements) }; } else @@ -228,9 +228,9 @@ void IntroduceControlFlowSSA::operator()(Block& _block) } m_variablesToReassign.clear(); - if (_s.type() == typeid(VariableDeclaration)) + if (holds_alternative(_s)) { - VariableDeclaration& varDecl = boost::get(_s); + VariableDeclaration& varDecl = std::get(_s); for (auto const& var: varDecl.variables) if (m_variablesToReplace.count(var.name)) { @@ -238,9 +238,9 @@ void IntroduceControlFlowSSA::operator()(Block& _block) m_variablesInScope.insert(var.name); } } - else if (_s.type() == typeid(Assignment)) + else if (holds_alternative(_s)) { - Assignment& assignment = boost::get(_s); + Assignment& assignment = std::get(_s); for (auto const& var: assignment.variableNames) if (m_variablesToReplace.count(var.name)) assignedVariables.insert(var.name); @@ -304,14 +304,14 @@ void PropagateValues::operator()(VariableDeclaration& _varDecl) if (m_variablesToReplace.count(variable)) { // `let a := a_1` - regular declaration of non-SSA variable - yulAssert(_varDecl.value->type() == typeid(Identifier), ""); - m_currentVariableValues[variable] = boost::get(*_varDecl.value).name; + yulAssert(holds_alternative(*_varDecl.value), ""); + m_currentVariableValues[variable] = std::get(*_varDecl.value).name; m_clearAtEndOfBlock.insert(variable); } - else if (_varDecl.value && _varDecl.value->type() == typeid(Identifier)) + else if (_varDecl.value && holds_alternative(*_varDecl.value)) { // `let a_1 := a` - assignment to SSA variable after a branch. - YulString value = boost::get(*_varDecl.value).name; + YulString value = std::get(*_varDecl.value).name; if (m_variablesToReplace.count(value)) { // This is safe because `a_1` is not a "variable to replace" and thus @@ -333,8 +333,8 @@ void PropagateValues::operator()(Assignment& _assignment) if (!m_variablesToReplace.count(name)) return; - yulAssert(_assignment.value && _assignment.value->type() == typeid(Identifier), ""); - m_currentVariableValues[name] = boost::get(*_assignment.value).name; + yulAssert(_assignment.value && holds_alternative(*_assignment.value), ""); + m_currentVariableValues[name] = std::get(*_assignment.value).name; m_clearAtEndOfBlock.insert(name); } diff --git a/libyul/optimiser/SSAValueTracker.cpp b/libyul/optimiser/SSAValueTracker.cpp index d4feacbd9..3b599644c 100644 --- a/libyul/optimiser/SSAValueTracker.cpp +++ b/libyul/optimiser/SSAValueTracker.cpp @@ -49,6 +49,16 @@ void SSAValueTracker::operator()(VariableDeclaration const& _varDecl) setValue(_varDecl.variables.front().name, _varDecl.value.get()); } +set SSAValueTracker::ssaVariables(Block const& _ast) +{ + SSAValueTracker t; + t(_ast); + set ssaVars; + for (auto const& value: t.values()) + ssaVars.insert(value.first); + return ssaVars; +} + void SSAValueTracker::setValue(YulString _name, Expression const* _value) { assertThrow( diff --git a/libyul/optimiser/SSAValueTracker.h b/libyul/optimiser/SSAValueTracker.h index 1062ca8e7..7eac6b6a4 100644 --- a/libyul/optimiser/SSAValueTracker.h +++ b/libyul/optimiser/SSAValueTracker.h @@ -49,6 +49,8 @@ public: std::map const& values() const { return m_values; } Expression const* value(YulString _name) const { return m_values.at(_name); } + static std::set ssaVariables(Block const& _ast); + private: void setValue(YulString _name, Expression const* _value); diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 001193533..0d79e09c5 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -108,11 +108,44 @@ void MSizeFinder::operator()(FunctionCall const& _functionCall) map SideEffectsPropagator::sideEffects( Dialect const& _dialect, - map> const& _directCallGraph + CallGraph const& _directCallGraph ) { + // Any loop currently makes a function non-movable, because + // it could be a non-terminating loop. + // The same is true for any function part of a call cycle. + // In the future, we should refine that, because the property + // is actually a bit different from "not movable". + map ret; - for (auto const& call: _directCallGraph) + for (auto const& function: _directCallGraph.functionsWithLoops) + { + ret[function].movable = false; + ret[function].sideEffectFree = false; + ret[function].sideEffectFreeIfNoMSize = false; + } + + // Detect recursive functions. + for (auto const& call: _directCallGraph.functionCalls) + { + // TODO we could shortcut the search as soon as we find a + // function that has as bad side-effects as we can + // ever achieve via recursion. + auto search = [&](YulString const& _functionName, CycleDetector& _cycleDetector, size_t) { + for (auto const& callee: _directCallGraph.functionCalls.at(_functionName)) + if (!_dialect.builtin(callee)) + if (_cycleDetector.run(callee)) + return; + }; + if (CycleDetector(search).run(call.first)) + { + ret[call.first].movable = false; + ret[call.first].sideEffectFree = false; + ret[call.first].sideEffectFreeIfNoMSize = false; + } + } + + for (auto const& call: _directCallGraph.functionCalls) { YulString funName = call.first; SideEffects sideEffects; @@ -123,11 +156,15 @@ map SideEffectsPropagator::sideEffects( if (BuiltinFunction const* f = _dialect.builtin(_function)) sideEffects += f->sideEffects; else - for (YulString callee: _directCallGraph.at(_function)) + { + if (ret.count(_function)) + sideEffects += ret[_function]; + for (YulString callee: _directCallGraph.functionCalls.at(_function)) _addChild(callee); + } } ); - ret[funName] = sideEffects; + ret[funName] += sideEffects; } return ret; } @@ -165,13 +202,13 @@ pair TerminationFinder::firstUncondition TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement const& _statement) { if ( - _statement.type() == typeid(ExpressionStatement) && - isTerminatingBuiltin(boost::get(_statement)) + holds_alternative(_statement) && + isTerminatingBuiltin(std::get(_statement)) ) return ControlFlow::Terminate; - else if (_statement.type() == typeid(Break)) + else if (holds_alternative(_statement)) return ControlFlow::Break; - else if (_statement.type() == typeid(Continue)) + else if (holds_alternative(_statement)) return ControlFlow::Continue; else return ControlFlow::FlowOut; @@ -179,13 +216,13 @@ TerminationFinder::ControlFlow TerminationFinder::controlFlowKind(Statement cons bool TerminationFinder::isTerminatingBuiltin(ExpressionStatement const& _exprStmnt) { - if (_exprStmnt.expression.type() == typeid(FunctionalInstruction)) + if (holds_alternative(_exprStmnt.expression)) return eth::SemanticInformation::terminatesControlFlow( - boost::get(_exprStmnt.expression).instruction + std::get(_exprStmnt.expression).instruction ); - else if (_exprStmnt.expression.type() == typeid(FunctionCall)) + else if (holds_alternative(_exprStmnt.expression)) if (auto const* dialect = dynamic_cast(&m_dialect)) - if (auto const* builtin = dialect->builtin(boost::get(_exprStmnt.expression).functionName.name)) + if (auto const* builtin = dialect->builtin(std::get(_exprStmnt.expression).functionName.name)) if (builtin->instruction) return eth::SemanticInformation::terminatesControlFlow(*builtin->instruction); return false; diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 8953ab321..8c266910a 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -86,7 +87,7 @@ class SideEffectsPropagator public: static std::map sideEffects( Dialect const& _dialect, - std::map> const& _directCallGraph + CallGraph const& _directCallGraph ); }; diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 96f225f8c..683534d26 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -67,13 +67,13 @@ bool SimplificationRules::isInitialized() const std::optional const*>> SimplificationRules::instructionAndArguments(Dialect const& _dialect, Expression const& _expr) { - if (_expr.type() == typeid(FunctionalInstruction)) - return make_pair(boost::get(_expr).instruction, &boost::get(_expr).arguments); - else if (_expr.type() == typeid(FunctionCall)) + if (holds_alternative(_expr)) + return make_pair(std::get(_expr).instruction, &std::get(_expr).arguments); + else if (holds_alternative(_expr)) if (auto const* dialect = dynamic_cast(&_dialect)) - if (auto const* builtin = dialect->builtin(boost::get(_expr).functionName.name)) + if (auto const* builtin = dialect->builtin(std::get(_expr).functionName.name)) if (builtin->instruction) - return make_pair(*builtin->instruction, &boost::get(_expr).arguments); + return make_pair(*builtin->instruction, &std::get(_expr).arguments); return {}; } @@ -113,7 +113,7 @@ SimplificationRules::SimplificationRules() assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } -yul::Pattern::Pattern(dev::eth::Instruction _instruction, vector const& _arguments): +yul::Pattern::Pattern(dev::eth::Instruction _instruction, initializer_list _arguments): m_kind(PatternKind::Operation), m_instruction(_instruction), m_arguments(_arguments) @@ -136,9 +136,9 @@ bool Pattern::matches( // Resolve the variable if possible. // Do not do it for "Any" because we can check identity better for variables. - if (m_kind != PatternKind::Any && _expr.type() == typeid(Identifier)) + if (m_kind != PatternKind::Any && holds_alternative(_expr)) { - YulString varName = boost::get(_expr).name; + YulString varName = std::get(_expr).name; if (_ssaValues.count(varName)) if (Expression const* new_expr = _ssaValues.at(varName)) expr = new_expr; @@ -147,9 +147,9 @@ bool Pattern::matches( if (m_kind == PatternKind::Constant) { - if (expr->type() != typeid(Literal)) + if (!holds_alternative(*expr)) return false; - Literal const& literal = boost::get(*expr); + Literal const& literal = std::get(*expr); if (literal.kind != LiteralKind::Number) return false; if (m_data && *m_data != u256(literal.value.str())) @@ -233,7 +233,7 @@ Expression Pattern::toExpression(SourceLocation const& _location) const u256 Pattern::d() const { - return valueOfNumberLiteral(boost::get(matchGroupValue())); + return valueOfNumberLiteral(std::get(matchGroupValue())); } Expression const& Pattern::matchGroupValue() const diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 1caf323d8..d07807d89 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -85,7 +87,7 @@ enum class PatternKind class Pattern { public: - using Builtins = dev::eth::EVMBuiltins; + using Builtins = dev::eth::EVMBuiltins; static constexpr size_t WordSize = 256; using Word = dev::u256; @@ -93,10 +95,12 @@ public: Pattern(PatternKind _kind = PatternKind::Any): m_kind(_kind) {} // Matches a specific constant value. Pattern(unsigned _value): Pattern(dev::u256(_value)) {} + Pattern(int _value): Pattern(dev::u256(_value)) {} + Pattern(long unsigned _value): Pattern(dev::u256(_value)) {} // Matches a specific constant value. Pattern(dev::u256 const& _value): m_kind(PatternKind::Constant), m_data(std::make_shared(_value)) {} // Matches a given instruction with given arguments - Pattern(dev::eth::Instruction _instruction, std::vector const& _arguments = {}); + Pattern(dev::eth::Instruction _instruction, std::initializer_list _arguments = {}); /// Sets this pattern to be part of the match group with the identifier @a _group. /// Inside one rule, all patterns in the same match group have to match expressions from the /// same expression equivalence class. diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 72f63b76d..611883b2e 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -85,9 +85,9 @@ public: // get called on left-hand-sides of assignments. void visit(Expression& _e) override { - if (_e.type() == typeid(Identifier)) + if (holds_alternative(_e)) { - YulString name = boost::get(_e).name; + YulString name = std::get(_e).name; if (m_expressionCodeCost.count(name)) { if (!m_value.count(name)) @@ -162,7 +162,7 @@ bool StackCompressor::run( { yulAssert( _object.code && - _object.code->statements.size() > 0 && _object.code->statements.at(0).type() == typeid(Block), + _object.code->statements.size() > 0 && holds_alternative(_object.code->statements.at(0)), "Need to run the function grouper before the stack compressor." ); bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code); @@ -177,7 +177,7 @@ bool StackCompressor::run( yulAssert(stackSurplus.at({}) > 0, "Invalid surplus value."); eliminateVariables( _dialect, - boost::get(_object.code->statements.at(0)), + std::get(_object.code->statements.at(0)), stackSurplus.at({}), allowMSizeOptimzation ); @@ -185,7 +185,7 @@ bool StackCompressor::run( for (size_t i = 1; i < _object.code->statements.size(); ++i) { - FunctionDefinition& fun = boost::get(_object.code->statements[i]); + FunctionDefinition& fun = std::get(_object.code->statements[i]); if (!stackSurplus.count(fun.name)) continue; diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 706176456..fb97e22a8 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -71,7 +71,8 @@ void StructuralSimplifier::operator()(Block& _block) void StructuralSimplifier::simplify(std::vector& _statements) { - GenericFallbackReturnsVisitor const visitor( + GenericVisitor visitor{ + VisitorFallback{}, [&](If& _ifStmt) -> OptionalStatements { if (expressionAlwaysTrue(*_ifStmt.condition)) return {std::move(_ifStmt.body.statements)}; @@ -89,13 +90,13 @@ void StructuralSimplifier::simplify(std::vector& _statements) return {std::move(_forLoop.pre.statements)}; return {}; } - ); + }; iterateReplacing( _statements, [&](Statement& _stmt) -> OptionalStatements { - OptionalStatements result = boost::apply_visitor(visitor, _stmt); + OptionalStatements result = std::visit(visitor, _stmt); if (result) simplify(*result); else @@ -123,8 +124,8 @@ bool StructuralSimplifier::expressionAlwaysFalse(Expression const& _expression) std::optional StructuralSimplifier::hasLiteralValue(Expression const& _expression) const { - if (_expression.type() == typeid(Literal)) - return valueOfLiteral(boost::get(_expression)); + if (holds_alternative(_expression)) + return valueOfLiteral(std::get(_expression)); else return std::optional(); } diff --git a/libyul/optimiser/Substitution.cpp b/libyul/optimiser/Substitution.cpp index bc9efe962..98d71d410 100644 --- a/libyul/optimiser/Substitution.cpp +++ b/libyul/optimiser/Substitution.cpp @@ -28,9 +28,9 @@ using namespace yul; Expression Substitution::translate(Expression const& _expression) { - if (_expression.type() == typeid(Identifier)) + if (holds_alternative(_expression)) { - YulString name = boost::get(_expression).name; + YulString name = std::get(_expression).name; if (m_substitutions.count(name)) // No recursive substitution return ASTCopier().translate(*m_substitutions.at(name)); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 50bfc6603..33d9a507a 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -80,7 +81,7 @@ void OptimiserSuite::run( set reservedIdentifiers = _externallyUsedIdentifiers; reservedIdentifiers += _dialect.fixedFunctionNames(); - *_object.code = boost::get(Disambiguator( + *_object.code = std::get(Disambiguator( _dialect, *_object.analysisInfo, reservedIdentifiers @@ -129,7 +130,8 @@ void OptimiserSuite::run( RedundantAssignEliminator::name, ExpressionSimplifier::name, CommonSubexpressionEliminator::name, - LoadResolver::name + LoadResolver::name, + LoopInvariantCodeMotion::name }, ast); } @@ -184,6 +186,18 @@ void OptimiserSuite::run( }, ast); } + { + // Prune a bit more in SSA + suite.runSequence({ + ExpressionSplitter::name, + SSATransform::name, + RedundantAssignEliminator::name, + UnusedPruner::name, + RedundantAssignEliminator::name, + UnusedPruner::name, + }, ast); + } + { // Turn into SSA again and simplify suite.runSequence({ @@ -291,7 +305,7 @@ void OptimiserSuite::run( { // If the first statement is an empty block, remove it. // We should only have function definitions after that. - if (ast.statements.size() > 1 && boost::get(ast.statements.front()).statements.empty()) + if (ast.statements.size() > 1 && std::get(ast.statements.front()).statements.empty()) ast.statements.erase(ast.statements.begin()); } suite.runSequence({ @@ -345,6 +359,7 @@ map> const& OptimiserSuite::allSteps() FunctionHoister, LiteralRematerialiser, LoadResolver, + LoopInvariantCodeMotion, RedundantAssignEliminator, Rematerialiser, SSAReverser, @@ -361,7 +376,7 @@ void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) { unique_ptr copy; if (m_debug == Debug::PrintChanges) - copy = make_unique(boost::get(ASTCopier{}(_ast))); + copy = make_unique(std::get(ASTCopier{}(_ast))); for (string const& step: _steps) { if (m_debug == Debug::PrintStep) @@ -376,7 +391,7 @@ void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) { cout << "== Running " << step << " changed the AST." << endl; cout << AsmPrinter{}(_ast) << endl; - copy = make_unique(boost::get(ASTCopier{}(_ast))); + copy = make_unique(std::get(ASTCopier{}(_ast))); } } } diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index 9e425b17e..85a0dfb6d 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -32,7 +32,7 @@ using namespace yul; bool SyntacticallyEqual::operator()(Expression const& _lhs, Expression const& _rhs) { - return boost::apply_visitor([this](auto&& _lhsExpr, auto&& _rhsExpr) -> bool { + return std::visit([this](auto&& _lhsExpr, auto&& _rhsExpr) -> bool { // ``this->`` is redundant, but required to work around a bug present in gcc 6.x. return this->expressionEqual(_lhsExpr, _rhsExpr); }, _lhs, _rhs); @@ -40,7 +40,7 @@ bool SyntacticallyEqual::operator()(Expression const& _lhs, Expression const& _r bool SyntacticallyEqual::operator()(Statement const& _lhs, Statement const& _rhs) { - return boost::apply_visitor([this](auto&& _lhsStmt, auto&& _rhsStmt) -> bool { + return std::visit([this](auto&& _lhsStmt, auto&& _rhsStmt) -> bool { // ``this->`` is redundant, but required to work around a bug present in gcc 6.x. return this->statementEqual(_lhsStmt, _rhsStmt); }, _lhs, _rhs); diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index f77c1f95c..e0d5a3dbe 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -68,18 +68,18 @@ UnusedPruner::UnusedPruner( void UnusedPruner::operator()(Block& _block) { for (auto&& statement: _block.statements) - if (statement.type() == typeid(FunctionDefinition)) + if (holds_alternative(statement)) { - FunctionDefinition& funDef = boost::get(statement); + FunctionDefinition& funDef = std::get(statement); if (!used(funDef.name)) { subtractReferences(ReferencesCounter::countReferences(funDef.body)); statement = Block{std::move(funDef.location), {}}; } } - else if (statement.type() == typeid(VariableDeclaration)) + else if (holds_alternative(statement)) { - VariableDeclaration& varDecl = boost::get(statement); + VariableDeclaration& varDecl = std::get(statement); // Multi-variable declarations are special. We can only remove it // if all variables are unused and the right-hand-side is either // movable or it returns a single value. In the latter case, we @@ -108,9 +108,9 @@ void UnusedPruner::operator()(Block& _block) }}; } } - else if (statement.type() == typeid(ExpressionStatement)) + else if (holds_alternative(statement)) { - ExpressionStatement& exprStmt = boost::get(statement); + ExpressionStatement& exprStmt = std::get(statement); if ( SideEffectsCollector(m_dialect, exprStmt.expression, m_functionSideEffects). sideEffectFree(m_allowMSizeOptimization) diff --git a/libyul/optimiser/VarDeclInitializer.cpp b/libyul/optimiser/VarDeclInitializer.cpp index 45b53ee61..abedfe069 100644 --- a/libyul/optimiser/VarDeclInitializer.cpp +++ b/libyul/optimiser/VarDeclInitializer.cpp @@ -30,7 +30,8 @@ void VarDeclInitializer::operator()(Block& _block) ASTModifier::operator()(_block); using OptionalStatements = std::optional>; - GenericFallbackReturnsVisitor visitor{ + GenericVisitor visitor{ + VisitorFallback{}, [](VariableDeclaration& _varDecl) -> OptionalStatements { if (_varDecl.value) @@ -51,5 +52,6 @@ void VarDeclInitializer::operator()(Block& _block) } } }; - iterateReplacing(_block.statements, boost::apply_visitor(visitor)); + + iterateReplacing(_block.statements, [&](auto&& _statement) { return std::visit(visitor, _statement); }); } diff --git a/libyul/optimiser/VarNameCleaner.cpp b/libyul/optimiser/VarNameCleaner.cpp index 4c3946295..edb60a82b 100644 --- a/libyul/optimiser/VarNameCleaner.cpp +++ b/libyul/optimiser/VarNameCleaner.cpp @@ -40,8 +40,8 @@ VarNameCleaner::VarNameCleaner( m_translatedNames{} { for (auto const& statement: _ast.statements) - if (statement.type() == typeid(FunctionDefinition)) - m_blacklist.insert(boost::get(statement).name); + if (holds_alternative(statement)) + m_blacklist.insert(std::get(statement).name); m_usedNames = m_blacklist; } diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 25f6f7f8c..b457b741d 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -34,7 +34,7 @@ else BUILD_DIR="$1" fi -docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.38.22-64bit \ +docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.39.3-64bit \ ./scripts/travis-emscripten/install_deps.sh -docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.38.22-64bit \ +docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.39.3-64bit \ ./scripts/travis-emscripten/build_emscripten.sh $BUILD_DIR diff --git a/scripts/codespell_whitelist.txt b/scripts/codespell_whitelist.txt index bbfe3e05c..0409dc1a2 100644 --- a/scripts/codespell_whitelist.txt +++ b/scripts/codespell_whitelist.txt @@ -10,3 +10,4 @@ fo compilability errorstring hist +otion diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh index 9f39a79c9..b38af9e9c 100755 --- a/scripts/deps-ppa/static_z3.sh +++ b/scripts/deps-ppa/static_z3.sh @@ -25,7 +25,9 @@ set -ev keyid=70D110489D66E2F6 email=builds@ethereum.org packagename=libz3-static-dev -version=4.8.6 +# On the next version the git cherry-pick below should be removed and the patch suffix removed from the version string. +version=4.8.7 +version_patchsuffix=-1 DISTRIBUTIONS="bionic disco eoan" @@ -40,11 +42,14 @@ pparepo=cpp-build-deps ppafilesurl=https://launchpad.net/~ethereum/+archive/ubuntu/${pparepo}/+files # Fetch source -git clone --depth 1 --branch z3-${version} https://github.com/Z3Prover/z3.git +git clone --branch z3-${version} https://github.com/Z3Prover/z3.git cd z3 -debversion="$version" +# Patch build failure. +git cherry-pick e212159f4e -CMAKE_OPTIONS="-DBUILD_LIBZ3_SHARED=OFF -DCMAKE_BUILD_TYPE=Release" +debversion="${version}${version_patchsuffix}" + +CMAKE_OPTIONS="-DZ3_BUILD_LIBZ3_SHARED=OFF -DCMAKE_BUILD_TYPE=Release" # gzip will create different tars all the time and we are not allowed # to upload the same file twice with different contents, so we only @@ -176,7 +181,7 @@ This program is free software: you can redistribute it and/or modify Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". EOF cat < debian/changelog -libz3-static-dev (0.0.1-0ubuntu1) saucy; urgency=low +libz3-static-dev (0.0.1-2ubuntu0) saucy; urgency=low * Initial release. @@ -186,7 +191,7 @@ mkdir debian/source echo "3.0 (quilt)" > debian/source/format chmod +x debian/rules -versionsuffix=0ubuntu1~${distribution} +versionsuffix=2ubuntu0~${distribution} EMAIL="$email" dch -v 1:${debversion}-${versionsuffix} "build of ${version}" # build source package diff --git a/scripts/install_static_z3.sh b/scripts/install_static_z3.sh index 7748fb1f3..6fba203d8 100644 --- a/scripts/install_static_z3.sh +++ b/scripts/install_static_z3.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -git clone --depth 1 --branch z3-4.8.1 https://github.com/Z3Prover/z3.git +git clone --depth 1 --branch z3-4.8.7 https://github.com/Z3Prover/z3.git cd z3 mkdir build cd build -LDFLAGS="-static" cmake -DBUILD_LIBZ3_SHARED=OFF .. +LDFLAGS="-static" cmake -DZ3_BUILD_LIBZ3_SHARED=OFF .. make -j 4 make install \ No newline at end of file diff --git a/scripts/tests.sh b/scripts/tests.sh index 01577af1c..266c3184e 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -81,7 +81,7 @@ EVM_VERSIONS="homestead byzantium" if [ -z "$CI" ] then - EVM_VERSIONS+=" constantinople petersburg" + EVM_VERSIONS+=" constantinople petersburg istanbul" fi # And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer @@ -91,9 +91,9 @@ do for vm in $EVM_VERSIONS do FORCE_ABIV2_RUNS="no" - if [[ "$vm" == "constantinople" ]] + if [[ "$vm" == "istanbul" ]] then - FORCE_ABIV2_RUNS="no yes" # run both in constantinople + FORCE_ABIV2_RUNS="no yes" # run both in istanbul fi for abiv2 in $FORCE_ABIV2_RUNS do diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 5c8a06e58..c2c8760ef 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -55,11 +55,11 @@ fi WORKSPACE=/root/project # Increase nodejs stack size -if ! [ -e /emsdk_portable/node/bin/node_orig ] +if ! [ -e /emsdk_portable/node/current/bin/node_orig ] then - mv /emsdk_portable/node/bin/node /emsdk_portable/node/bin/node_orig - echo -e '#!/bin/sh\nexec /emsdk_portable/node/bin/node_orig --stack-size=8192 $@' > /emsdk_portable/node/bin/node - chmod 755 /emsdk_portable/node/bin/node + mv /emsdk_portable/node/current/bin/node /emsdk_portable/node/current/bin/node_orig + echo -e '#!/bin/sh\nexec /emsdk_portable/node/current/bin/node_orig --stack-size=8192 $@' > /emsdk_portable/node/current/bin/node + chmod 755 /emsdk_portable/node/current/bin/node fi # Boost @@ -70,8 +70,8 @@ cd "$WORKSPACE"/boost_1_70_0 --with-system --with-filesystem --with-test --with-program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ --prefix="$WORKSPACE"/boost_1_70_0_install install ) -ln -sf "$WORKSPACE"/boost_1_70_0_install/lib/* /emsdk_portable/sdk/system/lib -ln -sf "$WORKSPACE"/boost_1_70_0_install/include/* /emsdk_portable/sdk/system/include +ln -sf "$WORKSPACE"/boost_1_70_0_install/lib/* /emsdk_portable/emscripten/sdk/system/lib +ln -sf "$WORKSPACE"/boost_1_70_0_install/include/* /emsdk_portable/emscripten/sdk/system/include echo -en 'travis_fold:end:compiling_boost\\r' echo -en 'travis_fold:start:install_cmake.sh\\r' @@ -95,7 +95,8 @@ make -j 4 cd .. mkdir -p upload # Patch soljson.js to provide backwards-compatibility with older emscripten versions -echo ";/* backwards compatibility */ Module['Runtime'] = Module;" >> $BUILD_DIR/libsolc/soljson.js +# TODO: remove in 0.6.0! +echo -n ";/* backwards compatibility */ Module['Runtime'] = Module; Module['Pointer_stringify'] = Module['UTF8ToString'];" >> $BUILD_DIR/libsolc/soljson.js cp $BUILD_DIR/libsolc/soljson.js upload/ cp $BUILD_DIR/libsolc/soljson.js ./ diff --git a/scripts/travis-emscripten/emscripten.jam b/scripts/travis-emscripten/emscripten.jam new file mode 100644 index 000000000..0cc92bd56 --- /dev/null +++ b/scripts/travis-emscripten/emscripten.jam @@ -0,0 +1,186 @@ +# Modified version of emscripten.jam from https://github.com/tee3/boost-build-emscripten +# which is released under the following license: +# +# Boost Software License - Version 1.0 - August 17th, 2003 +# +# Permission is hereby granted, free of charge, to any person or organization +# obtaining a copy of the software and accompanying documentation covered by +# this license (the "Software") to use, reproduce, display, distribute, +# execute, and transmit the Software, and to prepare derivative works of the +# Software, and to permit third-parties to whom the Software is furnished to +# do so, all subject to the following: +# +# The copyright notices in the Software and this entire statement, including +# the above license grant, this restriction and the following disclaimer, +# must be included in all copies of the Software, in whole or in part, and +# all derivative works of the Software, unless such copies or derivative +# works are solely in the form of machine-executable object code generated by +# a source language processor. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +# SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +# FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +# Boost.Build support for Emscipten. +# +# @todo add support for dynamic linking +# @todo add support for --js-library, --pre-js, and --post-js options + +import generators ; +import type ; +import toolset ; +import feature ; +import common ; +import errors ; + +if [ MATCH (--debug-configuration) : [ modules.peek : ARGV ] ] +{ + .debug-configuration = true ; +} + +# add an emscripten toolset +feature.extend toolset : emscripten ; + +# extend the target-os list to include emscripten +feature.extend target-os : emscripten ; +# make emscripten the default target-os when compiling with the emscripten toolset +feature.set-default target-os : emscripten ; + +# initialize the emscripten toolset +rule init ( version ? : command * : options * ) +{ + command = [ common.get-invocation-command emscripten : em++ : $(command) ] ; + + if $(command) + { + version ?= [ MATCH "^([0-9.]+)" : [ SHELL \""$(command)\" --version" ] ] ; + if $(version) + { + local actual_version = [ MATCH "^([0-9.]+)" : [ SHELL \""$(command)\" --version" ] ] ; + if $(actual_version) != $(version) + { + errors.user-error "emscripten: detected version $(actual_version) does not match desired $(version)" ; + } + } + } + else + { + errors.user-error "emscripten: em++ not found" ; + } + + local condition = [ common.check-init-parameters emscripten : version $(version) ] ; + + common.handle-options emscripten : $(condition) : $(command) : $(options) ; + + # @todo this seems to be the right way, but this is a list somehow + toolset.add-requirements emscripten:node ; + + toolset.flags emscripten.compile STDHDRS $(condition) : /emsdk_portable/emscripten/sdk/system/include ; + toolset.flags emscripten.link STDLIBPATH $(condition) : /emsdk_portable/emscripten/sdk/system/lib ; + toolset.flags emscripten AR $(condition) : /emsdk_portable/emscripten/sdk/emar ; + toolset.flags emscripten RANLIB $(condition) : /emsdk_portable/emscripten/sdk/emranlib ; +} + +type.set-generated-target-suffix EXE : emscripten : js ; +#type.set-generated-target-suffix STATIC_LIB : emscripten : bc ; +#type.set-generated-target-suffix SHARED_LIB : emscripten : bc ; +#type.set-generated-target-suffix OBJ : emscripten : bc ; + +generators.register-linker emscripten.link : OBJ STATIC_LIB : EXE : emscripten ; + +generators.register-archiver emscripten.archive : OBJ : STATIC_LIB : emscripten ; + +generators.register-c-compiler emscripten.compile.c++.preprocess : CPP : PREPROCESSED_CPP : emscripten ; +generators.register-c-compiler emscripten.compile.c.preprocess : C : PREPROCESSED_C : emscripten ; +generators.register-c-compiler emscripten.compile.c++ : CPP : OBJ : emscripten ; +generators.register-c-compiler emscripten.compile.c : C : OBJ : emscripten ; + +# Declare flags + +toolset.flags emscripten.compile OPTIONS off : -O0 ; +toolset.flags emscripten.compile OPTIONS speed : -O3 ; +toolset.flags emscripten.compile OPTIONS space : -Os ; + +toolset.flags emscripten.compile OPTIONS off : -fno-inline ; +toolset.flags emscripten.compile OPTIONS on : -Wno-inline ; +toolset.flags emscripten.compile OPTIONS full : -finline-functions -Wno-inline ; + +toolset.flags emscripten.compile OPTIONS off : -w ; +toolset.flags emscripten.compile OPTIONS on : -Wall ; +toolset.flags emscripten.compile OPTIONS all : -Wall -pedantic ; +toolset.flags emscripten.compile OPTIONS on : -Werror ; + +toolset.flags emscripten.compile OPTIONS on : -g ; +toolset.flags emscripten.compile OPTIONS on : -pg ; + +toolset.flags emscripten.compile.c++ OPTIONS off : -fno-rtti ; +toolset.flags emscripten.compile.c++ OPTIONS off : -fno-exceptions ; + +toolset.flags emscripten.compile USER_OPTIONS ; +toolset.flags emscripten.compile.c++ USER_OPTIONS ; +toolset.flags emscripten.compile DEFINES ; +toolset.flags emscripten.compile INCLUDES ; +toolset.flags emscripten.compile.c++ TEMPLATE_DEPTH ; +toolset.flags emscripten.compile.fortran USER_OPTIONS ; + +toolset.flags emscripten.link DEFAULTS : -Wno-warn-absolute-paths ; + +toolset.flags emscripten.link LIBRARY_PATH ; +toolset.flags emscripten.link FINDLIBS_ST ; +toolset.flags emscripten.link FINDLIBS_SA ; +toolset.flags emscripten.link LIBRARIES ; + +toolset.flags emscripten.link OPTIONS ; + +toolset.flags emscripten.archive AROPTIONS ; + +rule compile.c++ ( targets * : sources * : properties * ) +{ + # Some extensions are compiled as C++ by default. For others, we need to + # pass -x c++. We could always pass -x c++ but distcc does not work with it. + if ! $(>:S) in .cc .cp .cxx .cpp .c++ .C + { + LANG on $(<) = "-x c++" ; + } +} + +rule compile.c ( targets * : sources * : properties * ) +{ + LANG on $(<) = "-x c" ; +} + +actions compile.c++ +{ + "$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) $(USER_OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -c -o "$(<:W)" "$(>:W)" +} + +actions compile.c +{ + "$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) $(USER_OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" -c -o "$(<)" "$(>)" +} + +actions compile.c++.preprocess +{ + "$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) $(USER_OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" "$(>:W)" -E >"$(<:W)" +} + +actions compile.c.preprocess +{ + "$(CONFIG_COMMAND)" $(LANG) $(OPTIONS) $(USER_OPTIONS) -D$(DEFINES) -I"$(INCLUDES)" "$(>)" -E >$(<) +} + +actions link +{ + "$(CONFIG_COMMAND)" $(DEFAULTS) $(OPTIONS) -L"$(LIBRARY_PATH:W)" -L"$(STDLIBPATH:W)" -o "$(<:W)" "$(>:W)" -l"$(LIBRARIES:W)" -l"$(STDLIBRARIES:W)" +} + +RM = [ common.rm-command ] ; +actions piecemeal archive +{ + $(RM) "$(<)" + $(AR) $(AROPTIONS) rc "$(<:W)" "$(>:W)" +} diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index fc81851c2..7544da4e2 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -29,6 +29,8 @@ set -ev +SCRIPT_DIR="$(realpath $(dirname $0))" + echo -en 'travis_fold:start:installing_dependencies\\r' test -e boost_1_70_0_install/include/boost/version.hpp || ( rm -rf boost_1_70_0 @@ -40,8 +42,7 @@ tar -xzf boost.tar.gz rm boost.tar.gz cd boost_1_70_0 ./bootstrap.sh -wget -q 'https://raw.githubusercontent.com/tee3/boost-build-emscripten/master/emscripten.jam' -test "$(shasum emscripten.jam)" = "a7e13fc2c1e53b0e079ef440622f879aa6da3049 emscripten.jam" +cp "${SCRIPT_DIR}/emscripten.jam" . echo "using emscripten : : em++ ;" >> project-config.jam ) cd .. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 34b7c67c5..05b021379 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -122,6 +122,7 @@ static string const g_strHelp = "help"; static string const g_strInputFile = "input-file"; static string const g_strInterface = "interface"; static string const g_strYul = "yul"; +static string const g_strYulDialect = "yul-dialect"; static string const g_strIR = "ir"; static string const g_strEWasm = "ewasm"; static string const g_strLicense = "license"; @@ -220,6 +221,13 @@ static set const g_machineArgs g_streWasm }; +/// Possible arguments to for --yul-dialect +static set const g_yulDialectArgs +{ + g_strEVM, + g_streWasm +}; + static void version() { sout() << @@ -271,7 +279,7 @@ void CommandLineInterface::handleBinary(string const& _contract) createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", objectWithLinkRefsHex(m_compiler->object(_contract))); else { - sout() << "Binary: " << endl; + sout() << "Binary:" << endl; sout() << objectWithLinkRefsHex(m_compiler->object(_contract)) << endl; } } @@ -281,7 +289,7 @@ void CommandLineInterface::handleBinary(string const& _contract) createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", objectWithLinkRefsHex(m_compiler->runtimeObject(_contract))); else { - sout() << "Binary of the runtime part: " << endl; + sout() << "Binary of the runtime part:" << endl; sout() << objectWithLinkRefsHex(m_compiler->runtimeObject(_contract)) << endl; } } @@ -293,7 +301,7 @@ void CommandLineInterface::handleOpcode(string const& _contract) createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", dev::eth::disassemble(m_compiler->object(_contract).bytecode)); else { - sout() << "Opcodes: " << endl; + sout() << "Opcodes:" << endl; sout() << std::uppercase << dev::eth::disassemble(m_compiler->object(_contract).bytecode); sout() << endl; } @@ -307,7 +315,7 @@ void CommandLineInterface::handleIR(string const& _contractName) createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); else { - sout() << "IR: " << endl; + sout() << "IR:" << endl; sout() << m_compiler->yulIR(_contractName) << endl; } } @@ -327,7 +335,7 @@ void CommandLineInterface::handleEWasm(string const& _contractName) } else { - sout() << "EWasm text: " << endl; + sout() << "EWasm text:" << endl; sout() << m_compiler->eWasm(_contractName) << endl; sout() << "EWasm binary (hex): " << m_compiler->eWasmObject(_contractName).toHex() << endl; } @@ -355,7 +363,7 @@ void CommandLineInterface::handleSignatureHashes(string const& _contract) if (m_args.count(g_argOutputDir)) createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out); else - sout() << "Function signatures: " << endl << out; + sout() << "Function signatures:" << endl << out; } void CommandLineInterface::handleMetadata(string const& _contract) @@ -367,7 +375,7 @@ void CommandLineInterface::handleMetadata(string const& _contract) if (m_args.count(g_argOutputDir)) createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data); else - sout() << "Metadata: " << endl << data << endl; + sout() << "Metadata:" << endl << data << endl; } void CommandLineInterface::handleABI(string const& _contract) @@ -379,7 +387,7 @@ void CommandLineInterface::handleABI(string const& _contract) if (m_args.count(g_argOutputDir)) createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data); else - sout() << "Contract JSON ABI " << endl << data << endl; + sout() << "Contract JSON ABI" << endl << data << endl; } void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contract) @@ -563,7 +571,7 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) addrString = addrString.substr(2); if (addrString.empty()) { - serr() << "Empty address provided for library \"" << libName << "\": " << endl; + serr() << "Empty address provided for library \"" << libName << "\":" << endl; serr() << "Note that there should not be any whitespace after the colon." << endl; return false; } @@ -648,7 +656,8 @@ Allowed options)", ( g_strEVMVersion.c_str(), po::value()->value_name("version"), - "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg (default), istanbul or berlin." + "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, " + "byzantium, constantinople, petersburg, istanbul (default) or berlin." ) (g_argOptimize.c_str(), "Enable bytecode optimizer.") ( @@ -685,15 +694,20 @@ Allowed options)", ) ( g_argAssemble.c_str(), - "Switch to assembly mode, ignoring all options except --machine and --optimize and assumes input is assembly." + "Switch to assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is assembly." ) ( g_argYul.c_str(), - "Switch to Yul mode, ignoring all options except --machine and --optimize and assumes input is Yul." + "Switch to Yul mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is Yul." ) ( g_argStrictAssembly.c_str(), - "Switch to strict assembly mode, ignoring all options except --machine and --optimize and assumes input is strict assembly." + "Switch to strict assembly mode, ignoring all options except --machine, --yul-dialect and --optimize and assumes input is strict assembly." + ) + ( + g_strYulDialect.c_str(), + po::value()->value_name(boost::join(g_yulDialectArgs, ",")), + "Input dialect to use in assembly or yul mode." ) ( g_argMachine.c_str(), @@ -911,7 +925,27 @@ bool CommandLineInterface::processInput() } if (targetMachine == Machine::eWasm && inputLanguage == Input::StrictAssembly) inputLanguage = Input::EWasm; - if (optimize && inputLanguage != Input::StrictAssembly) + if (m_args.count(g_strYulDialect)) + { + string dialect = m_args[g_strYulDialect].as(); + if (dialect == g_strEVM) + inputLanguage = Input::StrictAssembly; + else if (dialect == g_streWasm) + { + inputLanguage = Input::EWasm; + if (targetMachine != Machine::eWasm) + { + serr() << "If you select eWasm as --yul-dialect, --machine has to be eWasm as well." << endl; + return false; + } + } + else + { + serr() << "Invalid option for --yul-dialect: " << dialect << endl; + return false; + } + } + if (optimize && (inputLanguage != Input::StrictAssembly && inputLanguage != Input::EWasm)) { serr() << "Optimizer can only be used for strict assembly. Use --" << @@ -933,7 +967,7 @@ bool CommandLineInterface::processInput() return link(); } - m_compiler.reset(new CompilerStack(fileReader)); + m_compiler = make_unique(fileReader); unique_ptr formatter; if (m_args.count(g_argNewReporter)) @@ -1331,14 +1365,14 @@ bool CommandLineInterface::assemble( for (auto const& sourceAndStack: assemblyStacks) { auto const& stack = sourceAndStack.second; - unique_ptr formatter; - if (m_args.count(g_argNewReporter)) - formatter = make_unique(serr(false), m_coloredOutput); - else - formatter = make_unique(serr(false)); - for (auto const& error: stack.errors()) { + unique_ptr formatter; + if (m_args.count(g_argNewReporter)) + formatter = make_unique(serr(false), m_coloredOutput); + else + formatter = make_unique(serr(false)); + g_hasOutput = true; formatter->printErrorInformation(*error); } @@ -1356,11 +1390,22 @@ bool CommandLineInterface::assemble( _targetMachine == yul::AssemblyStack::Machine::EVM15 ? "EVM 1.5" : "eWasm"; sout() << endl << "======= " << src.first << " (" << machine << ") =======" << endl; + yul::AssemblyStack& stack = assemblyStacks[src.first]; sout() << endl << "Pretty printed source:" << endl; sout() << stack.print() << endl; + if (_language != yul::AssemblyStack::Language::EWasm && _targetMachine == yul::AssemblyStack::Machine::eWasm) + { + stack.translate(yul::AssemblyStack::Language::EWasm); + stack.optimize(); + + sout() << endl << "==========================" << endl; + sout() << endl << "Translated source:" << endl; + sout() << stack.print() << endl; + } + yul::MachineAssemblyObject object; try { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 387d6e515..b0c3b47da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -129,6 +129,8 @@ set(libyul_sources libyul/Common.cpp libyul/Common.h libyul/CompilabilityChecker.cpp + libyul/EWasmTranslationTest.cpp + libyul/EWasmTranslationTest.h libyul/FunctionSideEffects.cpp libyul/FunctionSideEffects.h libyul/Inliner.cpp diff --git a/test/Common.h b/test/Common.h index ebbe2d2ad..bc632ff59 100644 --- a/test/Common.h +++ b/test/Common.h @@ -32,13 +32,13 @@ namespace test #ifdef _WIN32 static constexpr auto evmoneFilename = "evmone.dll"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-windows-amd64.zip"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-windows-amd64.zip"; #elif defined(__APPLE__) static constexpr auto evmoneFilename = "libevmone.dylib"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-darwin-x86_64.tar.gz"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-darwin-x86_64.tar.gz"; #else static constexpr auto evmoneFilename = "libevmone.so"; -static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-linux-x86_64.tar.gz"; +static constexpr auto evmoneDownloadLink = "https://github.com/ethereum/evmone/releases/download/v0.3.0/evmone-0.3.0-linux-x86_64.tar.gz"; #endif diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index b7755f28f..9ea472eae 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -33,24 +33,21 @@ using namespace std; using namespace dev; using namespace dev::test; +using namespace evmc::literals; - -evmc::vm* EVMHost::getVM(string const& _path) +evmc::VM& EVMHost::getVM(string const& _path) { - static unique_ptr theVM; + static evmc::VM theVM; if (!theVM && !_path.empty()) { evmc_loader_error_code errorCode = {}; - evmc_instance* vm = evmc_load_and_configure(_path.c_str(), &errorCode); + auto vm = evmc::VM{evmc_load_and_configure(_path.c_str(), &errorCode)}; if (vm && errorCode == EVMC_LOADER_SUCCESS) { - if (evmc_vm_has_capability(vm, EVMC_CAPABILITY_EVM1)) - theVM = make_unique(vm); + if (vm.get_capabilities() & EVMC_CAPABILITY_EVM1) + theVM = std::move(vm); else - { - evmc_destroy(vm); cerr << "VM loaded does not support EVM1" << endl; - } } else { @@ -60,11 +57,12 @@ evmc::vm* EVMHost::getVM(string const& _path) cerr << endl; } } - return theVM.get(); + return theVM; } -EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::vm* _vm): - m_vm(_vm) +EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::VM& _vm): + m_vm(_vm), + m_evmVersion(_evmVersion) { if (!m_vm) { @@ -73,85 +71,91 @@ EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::vm* _vm): } if (_evmVersion == langutil::EVMVersion::homestead()) - m_evmVersion = EVMC_HOMESTEAD; + m_evmRevision = EVMC_HOMESTEAD; else if (_evmVersion == langutil::EVMVersion::tangerineWhistle()) - m_evmVersion = EVMC_TANGERINE_WHISTLE; + m_evmRevision = EVMC_TANGERINE_WHISTLE; else if (_evmVersion == langutil::EVMVersion::spuriousDragon()) - m_evmVersion = EVMC_SPURIOUS_DRAGON; + m_evmRevision = EVMC_SPURIOUS_DRAGON; else if (_evmVersion == langutil::EVMVersion::byzantium()) - m_evmVersion = EVMC_BYZANTIUM; + m_evmRevision = EVMC_BYZANTIUM; else if (_evmVersion == langutil::EVMVersion::constantinople()) - m_evmVersion = EVMC_CONSTANTINOPLE; + m_evmRevision = EVMC_CONSTANTINOPLE; else if (_evmVersion == langutil::EVMVersion::istanbul()) - assertThrow(false, Exception, "Istanbul is not supported yet."); + m_evmRevision = EVMC_ISTANBUL; else if (_evmVersion == langutil::EVMVersion::berlin()) assertThrow(false, Exception, "Berlin is not supported yet."); else //if (_evmVersion == langutil::EVMVersion::petersburg()) - m_evmVersion = EVMC_PETERSBURG; -} + m_evmRevision = EVMC_PETERSBURG; -evmc_storage_status EVMHost::set_storage(const evmc::address& _addr, const evmc::bytes32& _key, const evmc::bytes32& _value) noexcept -{ - evmc::bytes32 previousValue = m_state.accounts[_addr].storage[_key]; - m_state.accounts[_addr].storage[_key] = _value; - - // TODO EVMC_STORAGE_MODIFIED_AGAIN should be also used - if (previousValue == _value) - return EVMC_STORAGE_UNCHANGED; - else if (previousValue == evmc::bytes32{}) - return EVMC_STORAGE_ADDED; - else if (_value == evmc::bytes32{}) - return EVMC_STORAGE_DELETED; - else - return EVMC_STORAGE_MODIFIED; + // Mark all precompiled contracts as existing. Existing here means to have a balance (as per EIP-161). + // NOTE: keep this in sync with `EVMHost::call` below. + // + // A lot of precompile addresses had a balance before they became valid addresses for precompiles. + // For example all the precompile addresses allocated in Byzantium had a 1 wei balance sent to them + // roughly 22 days before the update went live. + for (unsigned precompiledAddress = 1; precompiledAddress <= 8; precompiledAddress++) + { + evmc::address address{}; + address.bytes[19] = precompiledAddress; + // 1wei + accounts[address].balance.bytes[31] = 1; + } + // TODO: support short literals in EVMC and use them here + tx_context.block_difficulty = convertToEVMC(u256("200000000")); + tx_context.block_gas_limit = 20000000; + tx_context.block_coinbase = 0x7878787878787878787878787878787878787878_address; + tx_context.tx_gas_price = convertToEVMC(u256("3000000000")); + tx_context.tx_origin = 0x9292929292929292929292929292929292929292_address; + // Mainnet according to EIP-155 + tx_context.chain_id = convertToEVMC(u256(1)); } void EVMHost::selfdestruct(const evmc::address& _addr, const evmc::address& _beneficiary) noexcept { // TODO actual selfdestruct is even more complicated. - evmc::uint256be balance = m_state.accounts[_addr].balance; - m_state.accounts.erase(_addr); - m_state.accounts[_beneficiary].balance = balance; + evmc::uint256be balance = accounts[_addr].balance; + accounts.erase(_addr); + accounts[_beneficiary].balance = balance; } evmc::result EVMHost::call(evmc_message const& _message) noexcept { - if (_message.destination == convertToEVMC(Address(1))) + if (_message.destination == 0x0000000000000000000000000000000000000001_address) return precompileECRecover(_message); - else if (_message.destination == convertToEVMC(Address(2))) + else if (_message.destination == 0x0000000000000000000000000000000000000002_address) return precompileSha256(_message); - else if (_message.destination == convertToEVMC(Address(3))) + else if (_message.destination == 0x0000000000000000000000000000000000000003_address) return precompileRipeMD160(_message); - else if (_message.destination == convertToEVMC(Address(4))) + else if (_message.destination == 0x0000000000000000000000000000000000000004_address) return precompileIdentity(_message); - else if (_message.destination == convertToEVMC(Address(5))) + else if (_message.destination == 0x0000000000000000000000000000000000000005_address && m_evmVersion >= langutil::EVMVersion::byzantium()) return precompileModExp(_message); - else if (_message.destination == convertToEVMC(Address(6))) + else if (_message.destination == 0x0000000000000000000000000000000000000006_address && m_evmVersion >= langutil::EVMVersion::byzantium()) return precompileALTBN128G1Add(_message); - else if (_message.destination == convertToEVMC(Address(7))) + else if (_message.destination == 0x0000000000000000000000000000000000000007_address && m_evmVersion >= langutil::EVMVersion::byzantium()) return precompileALTBN128G1Mul(_message); - else if (_message.destination == convertToEVMC(Address(8))) + else if (_message.destination == 0x0000000000000000000000000000000000000008_address && m_evmVersion >= langutil::EVMVersion::byzantium()) return precompileALTBN128PairingProduct(_message); - State stateBackup = m_state; + auto const stateBackup = accounts; u256 value{convertFromEVMC(_message.value)}; - Account& sender = m_state.accounts[_message.sender]; + auto& sender = accounts[_message.sender]; - bytes code; + evmc::bytes code; evmc_message message = _message; if (message.depth == 0) { message.gas -= message.kind == EVMC_CREATE ? eth::GasCosts::txCreateGas : eth::GasCosts::txGas; for (size_t i = 0; i < message.input_size; ++i) - message.gas -= message.input_data[i] == 0 ? eth::GasCosts::txDataZeroGas : eth::GasCosts::txDataNonZeroGas; + message.gas -= message.input_data[i] == 0 ? eth::GasCosts::txDataZeroGas : eth::GasCosts::txDataNonZeroGas(m_evmVersion); if (message.gas < 0) { evmc::result result({}); result.status_code = EVMC_OUT_OF_GAS; - m_state = stateBackup; + accounts = stateBackup; return result; } } @@ -165,23 +169,23 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept asBytes(to_string(sender.nonce++)) )); message.destination = convertToEVMC(createAddress); - code = bytes(message.input_data, message.input_data + message.input_size); + code = evmc::bytes(message.input_data, message.input_data + message.input_size); } else if (message.kind == EVMC_DELEGATECALL) { - code = m_state.accounts[message.destination].code; + code = accounts[message.destination].code; message.destination = m_currentAddress; } else if (message.kind == EVMC_CALLCODE) { - code = m_state.accounts[message.destination].code; + code = accounts[message.destination].code; message.destination = m_currentAddress; } else - code = m_state.accounts[message.destination].code; + code = accounts[message.destination].code; //TODO CREATE2 - Account& destination = m_state.accounts[message.destination]; + auto& destination = accounts[message.destination]; if (value != 0 && message.kind != EVMC_DELEGATECALL && message.kind != EVMC_CALLCODE) { @@ -191,7 +195,7 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept evmc::address currentAddress = m_currentAddress; m_currentAddress = message.destination; - evmc::result result = m_vm->execute(*this, m_evmVersion, message, code.data(), code.size()); + evmc::result result = m_vm.execute(*this, m_evmRevision, message, code.data(), code.size()); m_currentAddress = currentAddress; if (message.kind == EVMC_CREATE) @@ -206,52 +210,22 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept else { result.create_address = message.destination; - destination.code = bytes(result.output_data, result.output_data + result.output_size); - destination.codeHash = convertToEVMC(keccak256(destination.code)); + destination.code = evmc::bytes(result.output_data, result.output_data + result.output_size); + destination.codehash = convertToEVMC(keccak256({result.output_data, result.output_size})); } } if (result.status_code != EVMC_SUCCESS) - m_state = stateBackup; + accounts = stateBackup; return result; } -evmc_tx_context EVMHost::get_tx_context() noexcept -{ - evmc_tx_context ctx = {}; - ctx.block_timestamp = m_state.timestamp; - ctx.block_number = m_state.blockNumber; - ctx.block_coinbase = m_coinbase; - ctx.block_difficulty = convertToEVMC(u256("200000000")); - ctx.block_gas_limit = 20000000; - ctx.tx_gas_price = convertToEVMC(u256("3000000000")); - ctx.tx_origin = convertToEVMC(Address("0x9292929292929292929292929292929292929292")); - return ctx; -} - -evmc::bytes32 EVMHost::get_block_hash(int64_t _number) noexcept +evmc::bytes32 EVMHost::get_block_hash(int64_t _number) const noexcept { return convertToEVMC(u256("0x3737373737373737373737373737373737373737373737373737373737373737") + _number); } -void EVMHost::emit_log( - evmc::address const& _addr, - uint8_t const* _data, - size_t _dataSize, - evmc::bytes32 const _topics[], - size_t _topicsCount -) noexcept -{ - LogEntry entry; - entry.address = convertFromEVMC(_addr); - for (size_t i = 0; i < _topicsCount; ++i) - entry.topics.emplace_back(convertFromEVMC(_topics[i])); - entry.data = bytes(_data, _data + _dataSize); - m_state.logs.emplace_back(std::move(entry)); -} - - Address EVMHost::convertFromEVMC(evmc::address const& _addr) { return Address(bytes(begin(_addr.bytes), end(_addr.bytes))); diff --git a/test/EVMHost.h b/test/EVMHost.h index c01d27ad0..c3f69960c 100644 --- a/test/EVMHost.h +++ b/test/EVMHost.h @@ -21,6 +21,7 @@ #pragma once +#include #include #include @@ -34,153 +35,64 @@ namespace test { using Address = h160; -class EVMHost: public evmc::Host +class EVMHost: public evmc::MockedHost { public: + using MockedHost::get_code_size; + using MockedHost::get_balance; + /// Tries to dynamically load libevmone. @returns nullptr on failure. /// The path has to be provided for the first successful run and will be ignored /// afterwards. - static evmc::vm* getVM(std::string const& _path = {}); + static evmc::VM& getVM(std::string const& _path = {}); - explicit EVMHost(langutil::EVMVersion _evmVersion, evmc::vm* _vm = getVM()); + explicit EVMHost(langutil::EVMVersion _evmVersion, evmc::VM& _vm = getVM()); - struct Account - { - evmc::uint256be balance = {}; - size_t nonce = 0; - bytes code; - evmc::bytes32 codeHash = {}; - std::map storage; - }; - - struct LogEntry - { - Address address; - std::vector topics; - bytes data; - }; - - struct State - { - size_t blockNumber; - uint64_t timestamp; - std::map accounts; - std::vector logs; - }; - - Account* account(evmc::address const& _address) - { - // Make all precompiled contracts exist. - // Be future-proof and consider everything below 1024 as precompiled contract. - if (u160(convertFromEVMC(_address)) < 1024) - m_state.accounts[_address]; - auto it = m_state.accounts.find(_address); - return it == m_state.accounts.end() ? nullptr : &it->second; - } - - void reset() { m_state = State{}; m_currentAddress = {}; } + void reset() { accounts.clear(); m_currentAddress = {}; } void newBlock() { - m_state.blockNumber++; - m_state.timestamp += 15; - m_state.logs.clear(); + tx_context.block_number++; + tx_context.block_timestamp += 15; + recorded_logs.clear(); } - bool account_exists(evmc::address const& _addr) noexcept final + bool account_exists(evmc::address const& _addr) const noexcept final { - return account(_addr) != nullptr; + return evmc::MockedHost::account_exists(_addr); } - evmc::bytes32 get_storage(evmc::address const& _addr, evmc::bytes32 const& _key) noexcept final - { - if (Account* acc = account(_addr)) - return acc->storage[_key]; - return {}; - } + void selfdestruct(evmc::address const& _addr, evmc::address const& _beneficiary) noexcept final; - evmc_storage_status set_storage( - evmc::address const& _addr, - evmc::bytes32 const& _key, - evmc::bytes32 const& _value - ) noexcept; + evmc::result call(evmc_message const& _message) noexcept final; - evmc::uint256be get_balance(evmc::address const& _addr) noexcept final - { - if (Account const* acc = account(_addr)) - return acc->balance; - return {}; - } - - size_t get_code_size(evmc::address const& _addr) noexcept final - { - if (Account const* acc = account(_addr)) - return acc->code.size(); - return 0; - } - - evmc::bytes32 get_code_hash(evmc::address const& _addr) noexcept final - { - if (Account const* acc = account(_addr)) - return acc->codeHash; - return {}; - } - - size_t copy_code( - evmc::address const& _addr, - size_t _codeOffset, - uint8_t* _bufferData, - size_t _bufferSize - ) noexcept final - { - size_t i = 0; - if (Account const* acc = account(_addr)) - for (; i < _bufferSize && _codeOffset + i < acc->code.size(); i++) - _bufferData[i] = acc->code[_codeOffset + i]; - return i; - } - - void selfdestruct(evmc::address const& _addr, evmc::address const& _beneficiary) noexcept; - - evmc::result call(evmc_message const& _message) noexcept; - - evmc_tx_context get_tx_context() noexcept; - - evmc::bytes32 get_block_hash(int64_t number) noexcept; - - void emit_log( - evmc::address const& _addr, - uint8_t const* _data, - size_t _dataSize, - evmc::bytes32 const _topics[], - size_t _topicsCount - ) noexcept; + evmc::bytes32 get_block_hash(int64_t number) const noexcept final; static Address convertFromEVMC(evmc::address const& _addr); static evmc::address convertToEVMC(Address const& _addr); static h256 convertFromEVMC(evmc::bytes32 const& _data); static evmc::bytes32 convertToEVMC(h256 const& _data); - - State m_state; - evmc::address m_currentAddress = {}; - evmc::address m_coinbase = convertToEVMC(Address("0x7878787878787878787878787878787878787878")); - private: - evmc::result precompileECRecover(evmc_message const& _message) noexcept; - evmc::result precompileSha256(evmc_message const& _message) noexcept; - evmc::result precompileRipeMD160(evmc_message const& _message) noexcept; - evmc::result precompileIdentity(evmc_message const& _message) noexcept; - evmc::result precompileModExp(evmc_message const& _message) noexcept; - evmc::result precompileALTBN128G1Add(evmc_message const& _message) noexcept; - evmc::result precompileALTBN128G1Mul(evmc_message const& _message) noexcept; - evmc::result precompileALTBN128PairingProduct(evmc_message const& _message) noexcept; - evmc::result precompileGeneric(evmc_message const& _message, std::map const& _inOut) noexcept; + evmc::address m_currentAddress = {}; + + static evmc::result precompileECRecover(evmc_message const& _message) noexcept; + static evmc::result precompileSha256(evmc_message const& _message) noexcept; + static evmc::result precompileRipeMD160(evmc_message const& _message) noexcept; + static evmc::result precompileIdentity(evmc_message const& _message) noexcept; + static evmc::result precompileModExp(evmc_message const& _message) noexcept; + static evmc::result precompileALTBN128G1Add(evmc_message const& _message) noexcept; + static evmc::result precompileALTBN128G1Mul(evmc_message const& _message) noexcept; + static evmc::result precompileALTBN128PairingProduct(evmc_message const& _message) noexcept; + static evmc::result precompileGeneric(evmc_message const& _message, std::map const& _inOut) noexcept; /// @returns a result object with no gas usage and result data taken from @a _data. /// @note The return value is only valid as long as @a _data is alive! static evmc::result resultWithGas(evmc_message const& _message, bytes const& _data) noexcept; - evmc::vm* m_vm = nullptr; - evmc_revision m_evmVersion; + evmc::VM& m_vm; + // EVM version requested by the testing tool + langutil::EVMVersion m_evmVersion; + // EVM version requested from EVMC (matches the above) + evmc_revision m_evmRevision; }; diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp index 9c08394ef..4f5c8badd 100644 --- a/test/ExecutionFramework.cpp +++ b/test/ExecutionFramework.cpp @@ -25,7 +25,6 @@ #include #include -#include #include @@ -56,7 +55,7 @@ ExecutionFramework::ExecutionFramework(langutil::EVMVersion _evmVersion): m_evmHost->reset(); for (size_t i = 0; i < 10; i++) - m_evmHost->m_state.accounts[EVMHost::convertToEVMC(account(i))].balance = + m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance = EVMHost::convertToEVMC(u256(1) << 100); } @@ -89,12 +88,12 @@ std::pair ExecutionFramework::compareAndCreateMessage( u256 ExecutionFramework::gasLimit() const { - return {m_evmHost->get_tx_context().block_gas_limit}; + return {m_evmHost->tx_context.block_gas_limit}; } u256 ExecutionFramework::gasPrice() const { - return {EVMHost::convertFromEVMC(m_evmHost->get_tx_context().tx_gas_price)}; + return {EVMHost::convertFromEVMC(m_evmHost->tx_context.tx_gas_price)}; } u256 ExecutionFramework::blockHash(u256 const& _number) const @@ -104,7 +103,7 @@ u256 ExecutionFramework::blockHash(u256 const& _number) const u256 ExecutionFramework::blockNumber() const { - return m_evmHost->m_state.blockNumber; + return m_evmHost->tx_context.block_number; } void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256 const& _value) @@ -178,7 +177,7 @@ void ExecutionFramework::sendEther(Address const& _addr, u256 const& _amount) size_t ExecutionFramework::currentTimestamp() { - return m_evmHost->get_tx_context().block_timestamp; + return m_evmHost->tx_context.block_timestamp; } size_t ExecutionFramework::blockTimestamp(u256 _block) @@ -201,27 +200,30 @@ bool ExecutionFramework::addressHasCode(Address const& _addr) size_t ExecutionFramework::numLogs() const { - return m_evmHost->m_state.logs.size(); + return m_evmHost->recorded_logs.size(); } size_t ExecutionFramework::numLogTopics(size_t _logIdx) const { - return m_evmHost->m_state.logs.at(_logIdx).topics.size(); + return m_evmHost->recorded_logs.at(_logIdx).topics.size(); } h256 ExecutionFramework::logTopic(size_t _logIdx, size_t _topicIdx) const { - return m_evmHost->m_state.logs.at(_logIdx).topics.at(_topicIdx); + return EVMHost::convertFromEVMC(m_evmHost->recorded_logs.at(_logIdx).topics.at(_topicIdx)); } Address ExecutionFramework::logAddress(size_t _logIdx) const { - return m_evmHost->m_state.logs.at(_logIdx).address; + return EVMHost::convertFromEVMC(m_evmHost->recorded_logs.at(_logIdx).creator); } -bytes const& ExecutionFramework::logData(size_t _logIdx) const +bytes ExecutionFramework::logData(size_t _logIdx) const { - return m_evmHost->m_state.logs.at(_logIdx).data; + const auto& data = m_evmHost->recorded_logs.at(_logIdx).data; + // TODO: Return a copy of log data, because this is expected from REQUIRE_LOG_DATA(), + // but reference type like string_view would be preferable. + return {data.begin(), data.end()}; } u256 ExecutionFramework::balanceAt(Address const& _addr) @@ -231,10 +233,11 @@ u256 ExecutionFramework::balanceAt(Address const& _addr) bool ExecutionFramework::storageEmpty(Address const& _addr) { - if (EVMHost::Account const* acc = m_evmHost->account(EVMHost::convertToEVMC(_addr))) + const auto it = m_evmHost->accounts.find(EVMHost::convertToEVMC(_addr)); + if (it != m_evmHost->accounts.end()) { - for (auto const& entry: acc->storage) - if (!(entry.second == evmc::bytes32{})) + for (auto const& entry: it->second.storage) + if (!(entry.second.value == evmc::bytes32{})) return false; } return true; diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index 5019c0fbc..912b987dd 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -266,7 +266,7 @@ protected: size_t numLogTopics(size_t _logIdx) const; h256 logTopic(size_t _logIdx, size_t _topicIdx) const; Address logAddress(size_t _logIdx) const; - bytes const& logData(size_t _logIdx) const; + bytes logData(size_t _logIdx) const; langutil::EVMVersion m_evmVersion; solidity::OptimiserSettings m_optimiserSettings = solidity::OptimiserSettings::minimal(); diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index b75be49e7..591d1d7ed 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,7 @@ struct Testsuite Testsuite const g_interactiveTestsuites[] = { /* Title Path Subpath SMT NeedsVM Creator function */ + {"EWasm Translation", "libyul", "ewasmTranslationTests",false,false, &yul::test::EWasmTranslationTest::create}, {"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create}, {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, diff --git a/test/TestCase.cpp b/test/TestCase.cpp index c264da100..a9f4e6252 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -186,7 +186,7 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer versionString = versionString.substr(versionBegin); std::optional version = langutil::EVMVersion::fromString(versionString); if (!version) - throw runtime_error("Invalid EVM version: \"" + versionString + "\""); + BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""}); if (comparator == ">") return _evmVersion > version; @@ -201,6 +201,5 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer else if (comparator == "!") return !(_evmVersion == version); else - throw runtime_error("Invalid EVM comparator: \"" + comparator + "\""); - return false; // not reached + BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""}); } diff --git a/test/boostTest.cpp b/test/boostTest.cpp index fd33f667e..27db38486 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -96,7 +96,7 @@ int registerTests( { static vector> filenames; - filenames.emplace_back(new string(_path.string())); + filenames.emplace_back(make_unique(_path.string())); _suite.add(make_test_case( [config, _testCaseCreator] { diff --git a/test/cmdlineTests/evm_to_wasm/args b/test/cmdlineTests/evm_to_wasm/args new file mode 100644 index 000000000..099ebdc3a --- /dev/null +++ b/test/cmdlineTests/evm_to_wasm/args @@ -0,0 +1 @@ +--assemble --optimize --yul-dialect evm --machine ewasm diff --git a/test/cmdlineTests/evm_to_wasm/err b/test/cmdlineTests/evm_to_wasm/err new file mode 100644 index 000000000..aa7ea77f9 --- /dev/null +++ b/test/cmdlineTests/evm_to_wasm/err @@ -0,0 +1 @@ +Warning: Yul and its optimizer are still experimental. Please use the output with care. diff --git a/test/cmdlineTests/evm_to_wasm/input.sol b/test/cmdlineTests/evm_to_wasm/input.sol new file mode 100644 index 000000000..f21cd2b7e --- /dev/null +++ b/test/cmdlineTests/evm_to_wasm/input.sol @@ -0,0 +1,3 @@ +{ + sstore(0, 1) +} diff --git a/test/cmdlineTests/evm_to_wasm/output b/test/cmdlineTests/evm_to_wasm/output new file mode 100644 index 000000000..3b1e0fab7 --- /dev/null +++ b/test/cmdlineTests/evm_to_wasm/output @@ -0,0 +1,104 @@ + +======= evm_to_wasm/input.sol (eWasm) ======= + +Pretty printed source: +object "object" { + code { { sstore(0, 1) } } +} + + +========================== + +Translated source: +object "object" { + code { + function main() + { + let _1 := 0 + mstore_internal(0, _1, _1, _1, _1) + mstore_internal(32, _1, _1, _1, 1) + eth.storageStore(0, 32) + } + function endian_swap_16(x) -> y + { + y := i64.or(i64.and(i64.shl(x, 8), 0xff00), i64.and(i64.shr_u(x, 8), 0xff)) + } + function endian_swap_32(x) -> y + { + let hi := i64.shl(endian_swap_16(x), 16) + y := i64.or(hi, endian_swap_16(i64.shr_u(x, 16))) + } + function endian_swap(x) -> y + { + let hi := i64.shl(endian_swap_32(x), 32) + y := i64.or(hi, endian_swap_32(i64.shr_u(x, 32))) + } + function mstore_internal(pos, y1, y2, y3, y4) + { + i64.store(pos, endian_swap(y1)) + i64.store(i64.add(pos, 8), endian_swap(y2)) + i64.store(i64.add(pos, 16), endian_swap(y3)) + i64.store(i64.add(pos, 24), endian_swap(y4)) + } + } +} + + +Binary representation: +0061736d0100000001160460000060017e017e60057e7e7e7e7e0060027f7f0002190108657468657265756d0c73746f7261676553746f7265000303060500010101020503010001060100071102066d656d6f72790200046d61696e00010ab501052801017e420021004200200020002000200010054220200020002000420110054200a74220a710000b1c01017e20004208864280fe0383200042088842ff018384210120010b1b01027e20001002421086210220022000421088100284210120010b1b01027e20001003422086210220022000422088100384210120010b3501007e2000a720011004370300200042087ca720021004370300200042107ca720031004370300200042187ca7200410043703000b + +Text representation: +(module + (import "ethereum" "storageStore" (func $eth.storageStore (param i32 i32))) + (memory $memory (export "memory") 1) + (export "main" (func $main)) + +(func $main + (local $_1 i64) + (local.set $_1 (i64.const 0)) + (call $mstore_internal (i64.const 0) (local.get $_1) (local.get $_1) (local.get $_1) (local.get $_1)) + (call $mstore_internal (i64.const 32) (local.get $_1) (local.get $_1) (local.get $_1) (i64.const 1)) + (call $eth.storageStore (i32.wrap_i64 (i64.const 0)) (i32.wrap_i64 (i64.const 32))) +) + +(func $endian_swap_16 + (param $x i64) + (result i64) + (local $y i64) + (local.set $y (i64.or (i64.and (i64.shl (local.get $x) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (local.get $x) (i64.const 8)) (i64.const 255)))) + (local.get $y) +) + +(func $endian_swap_32 + (param $x i64) + (result i64) + (local $y i64) + (local $hi i64) + (local.set $hi (i64.shl (call $endian_swap_16 (local.get $x)) (i64.const 16))) + (local.set $y (i64.or (local.get $hi) (call $endian_swap_16 (i64.shr_u (local.get $x) (i64.const 16))))) + (local.get $y) +) + +(func $endian_swap + (param $x i64) + (result i64) + (local $y i64) + (local $hi i64) + (local.set $hi (i64.shl (call $endian_swap_32 (local.get $x)) (i64.const 32))) + (local.set $y (i64.or (local.get $hi) (call $endian_swap_32 (i64.shr_u (local.get $x) (i64.const 32))))) + (local.get $y) +) + +(func $mstore_internal + (param $pos i64) + (param $y1 i64) + (param $y2 i64) + (param $y3 i64) + (param $y4 i64) + (i64.store (i32.wrap_i64 (local.get $pos)) (call $endian_swap (local.get $y1))) + (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 8))) (call $endian_swap (local.get $y2))) + (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 16))) (call $endian_swap (local.get $y3))) + (i64.store (i32.wrap_i64 (i64.add (local.get $pos) (i64.const 24))) (call $endian_swap (local.get $y4))) +) + +) diff --git a/test/cmdlineTests/yul_string_format_ascii/input.json b/test/cmdlineTests/yul_string_format_ascii/input.json new file mode 100644 index 000000000..c23c65b5a --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (string memory) { return \"abcabc\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json new file mode 100644 index 000000000..40ed5700f --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -0,0 +1,161 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(64) + mstore(converted, 6) + + mstore(add(converted, 32), \"abcabc\") + + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value, pos) -> end { + let length := array_length_t_string_memory_ptr(value) + pos := array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) + copy_memory_to_memory(add(value, 0x20), pos, length) + end := add(pos, round_up_to_mul_of_32(length)) + } + + function abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + mstore(add(headStart, 0), sub(tail, headStart)) + tail := abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value0, tail) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function array_length_t_string_memory_ptr(value) -> length { + + + length := mload(value) + + + + } + + function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { + mstore(pos, length) + updated_pos := add(pos, 0x20) + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(64) + mstore(converted, 6) + + mstore(add(converted, 32), \"abcabc\") + + } + + function copy_memory_to_memory(src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + function round_up_to_mul_of_32(value) -> result { + result := and(add(value, 31), not(31)) + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json new file mode 100644 index 000000000..247f665cb --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes32) { return \"abcabc\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json new file mode 100644 index 000000000..062cdfdc9 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -0,0 +1,114 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() -> converted { + converted := 0x6162636162630000000000000000000000000000000000000000000000000000 + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes32__to_t_bytes32__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes32_to_t_bytes32_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes32(value)) + } + + function abi_encode_tuple_t_bytes32__to_t_bytes32__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes32_to_t_bytes32_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes32(value) -> cleaned { + cleaned := value + } + + function convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() -> converted { + converted := 0x6162636162630000000000000000000000000000000000000000000000000000 + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_9f0adad0a59b05d2e04a1373342b10b9eb16c57c164c8a3bfcbf46dccee39a21_to_t_bytes32() + return_flag := 0 + break + + break + } + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json new file mode 100644 index 000000000..c7309f2af --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes4) { return 0x61626364; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json new file mode 100644 index 000000000..7c163ff12 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -0,0 +1,138 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function cleanup_t_rational_1633837924_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_1633837924_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_1633837924_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0x61626364 + vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes4_to_t_bytes4_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes4(value)) + } + + function abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes4_to_t_bytes4_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes4(value) -> cleaned { + cleaned := and(value, 0xffffffff00000000000000000000000000000000000000000000000000000000) + } + + function cleanup_t_rational_1633837924_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_1633837924_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_1633837924_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0x61626364 + vloc__4 := convert_t_rational_1633837924_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_ascii_long/input.json b/test/cmdlineTests/yul_string_format_ascii_long/input.json new file mode 100644 index 000000000..cf3b2a854 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_long/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (string memory) { return \"abcdabcdcafecafeabcdabcdcafecafeffffzzzzoooo0123456789,.<,>.?:;'[{]}|`~!@#$%^&*()-_=+\"; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json new file mode 100644 index 000000000..8245d0dd9 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -0,0 +1,169 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(128) + mstore(converted, 85) + + mstore(add(converted, 32), \"abcdabcdcafecafeabcdabcdcafecafe\") + + mstore(add(converted, 64), \"ffffzzzzoooo0123456789,.<,>.?:;'\") + + mstore(add(converted, 96), \"[{]}|`~!@#$%^&*()-_=+\") + + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value, pos) -> end { + let length := array_length_t_string_memory_ptr(value) + pos := array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) + copy_memory_to_memory(add(value, 0x20), pos, length) + end := add(pos, round_up_to_mul_of_32(length)) + } + + function abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + mstore(add(headStart, 0), sub(tail, headStart)) + tail := abi_encode_t_string_memory_ptr_to_t_string_memory_ptr_fromStack(value0, tail) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function array_length_t_string_memory_ptr(value) -> length { + + + length := mload(value) + + + + } + + function array_storeLengthForEncoding_t_string_memory_ptr_fromStack(pos, length) -> updated_pos { + mstore(pos, length) + updated_pos := add(pos, 0x20) + } + + function convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() -> converted { + converted := allocateMemory(128) + mstore(converted, 85) + + mstore(add(converted, 32), \"abcdabcdcafecafeabcdabcdcafecafe\") + + mstore(add(converted, 64), \"ffffzzzzoooo0123456789,.<,>.?:;'\") + + mstore(add(converted, 96), \"[{]}|`~!@#$%^&*()-_=+\") + + } + + function copy_memory_to_memory(src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + vloc__4 := convert_t_stringliteral_d6604f85ac07e2b33103a620b3d3d75b0473c7214912beded67b9b624d41c571_to_t_string_memory_ptr() + return_flag := 0 + break + + break + } + } + + function round_up_to_mul_of_32(value) -> result { + result := and(add(value, 31), not(31)) + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_string_format_hex/input.json b/test/cmdlineTests/yul_string_format_hex/input.json new file mode 100644 index 000000000..5ba723f56 --- /dev/null +++ b/test/cmdlineTests/yul_string_format_hex/input.json @@ -0,0 +1,17 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() external pure returns (bytes4) { return 0xaabbccdd; } }" + } + }, + "settings": + { + "outputSelection": + { + "*": { "*": ["ir"] } + } + } +} diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json new file mode 100644 index 000000000..38cbd08ea --- /dev/null +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -0,0 +1,138 @@ +{"contracts":{"A":{"C":{"ir":"/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + + +object \"C_10\" { + code { + mstore(64, 128) + + // Begin state variable initialization for contract \"C\" (0 variables) + // End state variable initialization for contract \"C\". + + + codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) + + + function cleanup_t_rational_2864434397_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_2864434397_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_2864434397_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0xaabbccdd + vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + } + object \"C_10_deployed\" { + code { + mstore(64, 128) + + if iszero(lt(calldatasize(), 4)) + { + let selector := shift_right_224_unsigned(calldataload(0)) + switch selector + + case 0x26121ff0 + { + // f() + if callvalue() { revert(0, 0) } + abi_decode_tuple_(4, calldatasize()) + let ret_0 := fun_f_9() + let memPos := allocateMemory(0) + let memEnd := abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(memPos , ret_0) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + revert(0, 0) + + + function abi_decode_tuple_(headStart, dataEnd) { + if slt(sub(dataEnd, headStart), 0) { revert(0, 0) } + + } + + function abi_encode_t_bytes4_to_t_bytes4_fromStack(value, pos) { + mstore(pos, cleanup_t_bytes4(value)) + } + + function abi_encode_tuple_t_bytes4__to_t_bytes4__fromStack(headStart , value0) -> tail { + tail := add(headStart, 32) + + abi_encode_t_bytes4_to_t_bytes4_fromStack(value0, add(headStart, 0)) + + } + + function allocateMemory(size) -> memPtr { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + + function cleanup_t_bytes4(value) -> cleaned { + cleaned := and(value, 0xffffffff00000000000000000000000000000000000000000000000000000000) + } + + function cleanup_t_rational_2864434397_by_1(value) -> cleaned { + cleaned := value + } + + function convert_t_rational_2864434397_by_1_to_t_bytes4(value) -> converted { + converted := shift_left_224(cleanup_t_rational_2864434397_by_1(value)) + } + + function fun_f_9() -> vloc__4 { + for { let return_flag := 1 } return_flag {} { + let expr_6 := 0xaabbccdd + vloc__4 := convert_t_rational_2864434397_by_1_to_t_bytes4(expr_6) + return_flag := 0 + break + + break + } + } + + function shift_left_224(value) -> newValue { + newValue := + + shl(224, value) + + } + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + } + } +} + +"}}},"sources":{"A":{"id":0}}} diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index b643754e9..df6721de5 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -27,7 +27,6 @@ #include #include -#include using namespace std; using namespace dev::test; @@ -223,7 +222,7 @@ protected: void deployRegistrar() { if (!s_compiledRegistrar) - s_compiledRegistrar.reset(new bytes(compileContract(registrarCode, "GlobalRegistrar"))); + s_compiledRegistrar = make_unique(compileContract(registrarCode, "GlobalRegistrar")); sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(m_transactionSuccessful); @@ -280,8 +279,7 @@ protected: } }; - size_t const m_biddingTime = size_t(7 * 24 * 3600); - size_t const m_renewalInterval = size_t(365 * 24 * 3600); + int64_t const m_biddingTime = 7 * 24 * 3600; }; } @@ -417,7 +415,7 @@ BOOST_AUTO_TEST_CASE(auction_simple) BOOST_CHECK_EQUAL(registrar.owner(name), 0); // "wait" until auction end - m_evmHost->m_state.timestamp += m_biddingTime + 10; + m_evmHost->tx_context.block_timestamp += m_biddingTime + 10; // trigger auction again registrar.reserve(name); BOOST_CHECK_EQUAL(registrar.owner(name), m_sender); @@ -429,7 +427,7 @@ BOOST_AUTO_TEST_CASE(auction_bidding) string name = "x"; unsigned startTime = 0x776347e2; - m_evmHost->m_state.timestamp = startTime; + m_evmHost->tx_context.block_timestamp = startTime; RegistrarInterface registrar(*this); // initiate auction @@ -437,19 +435,19 @@ BOOST_AUTO_TEST_CASE(auction_bidding) registrar.reserve(name); BOOST_CHECK_EQUAL(registrar.owner(name), 0); // overbid self - m_evmHost->m_state.timestamp = startTime + m_biddingTime - 10; + m_evmHost->tx_context.block_timestamp = startTime + m_biddingTime - 10; registrar.setNextValue(12); registrar.reserve(name); // another bid by someone else sendEther(account(1), 10 * ether); m_sender = account(1); - m_evmHost->m_state.timestamp = startTime + 2 * m_biddingTime - 50; + m_evmHost->tx_context.block_timestamp = startTime + 2 * m_biddingTime - 50; registrar.setNextValue(13); registrar.reserve(name); BOOST_CHECK_EQUAL(registrar.owner(name), 0); // end auction by first bidder (which is not highest) trying to overbid again (too late) m_sender = account(0); - m_evmHost->m_state.timestamp = startTime + 4 * m_biddingTime; + m_evmHost->tx_context.block_timestamp = startTime + 4 * m_biddingTime; registrar.setNextValue(20); registrar.reserve(name); BOOST_CHECK_EQUAL(registrar.owner(name), account(1)); diff --git a/test/contracts/FixedFeeRegistrar.cpp b/test/contracts/FixedFeeRegistrar.cpp index e82f389fc..d26cc94a9 100644 --- a/test/contracts/FixedFeeRegistrar.cpp +++ b/test/contracts/FixedFeeRegistrar.cpp @@ -132,7 +132,7 @@ protected: void deployRegistrar() { if (!s_compiledRegistrar) - s_compiledRegistrar.reset(new bytes(compileContract(registrarCode, "FixedFeeRegistrar"))); + s_compiledRegistrar = make_unique(compileContract(registrarCode, "FixedFeeRegistrar")); sendMessage(*s_compiledRegistrar, true); BOOST_REQUIRE(m_transactionSuccessful); diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index d0cbb9956..f9d3b0f0a 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -448,7 +448,7 @@ protected: ) { if (!s_compiledWallet) - s_compiledWallet.reset(new bytes(compileContract(walletCode, "Wallet"))); + s_compiledWallet = make_unique(compileContract(walletCode, "Wallet")); bytes args = encodeArgs(u256(0x60), _required, _dailyLimit, u256(_owners.size()), _owners); sendMessage(*s_compiledWallet + args, true, _value); diff --git a/test/evmc/CMakeLists.txt b/test/evmc/CMakeLists.txt index c08306e18..bd64e8625 100644 --- a/test/evmc/CMakeLists.txt +++ b/test/evmc/CMakeLists.txt @@ -16,7 +16,6 @@ target_sources(evmc INTERFACE ${PROJECT_SOURCE_DIR}/test/evmc/evmc.h ${PROJECT_SOURCE_DIR}/test/evmc/evmc.hpp ${PROJECT_SOURCE_DIR}/test/evmc/helpers.h - ${PROJECT_SOURCE_DIR}/test/evmc/helpers.hpp ${PROJECT_SOURCE_DIR}/test/evmc/utils.h ) target_include_directories(evmc INTERFACE ${PROJECT_SOURCE_DIR}/test/) diff --git a/test/evmc/README.md b/test/evmc/README.md new file mode 100644 index 000000000..37417222c --- /dev/null +++ b/test/evmc/README.md @@ -0,0 +1,3 @@ +# EVMC + +This is an import of [EVMC](https://github.com/ethereum/evmc) version [7.0.0](https://github.com/ethereum/evmc/releases/tag/v7.0.0). diff --git a/test/evmc/evmc.h b/test/evmc/evmc.h index c826bfeae..3fc3ca610 100644 --- a/test/evmc/evmc.h +++ b/test/evmc/evmc.h @@ -44,7 +44,7 @@ enum * * @see @ref versioning */ - EVMC_ABI_VERSION = 6 + EVMC_ABI_VERSION = 7 }; @@ -153,9 +153,15 @@ struct evmc_tx_context int64_t block_timestamp; /**< The block timestamp. */ int64_t block_gas_limit; /**< The block gas limit. */ evmc_uint256be block_difficulty; /**< The block difficulty. */ + evmc_uint256be chain_id; /**< The blockchain's ChainID. */ }; -struct evmc_context; +/** + * @struct evmc_host_context + * The opaque data type representing the Host execution context. + * @see evmc_execute_fn(). + */ +struct evmc_host_context; /** * Get transaction context callback function. @@ -166,7 +172,7 @@ struct evmc_context; * @param context The pointer to the Host execution context. * @return The transaction context. */ -typedef struct evmc_tx_context (*evmc_get_tx_context_fn)(struct evmc_context* context); +typedef struct evmc_tx_context (*evmc_get_tx_context_fn)(struct evmc_host_context* context); /** * Get block hash callback function. @@ -180,7 +186,7 @@ typedef struct evmc_tx_context (*evmc_get_tx_context_fn)(struct evmc_context* co * @return The block hash or null bytes * if the information about the block is not available. */ -typedef evmc_bytes32 (*evmc_get_block_hash_fn)(struct evmc_context* context, int64_t number); +typedef evmc_bytes32 (*evmc_get_block_hash_fn)(struct evmc_host_context* context, int64_t number); /** * The execution status code. @@ -420,7 +426,8 @@ struct evmc_result * @param address The address of the account the query is about. * @return true if exists, false otherwise. */ -typedef bool (*evmc_account_exists_fn)(struct evmc_context* context, const evmc_address* address); +typedef bool (*evmc_account_exists_fn)(struct evmc_host_context* context, + const evmc_address* address); /** * Get storage callback function. @@ -433,7 +440,7 @@ typedef bool (*evmc_account_exists_fn)(struct evmc_context* context, const evmc_ * @return The storage value at the given storage key or null bytes * if the account does not exist. */ -typedef evmc_bytes32 (*evmc_get_storage_fn)(struct evmc_context* context, +typedef evmc_bytes32 (*evmc_get_storage_fn)(struct evmc_host_context* context, const evmc_address* address, const evmc_bytes32* key); @@ -492,7 +499,7 @@ enum evmc_storage_status * @param value The value to be stored. * @return The effect on the storage item. */ -typedef enum evmc_storage_status (*evmc_set_storage_fn)(struct evmc_context* context, +typedef enum evmc_storage_status (*evmc_set_storage_fn)(struct evmc_host_context* context, const evmc_address* address, const evmc_bytes32* key, const evmc_bytes32* value); @@ -506,7 +513,7 @@ typedef enum evmc_storage_status (*evmc_set_storage_fn)(struct evmc_context* con * @param address The address of the account. * @return The balance of the given account or 0 if the account does not exist. */ -typedef evmc_uint256be (*evmc_get_balance_fn)(struct evmc_context* context, +typedef evmc_uint256be (*evmc_get_balance_fn)(struct evmc_host_context* context, const evmc_address* address); /** @@ -519,10 +526,11 @@ typedef evmc_uint256be (*evmc_get_balance_fn)(struct evmc_context* context, * @param address The address of the account. * @return The size of the code in the account or 0 if the account does not exist. */ -typedef size_t (*evmc_get_code_size_fn)(struct evmc_context* context, const evmc_address* address); +typedef size_t (*evmc_get_code_size_fn)(struct evmc_host_context* context, + const evmc_address* address); /** - * Get code size callback function. + * Get code hash callback function. * * This callback function is used by a VM to get the keccak256 hash of the code stored * in the account at the given address. For existing accounts not having a code, this @@ -532,28 +540,27 @@ typedef size_t (*evmc_get_code_size_fn)(struct evmc_context* context, const evmc * @param address The address of the account. * @return The hash of the code in the account or null bytes if the account does not exist. */ -typedef evmc_bytes32 (*evmc_get_code_hash_fn)(struct evmc_context* context, +typedef evmc_bytes32 (*evmc_get_code_hash_fn)(struct evmc_host_context* context, const evmc_address* address); /** * Copy code callback function. * - * This callback function is used by an EVM to request a copy of the code - * of the given account to the memory buffer provided by the EVM. - * The Client MUST copy the requested code, starting with the given offset, - * to the provided memory buffer up to the size of the buffer or the size of - * the code, whichever is smaller. + * This callback function is used by an EVM to request a copy of the code + * of the given account to the memory buffer provided by the EVM. + * The Client MUST copy the requested code, starting with the given offset, + * to the provided memory buffer up to the size of the buffer or the size of + * the code, whichever is smaller. * - * @param context The pointer to the Client execution context. - * @see ::evmc_context. - * @param address The address of the account. - * @param code_offset The offset of the code to copy. - * @param buffer_data The pointer to the memory buffer allocated by the EVM - * to store a copy of the requested code. - * @param buffer_size The size of the memory buffer. - * @return The number of bytes copied to the buffer by the Client. + * @param context The pointer to the Host execution context. See ::evmc_host_context. + * @param address The address of the account. + * @param code_offset The offset of the code to copy. + * @param buffer_data The pointer to the memory buffer allocated by the EVM + * to store a copy of the requested code. + * @param buffer_size The size of the memory buffer. + * @return The number of bytes copied to the buffer by the Client. */ -typedef size_t (*evmc_copy_code_fn)(struct evmc_context* context, +typedef size_t (*evmc_copy_code_fn)(struct evmc_host_context* context, const evmc_address* address, size_t code_offset, uint8_t* buffer_data, @@ -562,34 +569,31 @@ typedef size_t (*evmc_copy_code_fn)(struct evmc_context* context, /** * Selfdestruct callback function. * - * This callback function is used by an EVM to SELFDESTRUCT given contract. - * The execution of the contract will not be stopped, that is up to the EVM. + * This callback function is used by an EVM to SELFDESTRUCT given contract. + * The execution of the contract will not be stopped, that is up to the EVM. * - * @param context The pointer to the Host execution context. - * @see ::evmc_context. - * @param address The address of the contract to be selfdestructed. - * @param beneficiary The address where the remaining ETH is going to be - * transferred. + * @param context The pointer to the Host execution context. See ::evmc_host_context. + * @param address The address of the contract to be selfdestructed. + * @param beneficiary The address where the remaining ETH is going to be transferred. */ -typedef void (*evmc_selfdestruct_fn)(struct evmc_context* context, +typedef void (*evmc_selfdestruct_fn)(struct evmc_host_context* context, const evmc_address* address, const evmc_address* beneficiary); /** * Log callback function. * - * This callback function is used by an EVM to inform about a LOG that happened - * during an EVM bytecode execution. - * @param context The pointer to the Host execution context. - * @see ::evmc_context. - * @param address The address of the contract that generated the log. - * @param data The pointer to unindexed data attached to the log. - * @param data_size The length of the data. - * @param topics The pointer to the array of topics attached to the log. - * @param topics_count The number of the topics. Valid values are between - * 0 and 4 inclusively. + * This callback function is used by an EVM to inform about a LOG that happened + * during an EVM bytecode execution. + * + * @param context The pointer to the Host execution context. See ::evmc_host_context. + * @param address The address of the contract that generated the log. + * @param data The pointer to unindexed data attached to the log. + * @param data_size The length of the data. + * @param topics The pointer to the array of topics attached to the log. + * @param topics_count The number of the topics. Valid values are between 0 and 4 inclusively. */ -typedef void (*evmc_emit_log_fn)(struct evmc_context* context, +typedef void (*evmc_emit_log_fn)(struct evmc_host_context* context, const evmc_address* address, const uint8_t* data, size_t data_size, @@ -599,11 +603,11 @@ typedef void (*evmc_emit_log_fn)(struct evmc_context* context, /** * Pointer to the callback function supporting EVM calls. * - * @param context The pointer to the Host execution context. - * @param msg The call parameters. + * @param context The pointer to the Host execution context. + * @param msg The call parameters. * @return The result of the call. */ -typedef struct evmc_result (*evmc_call_fn)(struct evmc_context* context, +typedef struct evmc_result (*evmc_call_fn)(struct evmc_host_context* context, const struct evmc_message* msg); /** @@ -654,31 +658,15 @@ struct evmc_host_interface }; -/** - * Execution context managed by the Host. - * - * The Host MUST pass the pointer to the execution context to ::evmc_execute_fn. - * The VM MUST pass the same pointer back to the Host in every callback function. - * The context MUST contain at least the function table defining - * the context callback interface. - * Optionally, the Host MAY include in the context additional data. - */ -struct evmc_context -{ - /** The Host interface. */ - const struct evmc_host_interface* host; -}; - - /* Forward declaration. */ -struct evmc_instance; +struct evmc_vm; /** - * Destroys the EVM instance. + * Destroys the VM instance. * - * @param evm The EVM instance to be destroyed. + * @param vm The VM instance to be destroyed. */ -typedef void (*evmc_destroy_fn)(struct evmc_instance* evm); +typedef void (*evmc_destroy_fn)(struct evmc_vm* vm); /** * Possible outcomes of evmc_set_option. @@ -691,19 +679,19 @@ enum evmc_set_option_result }; /** - * Configures the EVM instance. + * Configures the VM instance. * - * Allows modifying options of the EVM instance. - * Options: - * - code cache behavior: on, off, read-only, ... - * - optimizations, + * Allows modifying options of the VM instance. + * Options: + * - code cache behavior: on, off, read-only, ... + * - optimizations, * - * @param evm The EVM instance to be configured. - * @param name The option name. NULL-terminated string. Cannot be NULL. - * @param value The new option value. NULL-terminated string. Cannot be NULL. - * @return The outcome of the operation. + * @param vm The VM instance to be configured. + * @param name The option name. NULL-terminated string. Cannot be NULL. + * @param value The new option value. NULL-terminated string. Cannot be NULL. + * @return The outcome of the operation. */ -typedef enum evmc_set_option_result (*evmc_set_option_fn)(struct evmc_instance* evm, +typedef enum evmc_set_option_result (*evmc_set_option_fn)(struct evmc_vm* vm, char const* name, char const* value); @@ -762,6 +750,7 @@ enum evmc_revision * The Petersburg revision. * * Other names: Constantinople2, ConstantinopleFix. + * * https://eips.ethereum.org/EIPS/eip-1716 */ EVMC_PETERSBURG = 6, @@ -773,23 +762,15 @@ enum evmc_revision */ EVMC_ISTANBUL = 7, + /** + * The Berlin revision. + * + * The spec draft: https://eips.ethereum.org/EIPS/eip-2070. + */ + EVMC_BERLIN = 8, + /** The maximum EVM revision supported. */ - EVMC_MAX_REVISION = EVMC_ISTANBUL, - - - /** - * Reserved for the post-Constantinople upgrade. - * - * @deprecated Replaced with ::EVMC_PETERSBURG. - */ - EVMC_CONSTANTINOPLE2 EVMC_DEPRECATED = EVMC_PETERSBURG, - - /** - * The latests EVM revision supported. - * - * @deprecated Replaced with ::EVMC_MAX_REVISION. - */ - EVMC_LATEST_REVISION EVMC_DEPRECATED = EVMC_MAX_REVISION + EVMC_MAX_REVISION = EVMC_BERLIN }; @@ -798,19 +779,22 @@ enum evmc_revision * * This function MAY be invoked multiple times for a single VM instance. * - * @param instance The VM instance. This argument MUST NOT be NULL. - * @param context The pointer to the Host execution context to be passed - * to the Host interface methods (::evmc_host_interface). - * This argument MUST NOT be NULL unless - * the @p instance has the ::EVMC_CAPABILITY_PRECOMPILES capability. + * @param vm The VM instance. This argument MUST NOT be NULL. + * @param host The Host interface. This argument MUST NOT be NULL unless + * the @p vm has the ::EVMC_CAPABILITY_PRECOMPILES capability. + * @param context The opaque pointer to the Host execution context. + * This argument MAY be NULL. The VM MUST pass the same + * pointer to the methods of the @p host interface. + * The VM MUST NOT dereference the pointer. * @param rev The requested EVM specification revision. * @param msg The call parameters. See ::evmc_message. This argument MUST NOT be NULL. * @param code The reference to the code to be executed. This argument MAY be NULL. * @param code_size The length of the code. If @p code is NULL this argument MUST be 0. * @return The execution result. */ -typedef struct evmc_result (*evmc_execute_fn)(struct evmc_instance* instance, - struct evmc_context* context, +typedef struct evmc_result (*evmc_execute_fn)(struct evmc_vm* vm, + const struct evmc_host_interface* host, + struct evmc_host_context* context, enum evmc_revision rev, const struct evmc_message* msg, uint8_t const* code, @@ -855,98 +839,20 @@ typedef uint32_t evmc_capabilities_flagset; * Return the supported capabilities of the VM instance. * * This function MAY be invoked multiple times for a single VM instance, - * and its value MAY be influenced by calls to evmc_instance::set_option. + * and its value MAY be influenced by calls to evmc_vm::set_option. * - * @param instance The EVM instance. - * @return The supported capabilities of the VM. @see evmc_capabilities. + * @param vm The VM instance. + * @return The supported capabilities of the VM. @see evmc_capabilities. */ -typedef evmc_capabilities_flagset (*evmc_get_capabilities_fn)(struct evmc_instance* instance); - -/** - * The opaque type representing a Client-side tracer object. - * - * @deprecated Deprecated since EVMC 6.3, see evmc_instance::set_tracer(). - */ -struct evmc_tracer_context; - -/** - * The callback to trace instructions execution in an EVM. - * - * This function informs the Client what instruction has been executed in the EVM implementation - * and what are the results of executing this particular instruction. - * The message level information (like call depth, destination address, etc.) are not provided here. - * This piece of information can be acquired by inspecting messages being sent to the EVM in - * ::evmc_execute_fn and the results of the messages execution. - * - * @deprecated Deprecated since EVMC 6.3, see evmc_instance::set_tracer(). - * - * @param context The pointer to the Client-side tracing context. This allows to - * implement the tracer in OOP manner. - * @param code_offset The current instruction position in the code. - * @param status_code The status code of the instruction execution. - * @param gas_left The amount of the gas left after the instruction execution. - * @param stack_num_items The current EVM stack height after the instruction execution. - * @param pushed_stack_item The top EVM stack item pushed as the result of the instruction - * execution. This value is null when the instruction does not push - * anything to the stack. - * @param memory_size The size of the EVM memory after the instruction execution. - * @param changed_memory_offset The offset in number of bytes of the beginning of the memory area - * modified as the result of the instruction execution. - * The Client MAY use this information together with - * @p changed_memory_size and @p changed_memory to incrementally - * update the copy of the full VM's memory. - * @param changed_memory_size The size of the memory area modified as the result of - * the instruction execution. - * @param changed_memory The pointer to the memory area modified as the result of - * the instruction execution. - * The Client MAY access the pointed memory area - * (limited by the @p changed_memory_size) only during the current - * execution of the evmc_trace_callback(). - * The pointer MUST NOT be stored by the Client. - * The Client MUST NOT assume that - * `changed_memory - changed_memory_offset` is a valid base pointer - * of the VM memory. - */ -typedef void (*evmc_trace_callback)(struct evmc_tracer_context* context, - size_t code_offset, - enum evmc_status_code status_code, - int64_t gas_left, - size_t stack_num_items, - const evmc_uint256be* pushed_stack_item, - size_t memory_size, - size_t changed_memory_offset, - size_t changed_memory_size, - const uint8_t* changed_memory); - -/** - * Sets the EVM instruction tracer. - * - * When the tracer is set in the EVM instance, the EVM SHOULD call back the tracer with information - * about instructions execution in the EVM. - * @see ::evmc_trace_callback. - * - * This will overwrite the previous settings (the callback and the context). - * - * @deprecated Deprecated since EVMC 6.3, see evmc_instance::set_tracer(). - * - * @param instance The EVM instance. - * @param callback The tracer callback function. This argument MAY be NULL to disable previously - * set tracer. - * @param context The Client-side tracer context. This argument MAY be NULL in case the tracer - * does not require any context. This argument MUST be NULL if the callback - * argument is NULL. - */ -typedef void (*evmc_set_tracer_fn)(struct evmc_instance* instance, - evmc_trace_callback callback, - struct evmc_tracer_context* context); +typedef evmc_capabilities_flagset (*evmc_get_capabilities_fn)(struct evmc_vm* vm); /** - * The EVM instance. + * The VM instance. * * Defines the base struct of the VM implementation. */ -struct evmc_instance +struct evmc_vm { /** * EVMC ABI version implemented by the VM instance. @@ -973,14 +879,14 @@ struct evmc_instance const char* version; /** - * Pointer to function destroying the EVM instance. + * Pointer to function destroying the VM instance. * * This is a mandatory method and MUST NOT be set to NULL. */ evmc_destroy_fn destroy; /** - * Pointer to function executing a code by the EVM instance. + * Pointer to function executing a code by the VM instance. * * This is a mandatory method and MUST NOT be set to NULL. */ @@ -998,17 +904,6 @@ struct evmc_instance */ evmc_get_capabilities_fn get_capabilities; - /** - * Optional pointer to function setting the EVM instruction tracer. - * - * If the EVM does not support this feature the pointer can be NULL. - * - * @deprecated - * Since EVMC 6.3, the tracing API has been deprecated as there have been some - * design flaws discovered. New API is expected to be introduced in future. - */ - evmc_set_tracer_fn set_tracer; - /** * Optional pointer to function modifying VM's options. * @@ -1033,9 +928,9 @@ struct evmc_instance * For example, the shared library with the "beta-interpreter" implementation may be named * `libbeta-interpreter.so`. * - * @return EVM instance or NULL indicating instance creation failure. + * @return The VM instance or NULL indicating instance creation failure. */ -struct evmc_instance* evmc_create_example_vm(void); +struct evmc_vm* evmc_create_example_vm(void); #endif #if __cplusplus diff --git a/test/evmc/evmc.hpp b/test/evmc/evmc.hpp index ff486a232..0b44cd12c 100644 --- a/test/evmc/evmc.hpp +++ b/test/evmc/evmc.hpp @@ -294,7 +294,7 @@ public: /// Destructor responsible for automatically releasing attached resources. ~result() noexcept { - if (release) + if (release != nullptr) release(this); } @@ -334,87 +334,6 @@ public: } }; -/// @copybrief evmc_instance -/// -/// This is a RAII wrapper for evmc_instance and objects of this type -/// automatically destroys the VM instance. -class vm -{ -public: - vm() noexcept = default; - - /// Converting constructor from evmc_instance. - explicit vm(evmc_instance* instance) noexcept : m_instance{instance} {} - - /// Destructor responsible for automatically destroying the VM instance. - ~vm() noexcept - { - if (m_instance) - m_instance->destroy(m_instance); - } - - vm(const vm&) = delete; - vm& operator=(const vm&) = delete; - - /// Move constructor. - vm(vm&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; } - - /// Move assignment operator. - vm& operator=(vm&& other) noexcept - { - this->~vm(); - m_instance = other.m_instance; - other.m_instance = nullptr; - return *this; - } - - /// The constructor that captures a VM instance and configures the instance - /// with provided list of options. - vm(evmc_instance* instance, - std::initializer_list> options) noexcept - : m_instance{instance} - { - for (auto option : options) - set_option(option.first, option.second); - } - - /// Checks if contains a valid pointer to the VM instance. - explicit operator bool() const noexcept { return m_instance != nullptr; } - - /// Checks whenever the VM instance is ABI compatible with the current EVMC API. - bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; } - - /// @copydoc evmc_instance::name - char const* name() const noexcept { return m_instance->name; } - - /// @copydoc evmc_instance::version - char const* version() const noexcept { return m_instance->version; } - - /// @copydoc evmc::instance::get_capabilities - evmc_capabilities_flagset get_capabilities() const noexcept - { - return m_instance->get_capabilities(m_instance); - } - - /// @copydoc evmc_set_option() - evmc_set_option_result set_option(const char name[], const char value[]) noexcept - { - return evmc_set_option(m_instance, name, value); - } - - /// @copydoc evmc_execute() - result execute(evmc_context& ctx, - evmc_revision rev, - const evmc_message& msg, - const uint8_t* code, - size_t code_size) noexcept - { - return result{m_instance->execute(m_instance, &ctx, rev, &msg, code, code_size)}; - } - -private: - evmc_instance* m_instance = nullptr; -}; /// The EVMC Host interface class HostInterface @@ -423,10 +342,10 @@ public: virtual ~HostInterface() noexcept = default; /// @copydoc evmc_host_interface::account_exists - virtual bool account_exists(const address& addr) noexcept = 0; + virtual bool account_exists(const address& addr) const noexcept = 0; /// @copydoc evmc_host_interface::get_storage - virtual bytes32 get_storage(const address& addr, const bytes32& key) noexcept = 0; + virtual bytes32 get_storage(const address& addr, const bytes32& key) const noexcept = 0; /// @copydoc evmc_host_interface::set_storage virtual evmc_storage_status set_storage(const address& addr, @@ -434,19 +353,19 @@ public: const bytes32& value) noexcept = 0; /// @copydoc evmc_host_interface::get_balance - virtual uint256be get_balance(const address& addr) noexcept = 0; + virtual uint256be get_balance(const address& addr) const noexcept = 0; /// @copydoc evmc_host_interface::get_code_size - virtual size_t get_code_size(const address& addr) noexcept = 0; + virtual size_t get_code_size(const address& addr) const noexcept = 0; /// @copydoc evmc_host_interface::get_code_hash - virtual bytes32 get_code_hash(const address& addr) noexcept = 0; + virtual bytes32 get_code_hash(const address& addr) const noexcept = 0; /// @copydoc evmc_host_interface::copy_code virtual size_t copy_code(const address& addr, size_t code_offset, uint8_t* buffer_data, - size_t buffer_size) noexcept = 0; + size_t buffer_size) const noexcept = 0; /// @copydoc evmc_host_interface::selfdestruct virtual void selfdestruct(const address& addr, const address& beneficiary) noexcept = 0; @@ -455,10 +374,10 @@ public: virtual result call(const evmc_message& msg) noexcept = 0; /// @copydoc evmc_host_interface::get_tx_context - virtual evmc_tx_context get_tx_context() noexcept = 0; + virtual evmc_tx_context get_tx_context() const noexcept = 0; /// @copydoc evmc_host_interface::get_block_hash - virtual bytes32 get_block_hash(int64_t block_number) noexcept = 0; + virtual bytes32 get_block_hash(int64_t block_number) const noexcept = 0; /// @copydoc evmc_host_interface::emit_log virtual void emit_log(const address& addr, @@ -471,64 +390,72 @@ public: /// Wrapper around EVMC host context / host interface. /// -/// To be used by VM implementations as better alternative to using ::evmc_context directly. +/// To be used by VM implementations as better alternative to using ::evmc_host_context directly. class HostContext : public HostInterface { - evmc_context* context = nullptr; - evmc_tx_context tx_context = {}; + const evmc_host_interface* host = nullptr; + evmc_host_context* context = nullptr; + mutable evmc_tx_context tx_context = {}; public: - /// Implicit converting constructor from evmc_context. - HostContext(evmc_context* ctx) noexcept : context{ctx} {} // NOLINT + /// Default constructor for null Host context. + HostContext() = default; - bool account_exists(const address& address) noexcept final + /// Constructor from the EVMC Host primitives. + /// @param interface The reference to the Host interface. + /// @param ctx The pointer to the Host context object. This parameter MAY be null. + HostContext(const evmc_host_interface& interface, evmc_host_context* ctx) noexcept + : host{&interface}, context{ctx} + {} + + bool account_exists(const address& address) const noexcept final { - return context->host->account_exists(context, &address); + return host->account_exists(context, &address); } - bytes32 get_storage(const address& address, const bytes32& key) noexcept final + bytes32 get_storage(const address& address, const bytes32& key) const noexcept final { - return context->host->get_storage(context, &address, &key); + return host->get_storage(context, &address, &key); } evmc_storage_status set_storage(const address& address, const bytes32& key, const bytes32& value) noexcept final { - return context->host->set_storage(context, &address, &key, &value); + return host->set_storage(context, &address, &key, &value); } - uint256be get_balance(const address& address) noexcept final + uint256be get_balance(const address& address) const noexcept final { - return context->host->get_balance(context, &address); + return host->get_balance(context, &address); } - size_t get_code_size(const address& address) noexcept final + size_t get_code_size(const address& address) const noexcept final { - return context->host->get_code_size(context, &address); + return host->get_code_size(context, &address); } - bytes32 get_code_hash(const address& address) noexcept final + bytes32 get_code_hash(const address& address) const noexcept final { - return context->host->get_code_hash(context, &address); + return host->get_code_hash(context, &address); } size_t copy_code(const address& address, size_t code_offset, uint8_t* buffer_data, - size_t buffer_size) noexcept final + size_t buffer_size) const noexcept final { - return context->host->copy_code(context, &address, code_offset, buffer_data, buffer_size); + return host->copy_code(context, &address, code_offset, buffer_data, buffer_size); } void selfdestruct(const address& addr, const address& beneficiary) noexcept final { - context->host->selfdestruct(context, &addr, &beneficiary); + host->selfdestruct(context, &addr, &beneficiary); } result call(const evmc_message& message) noexcept final { - return result{context->host->call(context, &message)}; + return result{host->call(context, &message)}; } /// @copydoc HostInterface::get_tx_context() @@ -537,16 +464,16 @@ public: /// by assuming that the block timestamp should never be zero. /// /// @return The cached transaction context. - evmc_tx_context get_tx_context() noexcept final + evmc_tx_context get_tx_context() const noexcept final { if (tx_context.block_timestamp == 0) - tx_context = context->host->get_tx_context(context); + tx_context = host->get_tx_context(context); return tx_context; } - bytes32 get_block_hash(int64_t number) noexcept final + bytes32 get_block_hash(int64_t number) const noexcept final { - return context->host->get_block_hash(context, number); + return host->get_block_hash(context, number); } void emit_log(const address& addr, @@ -555,97 +482,255 @@ public: const bytes32 topics[], size_t topics_count) noexcept final { - context->host->emit_log(context, &addr, data, data_size, topics, topics_count); + host->emit_log(context, &addr, data, data_size, topics, topics_count); } }; + /// Abstract class to be used by Host implementations. /// /// When implementing EVMC Host, you can directly inherit from the evmc::Host class. /// This way your implementation will be simpler by avoiding manual handling -/// of the ::evmc_context and the ::evmc_context::host. -class Host : public HostInterface, public evmc_context +/// of the ::evmc_host_context and the ::evmc_host_interface. +class Host : public HostInterface { public: - inline Host() noexcept; + /// Provides access to the global host interface. + /// @returns Reference to the host interface object. + static const evmc_host_interface& get_interface() noexcept; + + /// Converts the Host object to the opaque host context pointer. + /// @returns Pointer to evmc_host_context. + evmc_host_context* to_context() noexcept { return reinterpret_cast(this); } + + /// Converts the opaque host context pointer back to the original Host object. + /// @tparam DerivedClass The class derived from the Host class. + /// @param context The opaque host context pointer. + /// @returns The pointer to DerivedClass. + template + static DerivedClass* from_context(evmc_host_context* context) noexcept + { + // Get pointer of the Host base class. + auto* h = reinterpret_cast(context); + + // Additional downcast, only possible if DerivedClass inherits from Host. + return static_cast(h); + } }; + +/// @copybrief evmc_vm +/// +/// This is a RAII wrapper for evmc_vm, and object of this type +/// automatically destroys the VM instance. +class VM +{ +public: + VM() noexcept = default; + + /// Converting constructor from evmc_vm. + explicit VM(evmc_vm* vm) noexcept : m_instance{vm} {} + + /// Destructor responsible for automatically destroying the VM instance. + ~VM() noexcept + { + if (m_instance != nullptr) + m_instance->destroy(m_instance); + } + + VM(const VM&) = delete; + VM& operator=(const VM&) = delete; + + /// Move constructor. + VM(VM&& other) noexcept : m_instance{other.m_instance} { other.m_instance = nullptr; } + + /// Move assignment operator. + VM& operator=(VM&& other) noexcept + { + this->~VM(); + m_instance = other.m_instance; + other.m_instance = nullptr; + return *this; + } + + /// The constructor that captures a VM instance and configures the instance + /// with the provided list of options. + inline VM(evmc_vm* vm, + std::initializer_list> options) noexcept; + + /// Checks if contains a valid pointer to the VM instance. + explicit operator bool() const noexcept { return m_instance != nullptr; } + + /// Checks whenever the VM instance is ABI compatible with the current EVMC API. + bool is_abi_compatible() const noexcept { return m_instance->abi_version == EVMC_ABI_VERSION; } + + /// @copydoc evmc_vm::name + char const* name() const noexcept { return m_instance->name; } + + /// @copydoc evmc_vm::version + char const* version() const noexcept { return m_instance->version; } + + /// Checks if the VM has the given capability. + bool has_capability(evmc_capabilities capability) const noexcept + { + return (get_capabilities() & static_cast(capability)) != 0; + } + + /// @copydoc evmc::vm::get_capabilities + evmc_capabilities_flagset get_capabilities() const noexcept + { + return m_instance->get_capabilities(m_instance); + } + + /// @copydoc evmc_set_option() + evmc_set_option_result set_option(const char name[], const char value[]) noexcept + { + return evmc_set_option(m_instance, name, value); + } + + /// @copydoc evmc_execute() + result execute(const evmc_host_interface& host, + evmc_host_context* ctx, + evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return result{m_instance->execute(m_instance, &host, ctx, rev, &msg, code, code_size)}; + } + + /// Convenient variant of the VM::execute() that takes reference to evmc::Host class. + result execute(Host& host, + evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return execute(Host::get_interface(), host.to_context(), rev, msg, code, code_size); + } + + /// Executes code without the Host context. + /// + /// The same as + /// execute(const evmc_host_interface&, evmc_host_context*, evmc_revision, + /// const evmc_message&, const uint8_t*, size_t), + /// but without providing the Host context and interface. + /// This method is for experimental precompiles support where execution is + /// guaranteed not to require any Host access. + result execute(evmc_revision rev, + const evmc_message& msg, + const uint8_t* code, + size_t code_size) noexcept + { + return result{ + m_instance->execute(m_instance, nullptr, nullptr, rev, &msg, code, code_size)}; + } + +private: + evmc_vm* m_instance = nullptr; +}; + +inline VM::VM(evmc_vm* vm, + std::initializer_list> options) noexcept + : m_instance{vm} +{ + // This constructor is implemented outside of the class definition to workaround a doxygen bug. + for (const auto& option : options) + set_option(option.first, option.second); +} + + namespace internal { -inline bool account_exists(evmc_context* h, const evmc_address* addr) noexcept +inline bool account_exists(evmc_host_context* h, const evmc_address* addr) noexcept { - return static_cast(h)->account_exists(*addr); + return Host::from_context(h)->account_exists(*addr); } -inline evmc_bytes32 get_storage(evmc_context* h, + +inline evmc_bytes32 get_storage(evmc_host_context* h, const evmc_address* addr, const evmc_bytes32* key) noexcept { - return static_cast(h)->get_storage(*addr, *key); + return Host::from_context(h)->get_storage(*addr, *key); } -inline evmc_storage_status set_storage(evmc_context* h, + +inline evmc_storage_status set_storage(evmc_host_context* h, const evmc_address* addr, const evmc_bytes32* key, const evmc_bytes32* value) noexcept { - return static_cast(h)->set_storage(*addr, *key, *value); + return Host::from_context(h)->set_storage(*addr, *key, *value); } -inline evmc_uint256be get_balance(evmc_context* h, const evmc_address* addr) noexcept + +inline evmc_uint256be get_balance(evmc_host_context* h, const evmc_address* addr) noexcept { - return static_cast(h)->get_balance(*addr); + return Host::from_context(h)->get_balance(*addr); } -inline size_t get_code_size(evmc_context* h, const evmc_address* addr) noexcept + +inline size_t get_code_size(evmc_host_context* h, const evmc_address* addr) noexcept { - return static_cast(h)->get_code_size(*addr); + return Host::from_context(h)->get_code_size(*addr); } -inline evmc_bytes32 get_code_hash(evmc_context* h, const evmc_address* addr) noexcept + +inline evmc_bytes32 get_code_hash(evmc_host_context* h, const evmc_address* addr) noexcept { - return static_cast(h)->get_code_hash(*addr); + return Host::from_context(h)->get_code_hash(*addr); } -inline size_t copy_code(evmc_context* h, + +inline size_t copy_code(evmc_host_context* h, const evmc_address* addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) noexcept { - return static_cast(h)->copy_code(*addr, code_offset, buffer_data, buffer_size); + return Host::from_context(h)->copy_code(*addr, code_offset, buffer_data, buffer_size); } -inline void selfdestruct(evmc_context* h, + +inline void selfdestruct(evmc_host_context* h, const evmc_address* addr, const evmc_address* beneficiary) noexcept { - static_cast(h)->selfdestruct(*addr, *beneficiary); + Host::from_context(h)->selfdestruct(*addr, *beneficiary); } -inline evmc_result call(evmc_context* h, const evmc_message* msg) noexcept + +inline evmc_result call(evmc_host_context* h, const evmc_message* msg) noexcept { - return static_cast(h)->call(*msg).release_raw(); + return Host::from_context(h)->call(*msg).release_raw(); } -inline evmc_tx_context get_tx_context(evmc_context* h) noexcept + +inline evmc_tx_context get_tx_context(evmc_host_context* h) noexcept { - return static_cast(h)->get_tx_context(); + return Host::from_context(h)->get_tx_context(); } -inline evmc_bytes32 get_block_hash(evmc_context* h, int64_t block_number) noexcept + +inline evmc_bytes32 get_block_hash(evmc_host_context* h, int64_t block_number) noexcept { - return static_cast(h)->get_block_hash(block_number); + return Host::from_context(h)->get_block_hash(block_number); } -inline void emit_log(evmc_context* h, + +inline void emit_log(evmc_host_context* h, const evmc_address* addr, const uint8_t* data, size_t data_size, const evmc_bytes32 topics[], size_t num_topics) noexcept { - static_cast(h)->emit_log(*addr, data, data_size, static_cast(topics), + Host::from_context(h)->emit_log(*addr, data, data_size, static_cast(topics), num_topics); } - -constexpr evmc_host_interface interface{ - account_exists, get_storage, set_storage, get_balance, get_code_size, get_code_hash, - copy_code, selfdestruct, call, get_tx_context, get_block_hash, emit_log, -}; } // namespace internal -inline Host::Host() noexcept : evmc_context{&evmc::internal::interface} {} - +inline const evmc_host_interface& Host::get_interface() noexcept +{ + static constexpr evmc_host_interface interface{ + ::evmc::internal::account_exists, ::evmc::internal::get_storage, + ::evmc::internal::set_storage, ::evmc::internal::get_balance, + ::evmc::internal::get_code_size, ::evmc::internal::get_code_hash, + ::evmc::internal::copy_code, ::evmc::internal::selfdestruct, + ::evmc::internal::call, ::evmc::internal::get_tx_context, + ::evmc::internal::get_block_hash, ::evmc::internal::emit_log}; + return interface; +} } // namespace evmc diff --git a/test/evmc/helpers.h b/test/evmc/helpers.h index 2c4dbceae..36febfd72 100644 --- a/test/evmc/helpers.h +++ b/test/evmc/helpers.h @@ -22,36 +22,35 @@ #include /** - * Returns true if the VM instance has a compatible ABI version. + * Returns true if the VM has a compatible ABI version. */ -static inline int evmc_is_abi_compatible(struct evmc_instance* instance) +static inline bool evmc_is_abi_compatible(struct evmc_vm* vm) { - return instance->abi_version == EVMC_ABI_VERSION; + return vm->abi_version == EVMC_ABI_VERSION; } /** - * Returns the name of the VM instance. + * Returns the name of the VM. */ -static inline const char* evmc_vm_name(struct evmc_instance* instance) +static inline const char* evmc_vm_name(struct evmc_vm* vm) { - return instance->name; + return vm->name; } /** - * Returns the version of the VM instance. + * Returns the version of the VM. */ -static inline const char* evmc_vm_version(struct evmc_instance* instance) +static inline const char* evmc_vm_version(struct evmc_vm* vm) { - return instance->version; + return vm->version; } /** - * Checks if the VM instance has the given capability. + * Checks if the VM has the given capability. * * @see evmc_get_capabilities_fn */ -static inline bool evmc_vm_has_capability(struct evmc_instance* vm, - enum evmc_capabilities capability) +static inline bool evmc_vm_has_capability(struct evmc_vm* vm, enum evmc_capabilities capability) { return (vm->get_capabilities(vm) & (evmc_capabilities_flagset)capability) != 0; } @@ -61,52 +60,39 @@ static inline bool evmc_vm_has_capability(struct evmc_instance* vm, * * @see evmc_destroy_fn */ -static inline void evmc_destroy(struct evmc_instance* instance) +static inline void evmc_destroy(struct evmc_vm* vm) { - instance->destroy(instance); + vm->destroy(vm); } /** - * Sets the option for the VM instance, if the feature is supported by the VM. + * Sets the option for the VM, if the feature is supported by the VM. * * @see evmc_set_option_fn */ -static inline enum evmc_set_option_result evmc_set_option(struct evmc_instance* instance, +static inline enum evmc_set_option_result evmc_set_option(struct evmc_vm* vm, char const* name, char const* value) { - if (instance->set_option) - return instance->set_option(instance, name, value); + if (vm->set_option) + return vm->set_option(vm, name, value); return EVMC_SET_OPTION_INVALID_NAME; } -/** - * Sets the tracer callback for the VM instance, if the feature is supported by the VM. - * - * @see evmc_set_tracer_fn - */ -EVMC_DEPRECATED -static inline void evmc_set_tracer(struct evmc_instance* instance, - evmc_trace_callback callback, - struct evmc_tracer_context* context) -{ - if (instance->set_tracer) - instance->set_tracer(instance, callback, context); -} - /** * Executes code in the VM instance. * * @see evmc_execute_fn. */ -static inline struct evmc_result evmc_execute(struct evmc_instance* instance, - struct evmc_context* context, +static inline struct evmc_result evmc_execute(struct evmc_vm* vm, + const struct evmc_host_interface* host, + struct evmc_host_context* context, enum evmc_revision rev, const struct evmc_message* msg, uint8_t const* code, size_t code_size) { - return instance->execute(instance, context, rev, msg, code, code_size); + return vm->execute(vm, host, context, rev, msg, code, code_size); } /// The evmc_result release function using free() for releasing the memory. diff --git a/test/evmc/helpers.hpp b/test/evmc/helpers.hpp deleted file mode 100644 index f30eec87a..000000000 --- a/test/evmc/helpers.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* EVMC: Ethereum Client-VM Connector API. - * Copyright 2018-2019 The EVMC Authors. - * Licensed under the Apache License, Version 2.0. - */ - -/** - * @file - * A collection of helpers (overloaded operators) for using EVMC types effectively in C++. - * - * @addtogroup helpers - * @{ - */ -#pragma once - -#include - -#include -#include - -using evmc::is_zero; - -/// The comparator for std::map. -EVMC_DEPRECATED -inline bool operator<(const evmc_address& a, const evmc_address& b) -{ - return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0; -} - -/// The comparator for std::map. -EVMC_DEPRECATED -inline bool operator<(const evmc_bytes32& a, const evmc_bytes32& b) -{ - return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) < 0; -} - -/// The comparator for equality. -EVMC_DEPRECATED -inline bool operator==(const evmc_address& a, const evmc_address& b) -{ - return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) == 0; -} - -/// The comparator for equality. -EVMC_DEPRECATED -inline bool operator==(const evmc_bytes32& a, const evmc_bytes32& b) -{ - return std::memcmp(a.bytes, b.bytes, sizeof(a.bytes)) == 0; -} - -/// Parameters for the fnv1a hash function, specialized by the hash result size (size_t). -/// -/// The values for the matching size are taken from -/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV_hash_parameters. -/// -/// @tparam size The size of the hash result (size_t). -template -struct fnv1_params -{ -}; - -/// Parameters for the fnv1a hash function, specialized for the hash result of 4 bytes. -template <> -struct fnv1_params<4> -{ - static constexpr auto prime = 0x1000193; ///< The FNV prime. - static constexpr auto offset_basis = 0x811c9dc5; ///< The FNV offset basis. -}; - -/// Parameters for the fnv1a hash function, specialized for the hash result of 8 bytes. -template <> -struct fnv1_params<8> -{ - static constexpr auto prime = 0x100000001b3; ///< The FNV prime. - static constexpr auto offset_basis = 0xcbf29ce484222325; ///< The FNV offset basis. -}; - -/// FNV1a hash function. -inline size_t fnv1a(const uint8_t* ptr, size_t len) noexcept -{ - using params = fnv1_params; - - auto ret = size_t{params::offset_basis}; - for (size_t i = 0; i < len; i++) - { - ret ^= ptr[i]; - ret *= params::prime; - } - return ret; -} - -namespace std -{ -/// Hash operator template specialization for evmc_address needed for unordered containers. -template <> -struct EVMC_DEPRECATED hash -{ - /// Hash operator using FNV1a. - size_t operator()(const evmc_address& s) const noexcept - { - return fnv1a(s.bytes, sizeof(s.bytes)); - } -}; - -/// Hash operator template needed for std::unordered_set and others using hashes. -template <> -struct EVMC_DEPRECATED hash -{ - /// Hash operator using FNV1a. - size_t operator()(const evmc_bytes32& s) const noexcept - { - return fnv1a(s.bytes, sizeof(s.bytes)); - } -}; -} // namespace std - -/** @} */ diff --git a/test/evmc/loader.c b/test/evmc/loader.c index 30bb39023..0cd4834e7 100644 --- a/test/evmc/loader.c +++ b/test/evmc/loader.c @@ -152,8 +152,8 @@ evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* erro char* base_name = prefixed_name + prefix_length; strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos); - // Trim the file extension. - char* ext_pos = strrchr(prefixed_name, '.'); + // Trim all file extensions. + char* ext_pos = strchr(prefixed_name, '.'); if (ext_pos) *ext_pos = 0; @@ -163,15 +163,7 @@ evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* erro *dash_pos++ = '_'; // Search for the built function name. - while ((create_fn = DLL_GET_CREATE_FN(handle, prefixed_name)) == NULL) - { - // Shorten the base name by skipping the `word_` segment. - const char* shorter_name_pos = strchr(base_name, '_'); - if (!shorter_name_pos) - break; - - memmove(base_name, shorter_name_pos + 1, strlen(shorter_name_pos) + 1); - } + create_fn = DLL_GET_CREATE_FN(handle, prefixed_name); if (!create_fn) create_fn = DLL_GET_CREATE_FN(handle, "evmc_create"); @@ -196,8 +188,7 @@ const char* evmc_last_error_msg() return m; } -struct evmc_instance* evmc_load_and_create(const char* filename, - enum evmc_loader_error_code* error_code) +struct evmc_vm* evmc_load_and_create(const char* filename, enum evmc_loader_error_code* error_code) { // First load the DLL. This also resets the last_error_msg; evmc_create_fn create_fn = evmc_load(filename, error_code); @@ -207,21 +198,21 @@ struct evmc_instance* evmc_load_and_create(const char* filename, enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; - struct evmc_instance* instance = create_fn(); - if (!instance) + struct evmc_vm* vm = create_fn(); + if (!vm) { - ec = set_error(EVMC_LOADER_INSTANCE_CREATION_FAILURE, - "creating EVMC instance of %s has failed", filename); + ec = set_error(EVMC_LOADER_VM_CREATION_FAILURE, "creating EVMC VM of %s has failed", + filename); goto exit; } - if (!evmc_is_abi_compatible(instance)) + if (!evmc_is_abi_compatible(vm)) { ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH, "EVMC ABI version %d of %s mismatches the expected version %d", - instance->abi_version, filename, EVMC_ABI_VERSION); - evmc_destroy(instance); - instance = NULL; + vm->abi_version, filename, EVMC_ABI_VERSION); + evmc_destroy(vm); + vm = NULL; goto exit; } @@ -229,7 +220,7 @@ exit: if (error_code) *error_code = ec; - return instance; + return vm; } /// Gets the token delimited by @p delim character of the string pointed by the @p str_ptr. @@ -255,11 +246,10 @@ static char* get_token(char** str_ptr, char delim) return str; } -struct evmc_instance* evmc_load_and_configure(const char* config, - enum evmc_loader_error_code* error_code) +struct evmc_vm* evmc_load_and_configure(const char* config, enum evmc_loader_error_code* error_code) { enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS; - struct evmc_instance* instance = NULL; + struct evmc_vm* vm = NULL; char config_copy_buffer[PATH_MAX_LENGTH]; if (strcpy_sx(config_copy_buffer, sizeof(config_copy_buffer), config) != 0) @@ -273,14 +263,14 @@ struct evmc_instance* evmc_load_and_configure(const char* config, char* options = config_copy_buffer; const char* path = get_token(&options, ','); - instance = evmc_load_and_create(path, error_code); - if (!instance) + vm = evmc_load_and_create(path, error_code); + if (!vm) return NULL; - if (instance->set_option == NULL && strlen(options) != 0) + if (vm->set_option == NULL && strlen(options) != 0) { ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s) does not support any options", - instance->name, path); + vm->name, path); goto exit; } @@ -293,20 +283,26 @@ struct evmc_instance* evmc_load_and_configure(const char* config, // The option variable will have the value, can be empty. const char* name = get_token(&option, '='); - enum evmc_set_option_result r = instance->set_option(instance, name, option); + enum evmc_set_option_result r = vm->set_option(vm, name, option); switch (r) { case EVMC_SET_OPTION_SUCCESS: break; case EVMC_SET_OPTION_INVALID_NAME: ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s): unknown option '%s'", - instance->name, path, name); + vm->name, path, name); goto exit; case EVMC_SET_OPTION_INVALID_VALUE: ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE, - "%s (%s): unsupported value '%s' for option '%s'", instance->name, path, + "%s (%s): unsupported value '%s' for option '%s'", vm->name, path, option, name); goto exit; + + default: + ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE, + "%s (%s): unknown error when setting value '%s' for option '%s'", + vm->name, path, option, name); + goto exit; } } @@ -315,9 +311,9 @@ exit: *error_code = ec; if (ec == EVMC_LOADER_SUCCESS) - return instance; + return vm; - if (instance) - evmc_destroy(instance); + if (vm) + evmc_destroy(vm); return NULL; } diff --git a/test/evmc/loader.h b/test/evmc/loader.h index 0c50a81f4..523139e31 100644 --- a/test/evmc/loader.h +++ b/test/evmc/loader.h @@ -19,7 +19,7 @@ extern "C" { #endif /** The function pointer type for EVMC create functions. */ -typedef struct evmc_instance* (*evmc_create_fn)(void); +typedef struct evmc_vm* (*evmc_create_fn)(void); /** Error codes for the EVMC loader. */ enum evmc_loader_error_code @@ -37,7 +37,7 @@ enum evmc_loader_error_code EVMC_LOADER_INVALID_ARGUMENT = 3, /** The creation of a VM instance has failed. */ - EVMC_LOADER_INSTANCE_CREATION_FAILURE = 4, + EVMC_LOADER_VM_CREATION_FAILURE = 4, /** The ABI version of the VM instance has mismatched. */ EVMC_LOADER_ABI_VERSION_MISMATCH = 5, @@ -61,21 +61,16 @@ enum evmc_loader_error_code * After the DLL is successfully loaded the function tries to find the EVM create function in the * library. The `filename` is used to guess the EVM name and the name of the create function. * The create function name is constructed by the following rules. Consider example path: - * "/ethereum/libexample-interpreter.so". + * "/ethereum/libexample-interpreter.so.1.0". * - the filename is taken from the path: - * "libexample-interpreter.so", - * - the "lib" prefix and file extension are stripped from the name: + * "libexample-interpreter.so.1.0", + * - the "lib" prefix and all file extensions are stripped from the name: * "example-interpreter" * - all "-" are replaced with "_" to construct _base name_: * "example_interpreter", * - the function name "evmc_create_" + _base name_ is searched in the library: * "evmc_create_example_interpreter", - * - if function not found, the _base name_ is shorten by skipping the first word separated by "_": - * "interpreter", - * - then, the function of the shorter name "evmc_create_" + _base name_ is searched in the library: - * "evmc_create_interpreter", - * - the name shortening continues until a function is found or the name cannot be shorten more, - * - lastly, when no function found, the function name "evmc_create" is searched in the library. + * - if the function is not found, the function name "evmc_create" is searched in the library. * * If the create function is found in the library, the pointer to the function is returned. * Otherwise, the ::EVMC_LOADER_SYMBOL_NOT_FOUND error code is signaled and NULL is returned. @@ -98,7 +93,7 @@ evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* erro * * This is a macro for creating the VM instance with the function returned from evmc_load(). * The function signals the same errors as evmc_load() and additionally: - * - ::EVMC_LOADER_INSTANCE_CREATION_FAILURE when the create function returns NULL, + * - ::EVMC_LOADER_VM_CREATION_FAILURE when the create function returns NULL, * - ::EVMC_LOADER_ABI_VERSION_MISMATCH when the created VM instance has ABI version different * from the ABI version of this library (::EVMC_ABI_VERSION). * @@ -114,8 +109,7 @@ evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* erro * ::EVMC_LOADER_SUCCESS on success or any other error code as described above. * @return The pointer to the created VM or NULL in case of error. */ -struct evmc_instance* evmc_load_and_create(const char* filename, - enum evmc_loader_error_code* error_code); +struct evmc_vm* evmc_load_and_create(const char* filename, enum evmc_loader_error_code* error_code); /** * Dynamically loads the EVMC module, then creates and configures the VM instance. @@ -151,8 +145,8 @@ struct evmc_instance* evmc_load_and_create(const char* filename, * ::EVMC_LOADER_SUCCESS on success or any other error code as described above. * @return The pointer to the created VM or NULL in case of error. */ -struct evmc_instance* evmc_load_and_configure(const char* config, - enum evmc_loader_error_code* error_code); +struct evmc_vm* evmc_load_and_configure(const char* config, + enum evmc_loader_error_code* error_code); /** * Returns the human-readable message describing the most recent error diff --git a/test/evmc/mocked_host.hpp b/test/evmc/mocked_host.hpp new file mode 100644 index 000000000..2ff1701a4 --- /dev/null +++ b/test/evmc/mocked_host.hpp @@ -0,0 +1,315 @@ +// EVMC: Ethereum Client-VM Connector API. +// Copyright 2019 The EVMC Authors. +// Licensed under the Apache License, Version 2.0. +#pragma once + +#include +#include +#include +#include +#include + +namespace evmc +{ +/// The string of bytes. +using bytes = std::basic_string; + +/// Extended value (by dirty flag) for account storage. +struct storage_value +{ + /// The storage value. + bytes32 value; + + /// True means this value has been modified already by the current transaction. + bool dirty{false}; + + /// Default constructor. + storage_value() noexcept = default; + + /// Constructor. + storage_value(const bytes32& _value, bool _dirty = false) noexcept // NOLINT + : value{_value}, dirty{_dirty} + {} +}; + +/// Mocked account. +struct MockedAccount +{ + /// The account nonce. + int nonce = 0; + + /// The account code. + bytes code; + + /// The code hash. Can be a value not related to the actual code. + bytes32 codehash; + + /// The account balance. + uint256be balance; + + /// The account storage map. + std::unordered_map storage; + + /// Helper method for setting balance by numeric type. + void set_balance(uint64_t x) noexcept + { + balance = uint256be{}; + for (std::size_t i = 0; i < sizeof(x); ++i) + balance.bytes[sizeof(balance) - 1 - i] = static_cast(x >> (8 * i)); + } +}; + +/// Mocked EVMC Host implementation. +class MockedHost : public Host +{ +public: + /// LOG record. + struct log_record + { + /// The address of the account which created the log. + address creator; + + /// The data attached to the log. + bytes data; + + /// The log topics. + std::vector topics; + + /// Equal operator. + bool operator==(const log_record& other) const noexcept + { + return creator == other.creator && data == other.data && topics == other.topics; + } + }; + + /// SELFDESTRUCT record. + struct selfdestuct_record + { + /// The address of the account which has self-destructed. + address selfdestructed; + + /// The address of the beneficiary account. + address beneficiary; + + /// Equal operator. + bool operator==(const selfdestuct_record& other) const noexcept + { + return selfdestructed == other.selfdestructed && beneficiary == other.beneficiary; + } + }; + + /// The set of all accounts in the Host, organized by their addresses. + std::unordered_map accounts; + + /// The EVMC transaction context to be returned by get_tx_context(). + evmc_tx_context tx_context = {}; + + /// The block header hash value to be returned by get_block_hash(). + bytes32 block_hash = {}; + + /// The call result to be returned by the call() method. + evmc_result call_result = {}; + + /// The record of all block numbers for which get_block_hash() was called. + mutable std::vector recorded_blockhashes; + + /// The record of all account accesses. + mutable std::vector
recorded_account_accesses; + + /// The maximum number of entries in recorded_account_accesses record. + /// This is arbitrary value useful in fuzzing when we don't want the record to explode. + static constexpr auto max_recorded_account_accesses = 200; + + /// The record of all call messages requested in the call() method. + std::vector recorded_calls; + + /// The maximum number of entries in recorded_calls record. + /// This is arbitrary value useful in fuzzing when we don't want the record to explode. + static constexpr auto max_recorded_calls = 100; + + /// The record of all LOGs passed to the emit_log() method. + std::vector recorded_logs; + + /// The record of all SELFDESTRUCTs from the selfdestruct() method. + std::vector recorded_selfdestructs; + +protected: + /// The copy of call inputs for the recorded_calls record. + std::vector m_recorded_calls_inputs; + + /// Record an account access. + /// @param addr The address of the accessed account. + void record_account_access(const address& addr) const + { + if (recorded_account_accesses.empty()) + recorded_account_accesses.reserve(max_recorded_account_accesses); + + if (recorded_account_accesses.size() < max_recorded_account_accesses) + recorded_account_accesses.emplace_back(addr); + } + + /// Returns true if an account exists (EVMC Host method). + bool account_exists(const address& addr) const noexcept override + { + record_account_access(addr); + return accounts.count(addr) != 0; + } + + /// Get the account's storage value at the given key (EVMC Host method). + bytes32 get_storage(const address& addr, const bytes32& key) const noexcept override + { + record_account_access(addr); + + const auto account_iter = accounts.find(addr); + if (account_iter == accounts.end()) + return {}; + + const auto storage_iter = account_iter->second.storage.find(key); + if (storage_iter != account_iter->second.storage.end()) + return storage_iter->second.value; + return {}; + } + + /// Set the account's storage value (EVMC Host method). + evmc_storage_status set_storage(const address& addr, + const bytes32& key, + const bytes32& value) noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return EVMC_STORAGE_UNCHANGED; + + auto& old = it->second.storage[key]; + + // Follow https://eips.ethereum.org/EIPS/eip-1283 specification. + // WARNING! This is not complete implementation as refund is not handled here. + + if (old.value == value) + return EVMC_STORAGE_UNCHANGED; + + evmc_storage_status status{}; + if (!old.dirty) + { + old.dirty = true; + if (!old.value) + status = EVMC_STORAGE_ADDED; + else if (value) + status = EVMC_STORAGE_MODIFIED; + else + status = EVMC_STORAGE_DELETED; + } + else + status = EVMC_STORAGE_MODIFIED_AGAIN; + + old.value = value; + return status; + } + + /// Get the account's balance (EVMC Host method). + uint256be get_balance(const address& addr) const noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return {}; + + return it->second.balance; + } + + /// Get the account's code size (EVMC host method). + size_t get_code_size(const address& addr) const noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return 0; + return it->second.code.size(); + } + + /// Get the account's code hash (EVMC host method). + bytes32 get_code_hash(const address& addr) const noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return {}; + return it->second.codehash; + } + + /// Copy the account's code to the given buffer (EVMC host method). + size_t copy_code(const address& addr, + size_t code_offset, + uint8_t* buffer_data, + size_t buffer_size) const noexcept override + { + record_account_access(addr); + const auto it = accounts.find(addr); + if (it == accounts.end()) + return 0; + + const auto& code = it->second.code; + + if (code_offset >= code.size()) + return 0; + + const auto n = std::min(buffer_size, code.size() - code_offset); + + if (n > 0) + std::copy_n(&code[code_offset], n, buffer_data); + return n; + } + + /// Selfdestruct the account (EVMC host method). + void selfdestruct(const address& addr, const address& beneficiary) noexcept override + { + record_account_access(addr); + recorded_selfdestructs.push_back({addr, beneficiary}); + } + + /// Call/create other contract (EVMC host method). + result call(const evmc_message& msg) noexcept override + { + record_account_access(msg.destination); + + if (recorded_calls.empty()) + { + recorded_calls.reserve(max_recorded_calls); + m_recorded_calls_inputs.reserve(max_recorded_calls); // Iterators will not invalidate. + } + + if (recorded_calls.size() < max_recorded_calls) + { + recorded_calls.emplace_back(msg); + auto& call_msg = recorded_calls.back(); + if (call_msg.input_size > 0) + { + m_recorded_calls_inputs.emplace_back(call_msg.input_data, call_msg.input_size); + const auto& input_copy = m_recorded_calls_inputs.back(); + call_msg.input_data = input_copy.data(); + } + } + return result{call_result}; + } + + /// Get transaction context (EVMC host method). + evmc_tx_context get_tx_context() const noexcept override { return tx_context; } + + /// Get the block header hash (EVMC host method). + bytes32 get_block_hash(int64_t block_number) const noexcept override + { + recorded_blockhashes.emplace_back(block_number); + return block_hash; + } + + /// Emit LOG (EVMC host method). + void emit_log(const address& addr, + const uint8_t* data, + size_t data_size, + const bytes32 topics[], + size_t topics_count) noexcept override + { + recorded_logs.push_back({addr, {data, data_size}, {topics, topics + topics_count}}); + } +}; +} // namespace evmc diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh index c018789ac..dd9aa77aa 100644 --- a/test/externalTests/common.sh +++ b/test/externalTests/common.sh @@ -207,7 +207,7 @@ function run_test for optimize in "${optimizer_settings[@]}" do clean - force_solc_settings "$CONFIG" "$optimize" "petersburg" + force_solc_settings "$CONFIG" "$optimize" "istanbul" # Force ABIEncoderV2 in the last step. Has to be the last because code is modified. if [ "$FORCE_ABIv2" = true ]; then [[ "$optimize" =~ yul ]] && force_abi_v2 @@ -216,8 +216,13 @@ function run_test printLog "Running compile function..." $compile_fn verify_compiler_version "$SOLCVERSION" - printLog "Running test function..." - $test_fn + + if [[ "$COMPILE_ONLY" == 1 ]]; then + printLog "Skipping test function..." + else + printLog "Running test function..." + $test_fn + fi done } diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh index b24617f37..aa784c6ad 100755 --- a/test/externalTests/zeppelin.sh +++ b/test/externalTests/zeppelin.sh @@ -35,8 +35,6 @@ function zeppelin_test run_install install_fn CONFIG="truffle-config.js" - replace_libsolc_call - run_test compile_fn test_fn } diff --git a/test/libdevcore/CommonData.cpp b/test/libdevcore/CommonData.cpp index 8da937de8..18f7be6f8 100644 --- a/test/libdevcore/CommonData.cpp +++ b/test/libdevcore/CommonData.cpp @@ -18,7 +18,7 @@ * Unit tests for the StringUtils routines. */ - +#include #include #include #include // for IntegerType @@ -28,6 +28,9 @@ using namespace std; using namespace dev::solidity; +// TODO: Fix Boost... +BOOST_TEST_DONT_PRINT_LOG_VALUE(dev::bytes); + namespace dev { namespace test @@ -35,9 +38,62 @@ namespace test BOOST_AUTO_TEST_SUITE(CommonData) -BOOST_AUTO_TEST_CASE(test_to_hex) +BOOST_AUTO_TEST_CASE(fromhex_char) { - BOOST_CHECK_EQUAL(toHex(fromHex("FF"), HexPrefix::DontAdd, HexCase::Lower), "ff"); + BOOST_CHECK_EQUAL(fromHex('0', WhenError::DontThrow), 0x0); + BOOST_CHECK_EQUAL(fromHex('a', WhenError::DontThrow), 0xa); + BOOST_CHECK_EQUAL(fromHex('x', WhenError::DontThrow), -1); + BOOST_CHECK_EQUAL(fromHex('x', static_cast(42)), -1); + + BOOST_CHECK_EQUAL(fromHex('0', WhenError::Throw), 0x0); + BOOST_CHECK_EQUAL(fromHex('a', WhenError::Throw), 0xa); + BOOST_CHECK_THROW(fromHex('x', WhenError::Throw), BadHexCharacter); +} + +BOOST_AUTO_TEST_CASE(fromhex_string) +{ + bytes expectation_even = {{0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}}; + bytes expectation_odd = {{0x00, 0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9a, 0xab, 0xbc, 0xcd, 0xde, 0xef, 0xf0}}; + + // Defaults to WhenError::DontThrow + BOOST_CHECK_EQUAL(fromHex(""), bytes()); + BOOST_CHECK_EQUAL(fromHex("00112233445566778899aabbccddeeff"), expectation_even); + BOOST_CHECK_EQUAL(fromHex("0x00112233445566778899aabbccddeeff"), expectation_even); + BOOST_CHECK_EQUAL(fromHex("0x00112233445566778899aabbccddeeff0"), expectation_odd); + BOOST_CHECK_EQUAL(fromHex("gg"), bytes()); + BOOST_CHECK_EQUAL(fromHex("0xgg"), bytes()); + + BOOST_CHECK_EQUAL(fromHex("", WhenError::Throw), bytes()); + BOOST_CHECK_EQUAL(fromHex("00112233445566778899aabbccddeeff", WhenError::Throw), expectation_even); + BOOST_CHECK_EQUAL(fromHex("0x00112233445566778899aabbccddeeff", WhenError::Throw), expectation_even); + BOOST_CHECK_EQUAL(fromHex("0x00112233445566778899aabbccddeeff0", WhenError::Throw), expectation_odd); + BOOST_CHECK_THROW(fromHex("gg", WhenError::Throw), BadHexCharacter); + BOOST_CHECK_THROW(fromHex("0xgg", WhenError::Throw), BadHexCharacter); +} + +BOOST_AUTO_TEST_CASE(tohex_uint8) +{ + BOOST_CHECK_EQUAL(toHex(0xaa), "aa"); + BOOST_CHECK_EQUAL(toHex(0xaa, HexCase::Lower), "aa"); + BOOST_CHECK_EQUAL(toHex(0xaa, HexCase::Upper), "AA"); + BOOST_CHECK_THROW(toHex(0xaa, HexCase::Mixed), BadHexCase); + // Defaults to lower case on invalid setting. + BOOST_CHECK_EQUAL(toHex(0xaa, static_cast(42)), "aa"); +} + +BOOST_AUTO_TEST_CASE(tohex_bytes) +{ + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::DontAdd, HexCase::Lower), "00112233445566778899aabbccddeeff"); + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::DontAdd, HexCase::Upper), "00112233445566778899AABBCCDDEEFF"); + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::DontAdd, HexCase::Mixed), "00112233445566778899aabbCCDDeeff"); + // Defaults to lower case on invalid setting. + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::DontAdd, static_cast(42)), "00112233445566778899aabbccddeeff"); + + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::Add, HexCase::Lower), "0x00112233445566778899aabbccddeeff"); + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::Add, HexCase::Upper), "0x00112233445566778899AABBCCDDEEFF"); + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899AaBbCcDdEeFf"), HexPrefix::Add, HexCase::Mixed), "0x00112233445566778899aabbCCDDeeff"); + // Defaults to lower case on invalid setting. + BOOST_CHECK_EQUAL(toHex(fromHex("00112233445566778899aAbBcCdDeEfF"), HexPrefix::Add, static_cast(42)), "0x00112233445566778899aabbccddeeff"); } BOOST_AUTO_TEST_CASE(test_format_number) diff --git a/test/libdevcore/JSON.cpp b/test/libdevcore/JSON.cpp index a3e081be6..684fd92b2 100644 --- a/test/libdevcore/JSON.cpp +++ b/test/libdevcore/JSON.cpp @@ -69,47 +69,6 @@ BOOST_AUTO_TEST_CASE(json_compact_print) BOOST_CHECK("{\"1\":1,\"2\":\"2\",\"3\":{\"3.1\":\"3.1\",\"3.2\":2}}" == jsonCompactPrint(json)); } -BOOST_AUTO_TEST_CASE(parse_json_not_strict) -{ - Json::Value json; - std::string errors; - - // just parse a valid json input - BOOST_CHECK(jsonParse("{\"1\":1,\"2\":\"2\",\"3\":{\"3.1\":\"3.1\",\"3.2\":2}}", json, &errors)); - BOOST_CHECK(json["1"] == 1); - BOOST_CHECK(json["2"] == "2"); - BOOST_CHECK(json["3"]["3.1"] == "3.1"); - BOOST_CHECK(json["3"]["3.2"] == 2); - - // trailing garbage is allowed here - BOOST_CHECK(jsonParse("{\"1\":2,\"2\":\"2\",\"3\":{\"3.1\":\"3.1\",\"3.2\":3}}}}}}}}}}", json, &errors)); - BOOST_CHECK(json["1"] == 2); - BOOST_CHECK(json["2"] == "2"); - BOOST_CHECK(json["3"]["3.1"] == "3.1"); - BOOST_CHECK(json["3"]["3.2"] == 3); - - // comments are allowed - BOOST_CHECK(jsonParse( - "{\"1\":3, // awesome comment\n\"2\":\"2\",\"3\":{\"3.1\":\"3.1\",\"3.2\":4}}", json, &errors - )); - BOOST_CHECK(json["1"] == 3); - BOOST_CHECK(json["2"] == "2"); - BOOST_CHECK(json["3"]["3.1"] == "3.1"); - BOOST_CHECK(json["3"]["3.2"] == 4); - - // root element other than object or array is allowed - BOOST_CHECK(jsonParse("[]", json, &errors)); - BOOST_CHECK(jsonParse("{}", json, &errors)); - BOOST_CHECK(jsonParse("1", json, &errors)); - BOOST_CHECK(json == 1); - BOOST_CHECK(jsonParse("\"hello\"", json, &errors)); - BOOST_CHECK(json == "hello"); - - // non-UTF-8 escapes allowed - BOOST_CHECK(jsonParse("[ \"\x80\xec\x80\" ]", json, &errors)); - BOOST_CHECK(json[0] == "\x80\xec\x80"); -} - BOOST_AUTO_TEST_CASE(parse_json_strict) { Json::Value json; diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp index 3e2b38889..d44ccb49f 100644 --- a/test/liblll/Compiler.cpp +++ b/test/liblll/Compiler.cpp @@ -193,6 +193,8 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_functional) "4300", "4400", "4500", + "4600", + "4700", "60005000", "60005100", "600060005200", @@ -420,6 +422,8 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_asm) "4300", "4400", "4500", + "4600", + "4700", "60005000", "60005100", "600060005200", @@ -559,6 +563,8 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_asm) "(asm NUMBER)", "(asm DIFFICULTY)", "(asm GASLIMIT)", + "(asm CHAINID)", + "(asm SELFBALANCE)", "(asm 0 POP)", "(asm 0 MLOAD)", "(asm 0 0 MSTORE)", diff --git a/test/libsolidity/ABIJsonTest.h b/test/libsolidity/ABIJsonTest.h index e7be7947f..82a19da3f 100644 --- a/test/libsolidity/ABIJsonTest.h +++ b/test/libsolidity/ABIJsonTest.h @@ -36,7 +36,7 @@ class ABIJsonTest: public TestCase { public: static std::unique_ptr create(Config const& _config) - { return std::unique_ptr(new ABIJsonTest(_config.filename)); } + { return std::make_unique(_config.filename); } ABIJsonTest(std::string const& _filename); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; diff --git a/test/libsolidity/ASTJSONTest.h b/test/libsolidity/ASTJSONTest.h index dcd385623..cf508e87b 100644 --- a/test/libsolidity/ASTJSONTest.h +++ b/test/libsolidity/ASTJSONTest.h @@ -36,7 +36,7 @@ class ASTJSONTest: public TestCase { public: static std::unique_ptr create(Config const& _config) - { return std::unique_ptr(new ASTJSONTest(_config.filename)); } + { return std::make_unique(_config.filename); } ASTJSONTest(std::string const& _filename); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; diff --git a/test/libsolidity/GasCosts.cpp b/test/libsolidity/GasCosts.cpp index 8aad4fab7..c56187148 100644 --- a/test/libsolidity/GasCosts.cpp +++ b/test/libsolidity/GasCosts.cpp @@ -19,6 +19,7 @@ */ #include +#include #include #include @@ -37,10 +38,10 @@ namespace solidity namespace test { -#define CHECK_DEPLOY_GAS(_gasNoOpt, _gasOpt) \ +#define CHECK_DEPLOY_GAS(_gasNoOpt, _gasOpt, _evmVersion) \ do \ { \ - u256 bzzr1Cost = GasMeter::dataGas(dev::bzzr1Hash(m_compiler.metadata(m_compiler.lastContractName())).asBytes(), true); \ + u256 bzzr1Cost = GasMeter::dataGas(dev::bzzr1Hash(m_compiler.metadata(m_compiler.lastContractName())).asBytes(), true, _evmVersion); \ u256 gasOpt{_gasOpt}; \ u256 gasNoOpt{_gasNoOpt}; \ u256 gas = m_optimiserSettings == OptimiserSettings::minimal() ? gasNoOpt : gasOpt; \ @@ -95,33 +96,61 @@ BOOST_AUTO_TEST_CASE(string_storage) m_compiler.overwriteReleaseFlag(true); compileAndRun(sourceCode); - if (Options::get().evmVersion() <= EVMVersion::byzantium()) - CHECK_DEPLOY_GAS(134071, 130763); + auto evmVersion = dev::test::Options::get().evmVersion(); + + if (evmVersion <= EVMVersion::byzantium()) + CHECK_DEPLOY_GAS(134071, 130763, evmVersion); // This is only correct on >=Constantinople. else if (Options::get().useABIEncoderV2) { if (Options::get().optimizeYul) - CHECK_DEPLOY_GAS(151455, 127653); + { + // Costs with 0 are cases which cannot be triggered in tests. + if (evmVersion < EVMVersion::istanbul()) + CHECK_DEPLOY_GAS(0, 127653, evmVersion); + else + CHECK_DEPLOY_GAS(0, 113821, evmVersion); + } else - CHECK_DEPLOY_GAS(151455, 135371); + { + if (evmVersion < EVMVersion::istanbul()) + CHECK_DEPLOY_GAS(0, 135371, evmVersion); + else + CHECK_DEPLOY_GAS(0, 120083, evmVersion); + } } + else if (evmVersion < EVMVersion::istanbul()) + CHECK_DEPLOY_GAS(126861, 119591, evmVersion); else - CHECK_DEPLOY_GAS(126861, 119591); - if (Options::get().evmVersion() >= EVMVersion::byzantium()) + CHECK_DEPLOY_GAS(114173, 107163, evmVersion); + + if (evmVersion >= EVMVersion::byzantium()) { callContractFunction("f()"); - if (Options::get().evmVersion() == EVMVersion::byzantium()) + if (evmVersion == EVMVersion::byzantium()) CHECK_GAS(21551, 21526, 20); // This is only correct on >=Constantinople. else if (Options::get().useABIEncoderV2) { if (Options::get().optimizeYul) - CHECK_GAS(21713, 21567, 20); + { + if (evmVersion < EVMVersion::istanbul()) + CHECK_GAS(0, 21567, 20); + else + CHECK_GAS(0, 21351, 20); + } else - CHECK_GAS(21713, 21635, 20); + { + if (evmVersion < EVMVersion::istanbul()) + CHECK_GAS(0, 21635, 20); + else + CHECK_GAS(0, 21431, 20); + } } - else + else if (evmVersion < EVMVersion::istanbul()) CHECK_GAS(21546, 21526, 20); + else + CHECK_GAS(21332, 21322, 20); } } diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index 4b13172b1..bccf27458 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -114,9 +114,10 @@ public: static GasMeter::GasConsumption gasForTransaction(bytes const& _data, bool _isCreation) { + auto evmVersion = dev::test::Options::get().evmVersion(); GasMeter::GasConsumption gas = _isCreation ? GasCosts::txCreateGas : GasCosts::txGas; for (auto i: _data) - gas += i != 0 ? GasCosts::txDataNonZeroGas : GasCosts::txDataZeroGas; + gas += i != 0 ? GasCosts::txDataNonZeroGas(evmVersion) : GasCosts::txDataZeroGas; return gas; } diff --git a/test/libsolidity/Metadata.cpp b/test/libsolidity/Metadata.cpp index 2526dd8b6..f0af8b544 100644 --- a/test/libsolidity/Metadata.cpp +++ b/test/libsolidity/Metadata.cpp @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(metadata_useLiteralContent) BOOST_REQUIRE_MESSAGE(compilerStack.compile(), "Compiling contract failed"); string metadata_str = compilerStack.metadata("test"); Json::Value metadata; - jsonParse(metadata_str, metadata); + jsonParseStrict(metadata_str, metadata); BOOST_CHECK(dev::test::isValidMetadata(metadata_str)); BOOST_CHECK(metadata.isMember("settings")); if (_literal) diff --git a/test/libsolidity/SMTCheckerJSONTest.cpp b/test/libsolidity/SMTCheckerJSONTest.cpp index a2ce4e26b..19bdcaacc 100644 --- a/test/libsolidity/SMTCheckerJSONTest.cpp +++ b/test/libsolidity/SMTCheckerJSONTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename, langutil::EVMVersion _ev string jsonFilename = _filename.substr(0, _filename.size() - 4) + ".json"; if ( - !jsonParseFile(jsonFilename, m_smtResponses) || + !jsonParseStrict(readFileAsString(jsonFilename), m_smtResponses) || !m_smtResponses.isObject() ) BOOST_THROW_EXCEPTION(runtime_error("Invalid JSON file.")); @@ -158,7 +159,7 @@ Json::Value SMTCheckerTest::buildJson(string const& _extra) sources += "}"; string input = "{" + language + ", " + sources + "}"; Json::Value source; - if (!jsonParse(input, source)) + if (!jsonParseStrict(input, source)) BOOST_THROW_EXCEPTION(runtime_error("Could not build JSON from string: " + input)); return source; } diff --git a/test/libsolidity/SemVerMatcher.cpp b/test/libsolidity/SemVerMatcher.cpp index 4a6c123ef..e0e643968 100644 --- a/test/libsolidity/SemVerMatcher.cpp +++ b/test/libsolidity/SemVerMatcher.cpp @@ -70,6 +70,8 @@ BOOST_AUTO_TEST_CASE(positive_range) {"*", "1.2.3-foo"}, {"1.0.0 - 2.0.0", "1.2.3"}, {"1.0.0", "1.0.0"}, + {"1.0", "1.0.0"}, + {"1", "1.0.0"}, {">=*", "0.2.4"}, {"*", "1.2.3"}, {">=1.0.0", "1.0.0"}, @@ -82,6 +84,8 @@ BOOST_AUTO_TEST_CASE(positive_range) {"<=2.0.0", "0.2.9"}, {"<2.0.0", "1.9999.9999"}, {"<2.0.0", "0.2.9"}, + {"<1.0", "1.0.0-pre"}, + {"<1", "1.0.0-pre"}, {">= 1.0.0", "1.0.0"}, {">= 1.0.0", "1.0.1"}, {">= 1.0.0", "1.1.0"}, @@ -137,10 +141,14 @@ BOOST_AUTO_TEST_CASE(positive_range) {"^0.1.2", "0.1.2"}, {"^0.1", "0.1.2"}, {"^1.2", "1.4.2"}, + {"^1.2", "1.2.0"}, + {"^1", "1.2.0"}, {"<=1.2.3", "1.2.3-beta"}, {">1.2", "1.3.0-beta"}, {"<1.2.3", "1.2.3-beta"}, - {"^1.2 ^1", "1.4.2"} + {"^1.2 ^1", "1.4.2"}, + {"^0", "0.5.1"}, + {"^0", "0.1.1"}, }; for (auto const& t: tests) { @@ -155,11 +163,14 @@ BOOST_AUTO_TEST_CASE(positive_range) BOOST_AUTO_TEST_CASE(negative_range) { - // Positive range tests + // Negative range tests vector> tests = { {"1.0.0 - 2.0.0", "2.2.3"}, + {"1.0", "1.0.0-pre"}, + {"1", "1.0.0-pre"}, {"^1.2.3", "1.2.3-pre"}, {"^1.2", "1.2.0-pre"}, + {"^1.2", "1.2.1-pre"}, {"^1.2.3", "1.2.3-beta"}, {"=0.7.x", "0.7.0-asdf"}, {">=0.7.x", "0.7.0-asdf"}, @@ -202,8 +213,16 @@ BOOST_AUTO_TEST_CASE(negative_range) {"=1.2.3", "1.2.3-beta"}, {">1.2", "1.2.8"}, {"^1.2.3", "2.0.0-alpha"}, + {"^0.6", "0.6.2-alpha"}, + {"^0.6", "0.6.0-alpha"}, + {"^1.2", "1.2.1-pre"}, {"^1.2.3", "1.2.2"}, - {"^1.2", "1.1.9"} + {"^1", "1.2.0-pre"}, + {"^1", "1.2.0-pre"}, + {"^1.2", "1.1.9"}, + {"^0", "0.5.1-pre"}, + {"^0", "0.0.0-pre"}, + {"^0", "1.0.0"}, }; for (auto const& t: tests) { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index af940e0ef..bc03c2ef8 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -43,6 +43,7 @@ using namespace std; using namespace std::placeholders; using namespace dev::test; +using namespace langutil; #define ALSO_VIA_YUL(CODE) \ { \ @@ -1134,7 +1135,7 @@ BOOST_AUTO_TEST_CASE(blockchain) } } )"; - m_evmHost->m_coinbase = EVMHost::convertToEVMC(Address("0x1212121212121212121212121212121212121212")); + m_evmHost->tx_context.block_coinbase = EVMHost::convertToEVMC(Address("0x1212121212121212121212121212121212121212")); m_evmHost->newBlock(); m_evmHost->newBlock(); m_evmHost->newBlock(); @@ -12888,6 +12889,9 @@ BOOST_AUTO_TEST_CASE(address_overload_resolution) BOOST_AUTO_TEST_CASE(snark) { + if (dev::test::Options::get().evmVersion() <= EVMVersion::byzantium()) + return; + char const* sourceCode = R"( library Pairing { struct G1Point { diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index 7e7b58469..5d97ee988 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -505,7 +505,7 @@ BOOST_AUTO_TEST_CASE(valid_hex_literal) { Scanner scanner(CharStream("{ hex\"00112233FF\"", "")); BOOST_CHECK_EQUAL(scanner.currentToken(), Token::LBrace); - BOOST_CHECK_EQUAL(scanner.next(), Token::StringLiteral); + BOOST_CHECK_EQUAL(scanner.next(), Token::HexStringLiteral); BOOST_CHECK_EQUAL(scanner.currentLiteral(), std::string("\x00\x11\x22\x33\xFF", 5)); } diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index e9d8613a4..17b39b2e6 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -900,9 +900,11 @@ BOOST_AUTO_TEST_CASE(evm_version) BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"constantinople\"") != string::npos); result = compile(inputForVersion("\"evmVersion\": \"petersburg\",")); BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"petersburg\"") != string::npos); + result = compile(inputForVersion("\"evmVersion\": \"istanbul\",")); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"istanbul\"") != string::npos); // test default result = compile(inputForVersion("")); - BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"petersburg\"") != string::npos); + BOOST_CHECK(result["contracts"]["fileA"]["A"]["metadata"].asString().find("\"evmVersion\":\"istanbul\"") != string::npos); // test invalid result = compile(inputForVersion("\"evmVersion\": \"invalid\",")); BOOST_CHECK(result["errors"][0]["message"].asString() == "Invalid EVM version requested."); @@ -1199,7 +1201,9 @@ BOOST_AUTO_TEST_CASE(use_stack_optimization) result = compiler.compile(parsedInput); BOOST_REQUIRE(result["errors"].isArray()); BOOST_CHECK(result["errors"][0]["severity"] == "error"); - BOOST_CHECK(result["errors"][0]["type"] == "InternalCompilerError"); + BOOST_REQUIRE(result["errors"][0]["message"].isString()); + BOOST_CHECK(result["errors"][0]["message"].asString().find("Stack too deep when compiling inline assembly") != std::string::npos); + BOOST_CHECK(result["errors"][0]["type"] == "YulException"); } BOOST_AUTO_TEST_CASE(standard_output_selection_wildcard) diff --git a/test/libsolidity/gasTests/abiv2.sol b/test/libsolidity/gasTests/abiv2.sol index 2ee698008..390811a0b 100644 --- a/test/libsolidity/gasTests/abiv2.sol +++ b/test/libsolidity/gasTests/abiv2.sol @@ -18,7 +18,7 @@ contract C { // executionCost: 1167 // totalCost: 1123767 // external: -// a(): 530 +// a(): 1130 // b(uint256): infinite // f1(uint256): infinite // f2(uint256[],string[],uint16,address): infinite diff --git a/test/libsolidity/gasTests/abiv2_optimised.sol b/test/libsolidity/gasTests/abiv2_optimised.sol index 83d07911f..35b9b8057 100644 --- a/test/libsolidity/gasTests/abiv2_optimised.sol +++ b/test/libsolidity/gasTests/abiv2_optimised.sol @@ -21,8 +21,8 @@ contract C { // executionCost: 645 // totalCost: 609045 // external: -// a(): 429 -// b(uint256): 884 +// a(): 1029 +// b(uint256): 2084 // f1(uint256): 351 // f2(uint256[],string[],uint16,address): infinite // f3(uint16[],string[],uint16,address): infinite diff --git a/test/libsolidity/gasTests/dispatch_large.sol b/test/libsolidity/gasTests/dispatch_large.sol index 7fc28339c..014eb1a9a 100644 --- a/test/libsolidity/gasTests/dispatch_large.sol +++ b/test/libsolidity/gasTests/dispatch_large.sol @@ -28,25 +28,25 @@ contract Large { // executionCost: 670 // totalCost: 637470 // external: -// a(): 451 -// b(uint256): 846 +// a(): 1051 +// b(uint256): 2046 // f0(uint256): 427 -// f1(uint256): 40752 -// f2(uint256): 20693 -// f3(uint256): 20781 -// f4(uint256): 20759 -// f5(uint256): 20737 -// f6(uint256): 20760 -// f7(uint256): 20672 -// f8(uint256): 20672 -// f9(uint256): 20694 +// f1(uint256): 41352 +// f2(uint256): 21293 +// f3(uint256): 21381 +// f4(uint256): 21359 +// f5(uint256): 21337 +// f6(uint256): 21360 +// f7(uint256): 21272 +// f8(uint256): 21272 +// f9(uint256): 21294 // g0(uint256): 313 -// g1(uint256): 40707 -// g2(uint256): 20670 -// g3(uint256): 20758 -// g4(uint256): 20736 -// g5(uint256): 20692 -// g6(uint256): 20715 -// g7(uint256): 20714 -// g8(uint256): 20692 -// g9(uint256): 20649 +// g1(uint256): 41307 +// g2(uint256): 21270 +// g3(uint256): 21358 +// g4(uint256): 21336 +// g5(uint256): 21292 +// g6(uint256): 21315 +// g7(uint256): 21314 +// g8(uint256): 21292 +// g9(uint256): 21249 diff --git a/test/libsolidity/gasTests/dispatch_large_optimised.sol b/test/libsolidity/gasTests/dispatch_large_optimised.sol index cee223b4c..bb80d1e8e 100644 --- a/test/libsolidity/gasTests/dispatch_large_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_large_optimised.sol @@ -31,25 +31,25 @@ contract Large { // executionCost: 300 // totalCost: 260700 // external: -// a(): 398 -// b(uint256): 1105 +// a(): 998 +// b(uint256): 2305 // f0(uint256): 334 -// f1(uint256): 40874 -// f2(uint256): 20940 -// f3(uint256): 21028 -// f4(uint256): 21006 -// f5(uint256): 20984 -// f6(uint256): 20896 -// f7(uint256): 20676 -// f8(uint256): 20808 -// f9(uint256): 20830 +// f1(uint256): 41474 +// f2(uint256): 21540 +// f3(uint256): 21628 +// f4(uint256): 21606 +// f5(uint256): 21584 +// f6(uint256): 21496 +// f7(uint256): 21276 +// f8(uint256): 21408 +// f9(uint256): 21430 // g0(uint256): 574 -// g1(uint256): 40586 -// g2(uint256): 20674 -// g3(uint256): 20762 -// g4(uint256): 20740 -// g5(uint256): 20828 -// g6(uint256): 20608 -// g7(uint256): 20718 -// g8(uint256): 20696 -// g9(uint256): 20542 +// g1(uint256): 41186 +// g2(uint256): 21274 +// g3(uint256): 21362 +// g4(uint256): 21340 +// g5(uint256): 21428 +// g6(uint256): 21208 +// g7(uint256): 21318 +// g8(uint256): 21296 +// g9(uint256): 21142 diff --git a/test/libsolidity/gasTests/dispatch_medium.sol b/test/libsolidity/gasTests/dispatch_medium.sol index 0c1d80126..c1bc1176e 100644 --- a/test/libsolidity/gasTests/dispatch_medium.sol +++ b/test/libsolidity/gasTests/dispatch_medium.sol @@ -15,12 +15,12 @@ contract Medium { // executionCost: 294 // totalCost: 253294 // external: -// a(): 428 -// b(uint256): 846 -// f1(uint256): 40663 -// f2(uint256): 20693 -// f3(uint256): 20737 +// a(): 1028 +// b(uint256): 2046 +// f1(uint256): 41263 +// f2(uint256): 21293 +// f3(uint256): 21337 // g0(uint256): 313 -// g7(uint256): 20692 -// g8(uint256): 20670 -// g9(uint256): 20626 +// g7(uint256): 21292 +// g8(uint256): 21270 +// g9(uint256): 21226 diff --git a/test/libsolidity/gasTests/dispatch_medium_optimised.sol b/test/libsolidity/gasTests/dispatch_medium_optimised.sol index b2c76140a..db4686a84 100644 --- a/test/libsolidity/gasTests/dispatch_medium_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_medium_optimised.sol @@ -18,12 +18,12 @@ contract Medium { // executionCost: 183 // totalCost: 140983 // external: -// a(): 398 -// b(uint256): 863 -// f1(uint256): 40654 -// f2(uint256): 20698 -// f3(uint256): 20742 +// a(): 998 +// b(uint256): 2063 +// f1(uint256): 41254 +// f2(uint256): 21298 +// f3(uint256): 21342 // g0(uint256): 332 -// g7(uint256): 20608 -// g8(uint256): 20586 -// g9(uint256): 20542 +// g7(uint256): 21208 +// g8(uint256): 21186 +// g9(uint256): 21142 diff --git a/test/libsolidity/gasTests/dispatch_small.sol b/test/libsolidity/gasTests/dispatch_small.sol index cb163d1ba..3d48a4543 100644 --- a/test/libsolidity/gasTests/dispatch_small.sol +++ b/test/libsolidity/gasTests/dispatch_small.sol @@ -11,6 +11,6 @@ contract Small { // totalCost: 83735 // external: // fallback: 118 -// a(): 383 -// b(uint256): 802 -// f1(uint256): 40663 +// a(): 983 +// b(uint256): 2002 +// f1(uint256): 41263 diff --git a/test/libsolidity/gasTests/dispatch_small_optimised.sol b/test/libsolidity/gasTests/dispatch_small_optimised.sol index 7153a784c..9012dbd43 100644 --- a/test/libsolidity/gasTests/dispatch_small_optimised.sol +++ b/test/libsolidity/gasTests/dispatch_small_optimised.sol @@ -14,6 +14,6 @@ contract Small { // totalCost: 60511 // external: // fallback: 118 -// a(): 376 -// b(uint256): 753 -// f1(uint256): 40588 +// a(): 976 +// b(uint256): 1953 +// f1(uint256): 41188 diff --git a/test/libsolidity/semanticTests/inlineAssembly/chainid.sol b/test/libsolidity/semanticTests/inlineAssembly/chainid.sol new file mode 100644 index 000000000..491e03a54 --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/chainid.sol @@ -0,0 +1,12 @@ +contract C { + function f() public returns (uint id) { + assembly { + id := chainid() + } + } +} +// ==== +// compileViaYul: also +// EVMVersion: >=istanbul +// ---- +// f() -> 1 diff --git a/test/libsolidity/semanticTests/inlineAssembly/constant_access_referencing.sol b/test/libsolidity/semanticTests/inlineAssembly/constant_access_referencing.sol new file mode 100644 index 000000000..d2e24a70e --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/constant_access_referencing.sol @@ -0,0 +1,26 @@ +contract C { + uint constant a = 2; + uint constant aa = a; + uint constant aaa = aa; + bytes2 constant b = 0xabcd; + bytes2 constant bb = b; + bytes3 constant c = "abc"; + bytes3 constant cc = c; + bytes3 constant ccc = cc; + bytes3 constant cccc = ccc; + bool constant d = true; + bool constant dd = d; + address payable constant e = 0x1212121212121212121212121212121212121212; + address payable constant ee = e; + function f() public pure returns (uint w, bytes2 x, bytes3 y, bool z, address t) { + assembly { + w := aaa + x := bb + y := cccc + z := dd + t := ee + } + } +} +// ---- +// f() -> 2, left(0xabcd), left(0x616263), true, 0x1212121212121212121212121212121212121212 diff --git a/test/libsolidity/semanticTests/inlineAssembly/selfbalance.sol b/test/libsolidity/semanticTests/inlineAssembly/selfbalance.sol new file mode 100644 index 000000000..8a0a5caae --- /dev/null +++ b/test/libsolidity/semanticTests/inlineAssembly/selfbalance.sol @@ -0,0 +1,12 @@ +contract C { + function f() public payable returns (uint ret) { + assembly { + ret := selfbalance() + } + } +} +// ==== +// EVMVersion: >=istanbul +// compileViaYul: also +// ---- +// f(), 254 ether -> 254 diff --git a/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_pure.sol b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_pure.sol new file mode 100644 index 000000000..902ca66c1 --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_pure.sol @@ -0,0 +1,32 @@ +library L { + function f(uint256[] storage x) public pure returns (uint256) { + return 23; + } +} +contract C { + uint256[] y; + string x; + constructor() public { y.push(42); } + function f() public view returns (uint256) { + return L.f(y); + } + function g() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } + function h() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> 23 +// g() -> true, 23 +// h() -> true, 23 diff --git a/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_needed.sol b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_needed.sol new file mode 100644 index 000000000..b9254c811 --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_needed.sol @@ -0,0 +1,32 @@ +library L { + function f(uint256[] storage x) public view returns (uint256) { + return x.length; + } +} +contract C { + uint256[] y; + string x; + constructor() public { y.push(42); } + function f() public view returns (uint256) { + return L.f(y); + } + function g() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } + function h() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> 1 +// g() -> true, 1 +// h() -> true, 0 # this is bad - this should fail! # diff --git a/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_not_needed.sol b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_not_needed.sol new file mode 100644 index 000000000..450f74f1d --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_not_needed.sol @@ -0,0 +1,31 @@ +library L { + function f(uint256[] storage x) public view returns (uint256) { + return 84; + } +} +contract C { + uint256[] y; + constructor() public { y.push(42); } + function f() public view returns (uint256) { + return L.f(y); + } + function g() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } + function h() public returns (bool, uint256) { + uint256 ys; + assembly { ys := y_slot } + (bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, ys)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> 84 +// g() -> true, 84 +// h() -> true, 84 diff --git a/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_staticcall.sol b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_staticcall.sol new file mode 100644 index 000000000..dc394f7fa --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_delegatecall_guard_view_staticcall.sol @@ -0,0 +1,31 @@ +contract D { + uint public x; + constructor() public { x = 42; } +} +library L { + function f(D d) public view returns (uint256) { + return d.x(); + } +} +contract C { + D d; + constructor() public { d = new D(); } + function f() public view returns (uint256) { + return L.f(d); + } + function g() public returns (bool, uint256) { + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, d)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } + function h() public returns (bool, uint256) { + (bool success, bytes memory data) = address(L).call(abi.encodeWithSelector(L.f.selector, d)); + return (success, success ? abi.decode(data,(uint256)) : 0); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> 42 +// g() -> true, 42 +// h() -> true, 42 diff --git a/test/libsolidity/semanticTests/libraries/library_function_selectors.sol b/test/libsolidity/semanticTests/libraries/library_function_selectors.sol new file mode 100644 index 000000000..305ccf39b --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_function_selectors.sol @@ -0,0 +1,30 @@ +library L { + function f(uint256 x) external returns (uint) { return x; } + function g(uint256[] storage s) external returns (uint) { return s.length; } + function h(uint256[] memory m) public returns (uint) { return m.length; } +} +contract C { + uint256[] s; + constructor() public { while (s.length < 42) s.push(0); } + function f() public returns (bool, bool, uint256) { + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, 7)); + return (L.f.selector == bytes4(keccak256("f(uint256)")), success, abi.decode(data, (uint256))); + } + function g() public returns (bool, bool, uint256) { + uint256 s_ptr; + assembly { s_ptr := s_slot } + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.g.selector, s_ptr)); + return (L.g.selector == bytes4(keccak256("g(uint256[] storage)")), success, abi.decode(data, (uint256))); + } + function h() public returns (bool, bool, uint256) { + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.h.selector, new uint256[](23))); + return (L.h.selector == bytes4(keccak256("h(uint256[])")), success, abi.decode(data, (uint256))); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> true, true, 7 +// g() -> true, true, 42 +// h() -> true, true, 23 diff --git a/test/libsolidity/semanticTests/libraries/library_function_selectors_struct.sol b/test/libsolidity/semanticTests/libraries/library_function_selectors_struct.sol new file mode 100644 index 000000000..f0c71dec7 --- /dev/null +++ b/test/libsolidity/semanticTests/libraries/library_function_selectors_struct.sol @@ -0,0 +1,27 @@ +pragma experimental ABIEncoderV2; +library L { + struct S { uint256 a; } + function f(S storage s) external returns (uint) { return s.a; } + function g(S memory m) public returns (uint) { return m.a; } +} +contract C { + L.S s; + constructor() public { s.a = 42; } + + function f() public returns (bool, bool, uint256) { + uint256 s_ptr; + assembly { s_ptr := s_slot } + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.f.selector, s_ptr)); + return (L.f.selector == bytes4(keccak256("f(L.S storage)")), success, abi.decode(data, (uint256))); + } + function g() public returns (bool, bool, uint256) { + (bool success, bytes memory data) = address(L).delegatecall(abi.encodeWithSelector(L.g.selector, L.S(23))); + return (L.g.selector == bytes4(keccak256("g(L.S)")), success, abi.decode(data, (uint256))); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// library: L +// f() -> true, true, 42 +// g() -> true, true, 23 diff --git a/test/libsolidity/semanticTests/viaYul/string_format.sol b/test/libsolidity/semanticTests/viaYul/string_format.sol new file mode 100644 index 000000000..2d9d71d7c --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/string_format.sol @@ -0,0 +1,13 @@ +contract C { + function f1() external pure returns (string memory) { return "abcabc"; } + function f2() external pure returns (string memory) { return "abcabc`~12345677890- _=+!@#$%^&*()[{]}|;:',<.>?"; } + function g() external pure returns (bytes32) { return "abcabc"; } + function h() external pure returns (bytes4) { return 0xcafecafe; } +} +// ==== +// compileViaYul: only +// ---- +// f1() -> 0x20, 6, left(0x616263616263) +// f2() -> 32, 47, 44048183223289766195424279195050628400112610419087780792899004030957505095210, 18165586057823232067963737336409268114628061002662705707816940456850361417728 +// g() -> left(0x616263616263) +// h() -> left(0xcafecafe) diff --git a/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol index e4d8f4cfe..955d5fd1e 100644 --- a/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol +++ b/test/libsolidity/smtCheckerTests/complex/MerkleProof.sol @@ -35,6 +35,8 @@ library MerkleProof { // ---- // Warning: (755-767): Assertion checker does not yet support this expression. +// Warning: (988-991): Assertion checker does not yet implement type abi // Warning: (988-1032): Assertion checker does not yet implement this type of function call. +// Warning: (1175-1178): Assertion checker does not yet implement type abi // Warning: (1175-1219): Assertion checker does not yet implement this type of function call. // Warning: (755-767): Assertion checker does not yet support this expression. diff --git a/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol b/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol index 8404c1a89..8610f1840 100644 --- a/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol +++ b/test/libsolidity/smtCheckerTests/complex/slither/const_state_variables.sol @@ -53,6 +53,5 @@ contract MyConc{ // ---- // Warning: (773-792): This declaration shadows an existing declaration. // Warning: (1009-1086): Function state mutability can be restricted to view -// Warning: (874-879): Underflow (resulting value less than 0) happens here. -// Warning: (874-879): Overflow (resulting value larger than 2**256 - 1) happens here. +// Warning: (985-1002): Underflow (resulting value less than 0) happens here. // Warning: (985-1002): Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/complex/slither/data_dependency.sol b/test/libsolidity/smtCheckerTests/complex/slither/data_dependency.sol index 2053fff48..29f15c689 100644 --- a/test/libsolidity/smtCheckerTests/complex/slither/data_dependency.sol +++ b/test/libsolidity/smtCheckerTests/complex/slither/data_dependency.sol @@ -137,4 +137,3 @@ contract PropagateThroughReturnValue { // Warning: (748-755): Assertion checker does not yet support this expression. // Warning: (748-751): Assertion checker does not yet implement type struct Reference.St storage pointer // Warning: (748-770): Assertion checker does not yet implement such assignments. -// Warning: (849-905): Assertion checker does not yet support constructors. diff --git a/test/libsolidity/smtCheckerTests/complex/slither/external_function.sol b/test/libsolidity/smtCheckerTests/complex/slither/external_function.sol index 6db44a5ef..43bd10cf7 100644 --- a/test/libsolidity/smtCheckerTests/complex/slither/external_function.sol +++ b/test/libsolidity/smtCheckerTests/complex/slither/external_function.sol @@ -83,7 +83,7 @@ contract InternalCall { // Warning: (1144-1206): Function state mutability can be restricted to pure // Warning: (1212-1274): Function state mutability can be restricted to pure // Warning: (1280-1342): Function state mutability can be restricted to pure +// Warning: (771-774): Assertion checker does not yet implement type abi // Warning: (782-813): Type conversion is not yet fully supported and might yield false positives. // Warning: (771-814): Assertion checker does not yet implement this type of function call. -// Warning: (825-830): Assertion checker does not yet support the type of this variable. // Warning: (1403-1408): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/complex/warn_on_struct.sol b/test/libsolidity/smtCheckerTests/complex/warn_on_struct.sol index f6c6f89c2..33cf71e81 100644 --- a/test/libsolidity/smtCheckerTests/complex/warn_on_struct.sol +++ b/test/libsolidity/smtCheckerTests/complex/warn_on_struct.sol @@ -9,5 +9,6 @@ contract C { // ---- // Warning: (133-143): Unused local variable. // Warning: (133-143): Assertion checker does not yet support the type of this variable. +// Warning: (146-147): Assertion checker does not yet implement type type(struct C.A storage pointer) // Warning: (146-163): Assertion checker does not yet implement type struct C.A memory // Warning: (146-163): Assertion checker does not yet implement this expression. diff --git a/test/libsolidity/smtCheckerTests/functions/abi_encode_functions.sol b/test/libsolidity/smtCheckerTests/functions/abi_encode_functions.sol index 8c3ef4aff..bed409b90 100644 --- a/test/libsolidity/smtCheckerTests/functions/abi_encode_functions.sol +++ b/test/libsolidity/smtCheckerTests/functions/abi_encode_functions.sol @@ -6,5 +6,7 @@ contract C { } // ---- // Warning: (31-64): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (162-165): Assertion checker does not yet implement type abi // Warning: (162-176): Assertion checker does not yet implement this type of function call. +// Warning: (178-181): Assertion checker does not yet implement type abi // Warning: (178-203): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol b/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol new file mode 100644 index 000000000..f1ea2e053 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol @@ -0,0 +1,15 @@ +pragma experimental SMTChecker; + +contract A { + uint x; + constructor() public { + x = 2; + } +} + +contract B is A { + constructor() A() public { + x = 3; + } +} +// ---- diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy.sol index ebc674edf..9a772b27e 100644 --- a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy.sol +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy.sol @@ -1,6 +1,16 @@ pragma experimental SMTChecker; -contract C { constructor(uint) public {} } -contract A is C { constructor() C(2) public {} } +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract A is C { + constructor() C(2) public { + assert(a == 2); + assert(a == 3); + } +} // ---- -// Warning: (45-72): Assertion checker does not yet support constructors. -// Warning: (93-121): Assertion checker does not yet support constructors. +// Warning: (166-180): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_2.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_2.sol index 0b96c566c..b5a3f36d8 100644 --- a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_2.sol @@ -1,10 +1,7 @@ pragma experimental SMTChecker; -contract C { constructor(uint) public {} } -contract A is C { constructor() C(2) public {} } -contract B is C { constructor() C(3) public {} } -contract J is C { constructor() C(3) public {} } +contract C { uint a; constructor(uint x) public { a = x; } } +contract A is C { constructor() C(2) public { assert(a == 2); } } +contract B is C { constructor() C(3) public { assert(a == 3); } } +contract J is C { constructor() C(3) public { assert(a == 4); } } // ---- -// Warning: (45-72): Assertion checker does not yet support constructors. -// Warning: (93-121): Assertion checker does not yet support constructors. -// Warning: (142-170): Assertion checker does not yet support constructors. -// Warning: (191-219): Assertion checker does not yet support constructors. +// Warning: (271-285): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_3.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_3.sol new file mode 100644 index 000000000..4db811ee2 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_3.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B is C { + constructor(uint x) public { + a = x; + } +} + +contract A is B { + constructor(uint x) B(x) C(x + 2) public { + assert(a == x); + assert(a == x + 1); + } +} +// ---- +// Warning: (244-262): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_4.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_4.sol new file mode 100644 index 000000000..63a229614 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_4.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B is C { + constructor(uint x) public { + a = x; + } +} + +contract A is B { + constructor(uint x) C(x + 2) B(x + 1) public { + assert(a == x + 1); + } +} +// ---- +// Warning: (212-217): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (203-208): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (242-247): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond.sol new file mode 100644 index 000000000..44e600a0d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond.sol @@ -0,0 +1,29 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B1 is C { + constructor(uint x) public { + a = x; + } +} + +contract B2 is C { + constructor(uint x) C(x + 2) public { + a = x; + } +} + +contract A is B2, B1 { + constructor(uint x) B2(x) B1(x) public { + assert(a == x); + assert(a == x + 1); + } +} +// ---- +// Warning: (205-210): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (321-339): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_2.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_2.sol new file mode 100644 index 000000000..03922d16e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_2.sol @@ -0,0 +1,29 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B1 is C { + constructor(uint x) public { + a = x; + } +} + +contract B2 is C { + constructor(uint x) C(x + 2) public { + a = x; + } +} + +contract A is B2, B1 { + constructor(uint x) B1(x) B2(x) public { + assert(a == x); + assert(a == x + 1); + } +} +// ---- +// Warning: (205-210): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (321-339): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_3.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_3.sol new file mode 100644 index 000000000..429b5d135 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_3.sol @@ -0,0 +1,34 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B1 is C { + uint b1; + constructor(uint x) public { + b1 = x + a; + } +} + +contract B2 is C { + uint b2; + constructor(uint x) C(x + 2) public { + b2 = x + a; + } +} + +contract A is B2, B1 { + constructor(uint x) B2(x) B1(x) public { + assert(b1 == b2); + assert(b1 != b2); + } +} +// ---- +// Warning: (165-170): Underflow (resulting value less than 0) happens here +// Warning: (165-170): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (230-235): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (253-258): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (353-369): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle.sol new file mode 100644 index 000000000..7819019e9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle.sol @@ -0,0 +1,23 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor() public { + a = 2; + } +} + +contract B is C { +} + +contract B2 is C { +} + +contract A is B, B2 { + constructor(uint x) public { + assert(a == 2); + assert(a == 3); + } +} +// ---- +// Warning: (171-177): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (208-222): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle_empty_base.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle_empty_base.sol new file mode 100644 index 000000000..7c35f4fae --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_diamond_empty_middle_empty_base.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor() public { + a = 2; + } +} + +contract B is C { +} + +contract B2 is C { + constructor() public { + assert(a == 2); + } +} + +contract A is B, B2 { +} diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_chain.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_chain.sol new file mode 100644 index 000000000..2c7d8827d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_chain.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor() public { + a = 2; + } +} + +contract E is F {} +contract D is E {} +contract C is D {} +contract B is C {} + +contract A is B { + constructor(uint x) public { + assert(a == 2); + assert(a == 3); + } +} +// ---- +// Warning: (201-207): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (238-252): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle.sol new file mode 100644 index 000000000..fbd3436dc --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor() public { + a = 2; + } +} + +contract B is C { +} + +contract A is B { + constructor(uint x) B() public { + assert(a == 2); + assert(a == 3); + } +} +// ---- +// Warning: (145-151): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (186-200): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle_no_invocation.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle_no_invocation.sol new file mode 100644 index 000000000..8b94c53a9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_empty_middle_no_invocation.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor() public { + a = 2; + } +} + +contract B is C { +} + +contract A is B { + constructor(uint x) public { + assert(a == 3); + } +} +// ---- +// Warning: (145-151): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (164-178): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain.sol new file mode 100644 index 000000000..ec534a431 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain.sol @@ -0,0 +1,30 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor() public { + a = 2; + } +} + +contract E is F {} +contract D is E { + constructor() public { + a = 3; + } +} +contract C is D {} +contract B is C { + constructor() public { + a = 4; + } +} + +contract A is B { + constructor(uint x) public { + assert(a == 4); + assert(a == 5); + } +} +// ---- +// Warning: (275-281): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (312-326): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_empty_base.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_empty_base.sol new file mode 100644 index 000000000..71cbbd13c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_empty_base.sol @@ -0,0 +1,24 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor() public { + a = 2; + } +} + +contract E is F {} +contract D is E { + constructor() public { + a = 3; + } +} +contract C is D {} +contract B is C { + constructor() public { + assert(a == 3); + a = 4; + } +} + +contract A is B { +} diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_local_vars.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_local_vars.sol new file mode 100644 index 000000000..eaa7c5dc4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_local_vars.sol @@ -0,0 +1,35 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor() public { + uint f = 2; + a = f; + } +} + +contract E is F {} +contract D is E { + constructor() public { + uint d = 3; + a = d; + } +} +contract C is D {} +contract B is C { + constructor() public { + uint b = 4; + a = b; + } +} + +contract A is B { + constructor(uint x) public { + uint a1 = 4; + uint a2 = 5; + assert(a == a1); + assert(a == a2); + } +} +// ---- +// Warning: (317-323): Unused function parameter. Remove or comment out the variable name to silence this warning. +// Warning: (385-400): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params.sol new file mode 100644 index 000000000..66380328d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params.sol @@ -0,0 +1,29 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract E is F {} +contract D is E { + constructor() public { + a = 3; + } +} +contract C is D {} +contract B is C { + constructor(uint x) F(x + 1) public { + } +} + +contract A is B { + constructor(uint x) B(x) public { + assert(a == 3); + assert(a == 4); + } +} +// ---- +// Warning: (234-239): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (329-343): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params_2.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params_2.sol new file mode 100644 index 000000000..12890009a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_mixed_chain_with_params_2.sol @@ -0,0 +1,27 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract E is F {} +contract D is E { + constructor() public { + a = 3; + } +} +contract C is D {} +contract B is C { + constructor() F(1) public { + assert(a == 3); + assert(a == 2); + } +} + +contract A is B { +} +// ---- +// Warning: (260-274): Assertion violation happens here +// Warning: (260-274): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_modifier.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_modifier.sol new file mode 100644 index 000000000..3feff4dae --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_modifier.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; +contract C { + uint a; + modifier n { _; a = 7; } + constructor(uint x) n public { + a = x; + } +} + +contract A is C { + modifier m { a = 5; _; } + constructor() C(2) public { + assert(a == 4); + } +} +// ---- +// Warning: (202-216): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_same_var.sol b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_same_var.sol new file mode 100644 index 000000000..a607a9556 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_hierarchy_same_var.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract A is C { + constructor() C(2) public { + assert(a == 0); + assert(C.a == 0); + } +} +// ---- +// Warning: (148-162): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_simple.sol b/test/libsolidity/smtCheckerTests/functions/constructor_simple.sol new file mode 100644 index 000000000..b4f68e33a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_simple.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + + constructor() public { + assert(x == 0); + x = 10; + } + + function f(uint y) public view { + assert(y == x); + } +} +// ---- +// Warning: (148-162): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_state_value.sol b/test/libsolidity/smtCheckerTests/functions/constructor_state_value.sol new file mode 100644 index 000000000..1df298f13 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_state_value.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C { + uint x = 5; + + constructor() public { + assert(x == 5); + x = 10; + } + + function f(uint y) public view { + assert(y == x); + } +} +// ---- +// Warning: (152-166): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_state_value_inherited.sol b/test/libsolidity/smtCheckerTests/functions/constructor_state_value_inherited.sol new file mode 100644 index 000000000..e7349e044 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_state_value_inherited.sol @@ -0,0 +1,18 @@ +pragma experimental SMTChecker; + +contract B { + uint x = 5; +} + +contract C is B { + constructor() public { + assert(x == 5); + x = 10; + } + + function f(uint y) public view { + assert(y == x); + } +} +// ---- +// Warning: (172-186): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_state_value_parameter.sol b/test/libsolidity/smtCheckerTests/functions/constructor_state_value_parameter.sol new file mode 100644 index 000000000..214eb1cd6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_state_value_parameter.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C { + uint x = 5; + + constructor(uint a, uint b) public { + assert(x == 5); + x = a + b; + } + + function f(uint y) public view { + assert(y == x); + } +} +// ---- +// Warning: (169-183): Assertion violation happens here +// Warning: (122-127): Overflow (resulting value larger than 2**256 - 1) happens here diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_this.sol b/test/libsolidity/smtCheckerTests/functions/constructor_this.sol new file mode 100644 index 000000000..0a308a166 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_this.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; +contract C { + function f() public pure {} + constructor() public { + C c = this; + c.f(); // this does not warn now, but should warn in the future + this.f(); + (this).f(); + } +} +// ---- +// Warning: (204-208): "this" used in constructor. Note that external functions of a contract cannot be called while it is being constructed. +// Warning: (223-227): "this" used in constructor. Note that external functions of a contract cannot be called while it is being constructed. diff --git a/test/libsolidity/smtCheckerTests/functions/functions_library_1.sol b/test/libsolidity/smtCheckerTests/functions/functions_library_1.sol index 2ceb9e607..85387017b 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_library_1.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_library_1.sol @@ -16,3 +16,5 @@ contract C assert(y < 10000); } } +// ---- +// Warning: (228-229): Assertion checker does not yet implement type type(library L) diff --git a/test/libsolidity/smtCheckerTests/functions/functions_library_1_fail.sol b/test/libsolidity/smtCheckerTests/functions/functions_library_1_fail.sol index 324197004..5eda70213 100644 --- a/test/libsolidity/smtCheckerTests/functions/functions_library_1_fail.sol +++ b/test/libsolidity/smtCheckerTests/functions/functions_library_1_fail.sol @@ -17,4 +17,5 @@ contract C } } // ---- +// Warning: (228-229): Assertion checker does not yet implement type type(library L) // Warning: (245-261): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_hierarchy_mixed_chain_with_params.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_hierarchy_mixed_chain_with_params.sol new file mode 100644 index 000000000..66380328d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_hierarchy_mixed_chain_with_params.sol @@ -0,0 +1,29 @@ +pragma experimental SMTChecker; +contract F { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract E is F {} +contract D is E { + constructor() public { + a = 3; + } +} +contract C is D {} +contract B is C { + constructor(uint x) F(x + 1) public { + } +} + +contract A is B { + constructor(uint x) B(x) public { + assert(a == 3); + assert(a == 4); + } +} +// ---- +// Warning: (234-239): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (329-343): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init.sol new file mode 100644 index 000000000..bef807b81 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract C { + uint x = 2; + constructor () public { + assert(x == 2); + assert(x == 3); + } +} +// ---- +// Warning: (104-118): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_base.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_base.sol new file mode 100644 index 000000000..179b2c6c6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_base.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C { + uint x = 2; +} + +contract D is C { + constructor() public { + assert(x == 2); + assert(x == 3); + } +} +// ---- +// Warning: (124-138): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain.sol new file mode 100644 index 000000000..e43d9e73a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract A { + uint x = 1; +} + +contract B is A { + constructor() public { x = 2; } +} + +contract C is B { + constructor() public { x = 3; } +} + +contract D is C { + constructor() public { + assert(x == 3); + assert(x == 2); + } +} +// ---- +// Warning: (232-246): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_alternate.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_alternate.sol new file mode 100644 index 000000000..ee1b098a5 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_alternate.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract A { + uint x = 1; +} + +contract B is A { + constructor() public { x = 2; } +} + +contract C is B { +} + +contract D is C { + constructor() public { + assert(x == 2); + assert(x == 3); + } +} +// ---- +// Warning: (199-213): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all.sol new file mode 100644 index 000000000..edc6eeb33 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all.sol @@ -0,0 +1,27 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B is C { + uint b; + constructor(uint x) public { + b = a + x; + } +} + +contract A is B { + constructor(uint x) B(x) C(x + 2) public { + assert(a == x + 2); + assert(b == x + x + 2); + assert(a == x + 5); + } +} + +// ---- +// Warning: (162-167): Underflow (resulting value less than 0) happens here +// Warning: (162-167): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (287-305): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all_2.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all_2.sol new file mode 100644 index 000000000..7efffde55 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_chain_run_all_2.sol @@ -0,0 +1,26 @@ +pragma experimental SMTChecker; +contract C { + uint a; + constructor(uint x) public { + a = x; + } +} + +contract B is C { + uint b; + constructor(uint x) public { + b = x + 10; + } +} + +contract A is B { + constructor(uint x) B(x) C(x + 2) public { + assert(a == x + 2); + assert(b == x + 10); + assert(b == x + 5); + } +} + +// ---- +// Warning: (162-168): Overflow (resulting value larger than 2**256 - 1) happens here +// Warning: (285-303): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond.sol new file mode 100644 index 000000000..65fddfe52 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; + +contract A { + uint x = 2; +} + +contract B is A { +} + +contract C is A { +} + +contract D is B, C { + constructor() public { + assert(x == 2); + assert(x == 3); + } +} +// ---- +// Warning: (169-183): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond_middle.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond_middle.sol new file mode 100644 index 000000000..5a28788c4 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_diamond_middle.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract A { + uint x = 1; +} + +contract B is A { + constructor() public { x = 2; } +} + +contract C is A { + constructor() public { x = 3; } +} + +contract D is B, C { + constructor() public { + assert(x == 3); + assert(x == 4); + } +} +// ---- +// Warning: (235-249): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_function_call.sol b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_function_call.sol new file mode 100644 index 000000000..91798ec0c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/constructor_state_variable_init_function_call.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C { + uint x = f(2); + constructor () public { + assert(x == 2); + } + + function f(uint y) internal view returns (uint) { + assert(y > 0); + assert(x == 0); + return y; + } +} +// ---- +// Warning: (162-175): Assertion violation happens here +// Warning: (179-193): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/inheritance/functions_1.sol b/test/libsolidity/smtCheckerTests/inheritance/functions_1.sol index d43e90955..23b8180bb 100644 --- a/test/libsolidity/smtCheckerTests/inheritance/functions_1.sol +++ b/test/libsolidity/smtCheckerTests/inheritance/functions_1.sol @@ -15,7 +15,7 @@ contract A { // 2 warnings, B.f and A.g contract B is A { function f() public view { - assert(x == 0); + assert(x == 1); } } // ---- diff --git a/test/libsolidity/smtCheckerTests/inheritance/functions_2.sol b/test/libsolidity/smtCheckerTests/inheritance/functions_2.sol index c23eb0037..dd278a78a 100644 --- a/test/libsolidity/smtCheckerTests/inheritance/functions_2.sol +++ b/test/libsolidity/smtCheckerTests/inheritance/functions_2.sol @@ -17,7 +17,7 @@ contract B is A { uint y; function f() public view { - assert(x == 0); + assert(x == 1); } } // ---- diff --git a/test/libsolidity/smtCheckerTests/inheritance/functions_3.sol b/test/libsolidity/smtCheckerTests/inheritance/functions_3.sol index d8fbdabfc..2005eb5e7 100644 --- a/test/libsolidity/smtCheckerTests/inheritance/functions_3.sol +++ b/test/libsolidity/smtCheckerTests/inheritance/functions_3.sol @@ -17,10 +17,10 @@ contract B is A { uint y; function f() public view { - assert(x == 0); + assert(x == 1); } function h() public view { - assert(x == 2); + assert(x == 1); } } @@ -29,10 +29,10 @@ contract C is B { uint z; function f() public view { - assert(x == 0); + assert(x == 1); } function i() public view { - assert(x == 0); + assert(x == 1); } } // ---- diff --git a/test/libsolidity/smtCheckerTests/inheritance/implicit_constructor_hierarchy.sol b/test/libsolidity/smtCheckerTests/inheritance/implicit_constructor_hierarchy.sol new file mode 100644 index 000000000..1eeb4e640 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/implicit_constructor_hierarchy.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract A { + uint x; + constructor (uint y) public { assert(x == 0); x = y; } +} + +contract B is A { + constructor () A(2) public { assert(x == 2); } +} + +contract C is B { + function f() public view { + assert(x == 2); + } +} diff --git a/test/libsolidity/smtCheckerTests/inheritance/implicit_only_constructor_hierarchy.sol b/test/libsolidity/smtCheckerTests/inheritance/implicit_only_constructor_hierarchy.sol new file mode 100644 index 000000000..c724b7289 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/inheritance/implicit_only_constructor_hierarchy.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; + +contract A { + uint x; + function h() public view { + assert(x == 0); + } +} + +contract B is A { + function g() public view { + assert(x == 0); + } +} + +contract C is B { + function f() public view { + assert(x == 0); + } +} diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_array.sol b/test/libsolidity/smtCheckerTests/invariants/loop_array.sol deleted file mode 100644 index 0f6937d36..000000000 --- a/test/libsolidity/smtCheckerTests/invariants/loop_array.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma experimental SMTChecker; - -contract Simple { - uint[] a; - function f(uint n) public { - uint i; - while (i < n) - { - a[i] = i; - ++i; - } - require(n > 1); - // Assertion is safe but current solver version cannot solve it. - // Keep test for next solver release. - assert(a[n-1] > a[n-2]); - } -} -// ---- -// Warning: (273-296): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol new file mode 100644 index 000000000..92d1ded3e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol @@ -0,0 +1,26 @@ +pragma experimental SMTChecker; + +contract LoopFor2 { + uint[] b; + uint[] c; + + function testUnboundedForLoop(uint n) public { + b[0] = 900; + uint[] storage a = b; + require(n > 0 && n < 100); + uint i; + while (i < n) { + b[i] = i + 1; + c[i] = b[i]; + ++i; + } + // Fails as false positive. + assert(b[0] == c[0]); + assert(a[0] == 900); + assert(b[0] == 900); + } +} +// ---- +// Warning: (296-316): Assertion violation happens here +// Warning: (320-339): Assertion violation happens here +// Warning: (343-362): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2.sol b/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2.sol new file mode 100644 index 000000000..154939913 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; +pragma experimental "ABIEncoderV2"; + +contract C { + struct S { uint x; uint[] b; } + function f() public pure returns (S memory, bytes memory, uint[][2] memory) { + return abi.decode("abc", (S, bytes, uint[][2])); + } +} +// ---- +// Warning: (32-67): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (151-159): Assertion checker does not yet support the type of this variable. +// Warning: (206-209): Assertion checker does not yet implement type abi +// Warning: (225-226): Assertion checker does not yet implement type type(struct C.S storage pointer) +// Warning: (235-241): Assertion checker does not yet implement type type(uint256[] memory) +// Warning: (235-244): Assertion checker does not yet implement type type(uint256[] memory[2] memory) +// Warning: (206-246): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2_value_types.sol b/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2_value_types.sol new file mode 100644 index 000000000..cf86e8e78 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/abi_decode_memory_v2_value_types.sol @@ -0,0 +1,20 @@ +pragma experimental SMTChecker; +pragma experimental "ABIEncoderV2"; + +contract C { + function f() public pure { + (uint x1, bool b1) = abi.decode("abc", (uint, bool)); + (uint x2, bool b2) = abi.decode("abc", (uint, bool)); + // False positive until abi.* are implemented as uninterpreted functions. + assert(x1 == x2); + } +} +// ---- +// Warning: (32-67): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (125-132): Unused local variable. +// Warning: (183-190): Unused local variable. +// Warning: (136-139): Assertion checker does not yet implement type abi +// Warning: (136-167): Assertion checker does not yet implement this type of function call. +// Warning: (194-197): Assertion checker does not yet implement type abi +// Warning: (194-225): Assertion checker does not yet implement this type of function call. +// Warning: (303-319): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/abi_decode_simple.sol b/test/libsolidity/smtCheckerTests/special/abi_decode_simple.sol new file mode 100644 index 000000000..c06577e0f --- /dev/null +++ b/test/libsolidity/smtCheckerTests/special/abi_decode_simple.sol @@ -0,0 +1,24 @@ +pragma experimental SMTChecker; +contract C { + function f() public pure { + (uint a1, bytes32 b1, C c1) = abi.decode("abc", (uint, bytes32, C)); + (uint a2, bytes32 b2, C c2) = abi.decode("abc", (uint, bytes32, C)); + // False positive until abi.* are implemented as uninterpreted functions. + assert(a1 == a2); + assert(a1 != a2); + } + +} +// ---- +// Warning: (88-98): Unused local variable. +// Warning: (100-104): Unused local variable. +// Warning: (161-171): Unused local variable. +// Warning: (173-177): Unused local variable. +// Warning: (108-111): Assertion checker does not yet implement type abi +// Warning: (142-143): Assertion checker does not yet implement type type(contract C) +// Warning: (108-145): Assertion checker does not yet implement this type of function call. +// Warning: (181-184): Assertion checker does not yet implement type abi +// Warning: (215-216): Assertion checker does not yet implement type type(contract C) +// Warning: (181-218): Assertion checker does not yet implement this type of function call. +// Warning: (296-312): Assertion violation happens here +// Warning: (315-331): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol b/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol index 44c252868..33a7d972b 100644 --- a/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol +++ b/test/libsolidity/smtCheckerTests/typecast/enum_from_uint.sol @@ -10,5 +10,6 @@ contract C } } // ---- +// Warning: (132-133): Assertion checker does not yet implement type type(enum C.D) // Warning: (132-136): Type conversion is not yet fully supported and might yield false positives. // Warning: (140-160): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_call.sol b/test/libsolidity/smtCheckerTests/types/address_call.sol index d6d165591..da87778e6 100644 --- a/test/libsolidity/smtCheckerTests/types/address_call.sol +++ b/test/libsolidity/smtCheckerTests/types/address_call.sol @@ -20,3 +20,6 @@ contract C // ---- // Warning: (224-240): Unused local variable. // Warning: (260-275): Assertion violation happens here +// Warning: (279-293): Assertion violation happens here +// Warning: (297-316): Assertion violation happens here +// Warning: (320-344): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol b/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol index 06925a66b..b9d3bcaea 100644 --- a/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol +++ b/test/libsolidity/smtCheckerTests/types/address_delegatecall.sol @@ -20,3 +20,6 @@ contract C // ---- // Warning: (224-240): Unused local variable. // Warning: (268-283): Assertion violation happens here +// Warning: (287-301): Assertion violation happens here +// Warning: (305-324): Assertion violation happens here +// Warning: (328-352): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/address_staticcall.sol b/test/libsolidity/smtCheckerTests/types/address_staticcall.sol index 529ce7ac0..32e4cbb9a 100644 --- a/test/libsolidity/smtCheckerTests/types/address_staticcall.sol +++ b/test/libsolidity/smtCheckerTests/types/address_staticcall.sol @@ -20,3 +20,6 @@ contract C // ---- // Warning: (224-240): Unused local variable. // Warning: (266-281): Assertion violation happens here +// Warning: (285-299): Assertion violation happens here +// Warning: (303-322): Assertion violation happens here +// Warning: (326-350): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/data_location_in_function_type.sol b/test/libsolidity/smtCheckerTests/types/data_location_in_function_type.sol new file mode 100644 index 000000000..9aa72ea7d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/data_location_in_function_type.sol @@ -0,0 +1,5 @@ +pragma experimental SMTChecker; +library L { + struct Nested { uint y; } + function c(function(Nested memory) external returns (uint)[] storage) external pure {} +} diff --git a/test/libsolidity/smtCheckerTests/types/function_type_array_as_reference_type.sol b/test/libsolidity/smtCheckerTests/types/function_type_array_as_reference_type.sol new file mode 100644 index 000000000..2500d3ec7 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/function_type_array_as_reference_type.sol @@ -0,0 +1,9 @@ +pragma experimental SMTChecker; +contract C { + struct Nested { uint y; } + // ensure that we consider array of function pointers as reference type + function b(function(Nested memory) external returns (uint)[] storage) internal pure {} + function c(function(Nested memory) external returns (uint)[] memory) public pure {} + function d(function(Nested memory) external returns (uint)[] calldata) external pure {} +} +// ---- diff --git a/test/libsolidity/smtCheckerTests/types/function_type_arrays.sol b/test/libsolidity/smtCheckerTests/types/function_type_arrays.sol new file mode 100644 index 000000000..f004b5514 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/function_type_arrays.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; +contract C { + function(uint) external returns (uint)[] public x; + function(uint) internal returns (uint)[10] y; + function f() view public { + function(uint) returns (uint)[10] memory a; + function(uint) returns (uint)[10] storage b = y; + function(uint) external returns (uint)[] memory c; + c = new function(uint) external returns (uint)[](200); + a; b; + } +} +// ---- +// Warning: (361-410): Assertion checker does not yet implement this type of function call. diff --git a/test/libsolidity/smtCheckerTests/types/mapping_and_array_of_functions.sol b/test/libsolidity/smtCheckerTests/types/mapping_and_array_of_functions.sol new file mode 100644 index 000000000..a10559ece --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/mapping_and_array_of_functions.sol @@ -0,0 +1,7 @@ +pragma experimental SMTChecker; +contract test { + mapping (address => function() internal returns (uint)) a; + mapping (address => function() external) b; + mapping (address => function() external[]) c; + function() external[] d; +} diff --git a/test/libsolidity/smtCheckerTests/types/no_effect_statements.sol b/test/libsolidity/smtCheckerTests/types/no_effect_statements.sol new file mode 100644 index 000000000..f4d1299e9 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/no_effect_statements.sol @@ -0,0 +1,24 @@ +pragma experimental SMTChecker; +contract test { + struct s { uint a; uint b;} + function f() pure public returns (byte) { + s; + s(1,2); + s[7]; + uint; + uint[7]; + } +} +// ---- +// Warning: (125-126): Statement has no effect. +// Warning: (130-136): Statement has no effect. +// Warning: (140-144): Statement has no effect. +// Warning: (148-152): Statement has no effect. +// Warning: (156-163): Statement has no effect. +// Warning: (125-126): Assertion checker does not yet implement type type(struct test.s storage pointer) +// Warning: (130-131): Assertion checker does not yet implement type type(struct test.s storage pointer) +// Warning: (130-136): Assertion checker does not yet implement type struct test.s memory +// Warning: (130-136): Assertion checker does not yet implement this expression. +// Warning: (140-141): Assertion checker does not yet implement type type(struct test.s storage pointer) +// Warning: (140-144): Assertion checker does not yet implement type type(struct test.s memory[7] memory) +// Warning: (156-163): Assertion checker does not yet implement type type(uint256[7] memory) diff --git a/test/libsolidity/smtCheckerTests/types/struct_1.sol b/test/libsolidity/smtCheckerTests/types/struct_1.sol index 38aa311a6..89c10c73c 100644 --- a/test/libsolidity/smtCheckerTests/types/struct_1.sol +++ b/test/libsolidity/smtCheckerTests/types/struct_1.sol @@ -17,8 +17,10 @@ contract C // Warning: (157-170): Unused local variable. // Warning: (157-170): Assertion checker does not yet support the type of this variable. // Warning: (139-146): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (149-150): Assertion checker does not yet implement type type(struct C.S storage pointer) // Warning: (149-153): Assertion checker does not yet implement type struct C.S memory // Warning: (149-153): Assertion checker does not yet implement this expression. // Warning: (139-153): Assertion checker does not yet implement type struct C.S storage ref +// Warning: (173-174): Assertion checker does not yet implement type type(struct C.S storage pointer) // Warning: (173-177): Assertion checker does not yet implement type struct C.S memory // Warning: (173-177): Assertion checker does not yet implement this expression. diff --git a/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol b/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol index 99db33948..1a266aaa7 100644 --- a/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol +++ b/test/libsolidity/smtCheckerTests/types/tuple_return_branch.sol @@ -15,9 +15,11 @@ contract C { } // ---- // Warning: (112-120): Assertion checker does not yet support the type of this variable. +// Warning: (137-138): Assertion checker does not yet implement type type(struct C.S storage pointer) // Warning: (137-141): Assertion checker does not yet implement type struct C.S memory // Warning: (137-141): Assertion checker does not yet implement this expression. // Warning: (193-203): Assertion checker does not yet support the type of this variable. +// Warning: (137-138): Assertion checker does not yet implement type type(struct C.S storage pointer) // Warning: (137-141): Assertion checker does not yet implement type struct C.S memory // Warning: (137-141): Assertion checker does not yet implement this expression. // Warning: (227-228): Assertion checker does not yet implement type struct C.S memory diff --git a/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol b/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol index f63e6aef3..2bd64030d 100644 --- a/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol +++ b/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol @@ -7,5 +7,4 @@ function f() public pure { int[][]; } // ---- // Warning: (73-80): Statement has no effect. // Warning: (73-78): Assertion checker does not yet implement type type(int256[] memory) -// Warning: (73-78): Assertion checker does not yet implement this expression. // Warning: (73-80): Assertion checker does not yet implement type type(int256[] memory[] memory) diff --git a/test/libsolidity/smtCheckerTests/types/type_expression_array_3d.sol b/test/libsolidity/smtCheckerTests/types/type_expression_array_3d.sol index 2588ee98d..3f0967caf 100644 --- a/test/libsolidity/smtCheckerTests/types/type_expression_array_3d.sol +++ b/test/libsolidity/smtCheckerTests/types/type_expression_array_3d.sol @@ -7,6 +7,5 @@ function f() public pure { int[][][]; } // ---- // Warning: (73-82): Statement has no effect. // Warning: (73-78): Assertion checker does not yet implement type type(int256[] memory) -// Warning: (73-78): Assertion checker does not yet implement this expression. // Warning: (73-80): Assertion checker does not yet implement type type(int256[] memory[] memory) // Warning: (73-82): Assertion checker does not yet implement type type(int256[] memory[] memory[] memory) diff --git a/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_2d.sol b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_2d.sol index 02c2ac056..e648b5d6d 100644 --- a/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_2d.sol +++ b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_2d.sol @@ -7,6 +7,5 @@ function f() public pure { (int[][]); } // ---- // Warning: (73-82): Statement has no effect. // Warning: (74-79): Assertion checker does not yet implement type type(int256[] memory) -// Warning: (74-79): Assertion checker does not yet implement this expression. // Warning: (74-81): Assertion checker does not yet implement type type(int256[] memory[] memory) // Warning: (73-82): Assertion checker does not yet implement type type(int256[] memory[] memory) diff --git a/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_3d.sol b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_3d.sol index 893f30581..2d66a3bb2 100644 --- a/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_3d.sol +++ b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_3d.sol @@ -7,7 +7,6 @@ function f() public pure { (int[][][]); } // ---- // Warning: (73-84): Statement has no effect. // Warning: (74-79): Assertion checker does not yet implement type type(int256[] memory) -// Warning: (74-79): Assertion checker does not yet implement this expression. // Warning: (74-81): Assertion checker does not yet implement type type(int256[] memory[] memory) // Warning: (74-83): Assertion checker does not yet implement type type(int256[] memory[] memory[] memory) // Warning: (73-84): Assertion checker does not yet implement type type(int256[] memory[] memory[] memory) diff --git a/test/libsolidity/syntaxTests/conversion/not_allowed_conversion_from_super.sol b/test/libsolidity/syntaxTests/conversion/not_allowed_conversion_from_super.sol new file mode 100644 index 000000000..dc6f00f43 --- /dev/null +++ b/test/libsolidity/syntaxTests/conversion/not_allowed_conversion_from_super.sol @@ -0,0 +1,15 @@ +contract S +{ + int o; + function foo() public returns (int) { return o = 3; } +} + +contract B is S +{ + function fii() public + { + o = S(super).foo(); + } +} +// ---- +// TypeError: (129-137): Explicit type conversion not allowed from "contract super B" to "contract S". diff --git a/test/libsolidity/syntaxTests/inlineAssembly/constant_array.sol b/test/libsolidity/syntaxTests/inlineAssembly/constant_array.sol index 04cb37421..0711c5368 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/constant_array.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/constant_array.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// TypeError: (115-116): Only direct number constants are supported by inline assembly. +// TypeError: (115-116): Only direct number constants and references to such constants are supported by inline assembly. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/constant_bytes_ref.sol b/test/libsolidity/syntaxTests/inlineAssembly/constant_bytes_ref.sol new file mode 100644 index 000000000..dba00a588 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/constant_bytes_ref.sol @@ -0,0 +1,11 @@ +contract C { + bytes32 constant x = keccak256("abc"); + bytes32 constant y = x; + function f() public pure returns (uint t) { + assembly { + t := y + } + } +} +// ---- +// TypeError: (168-169): Only direct number constants and references to such constants are supported by inline assembly. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/constant_ref.sol b/test/libsolidity/syntaxTests/inlineAssembly/constant_ref.sol index eba70edce..dbde5cfa3 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/constant_ref.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/constant_ref.sol @@ -8,4 +8,3 @@ contract C { } } // ---- -// TypeError: (134-135): Only direct number constants are supported by inline assembly. diff --git a/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol b/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol new file mode 100644 index 000000000..684322143 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_hex_valid_parts.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"aa" hex"b"; + return escapeCharacters; + } +} +// ---- +// ParserError: (108-112): Expected even number of hex-nibbles within double-quotes. diff --git a/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol b/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol new file mode 100644 index 000000000..1d4de1759 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_newline_with_hex_prefix.sol @@ -0,0 +1,9 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"0000" + hex"deaf" + hex"feed"; + return escapeCharacters; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol b/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol new file mode 100644 index 000000000..613b86da6 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_newline_without_hex_prefix.sol @@ -0,0 +1,10 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"0000" + "deaf" + "feed"; + return escapeCharacters; + } +} +// ---- +// ParserError: (118-124): Expected ';' but got 'StringLiteral' \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol b/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol new file mode 100644 index 000000000..d748104f2 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_only_hex.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = hex"aa" hex"bb" "cc"; + return escapeCharacters; + } +} +// ---- +// ParserError: (116-120): Expected ';' but got 'StringLiteral' diff --git a/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol b/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol new file mode 100644 index 000000000..b420601f5 --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_only_regular.sol @@ -0,0 +1,8 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = "foo" "bar" hex"aa"; + return escapeCharacters; + } +} +// ---- +// ParserError: (112-119): Expected ';' but got 'HexStringLiteral' diff --git a/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol b/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol new file mode 100644 index 000000000..c07bde30c --- /dev/null +++ b/test/libsolidity/syntaxTests/string/string_multipart_single_line.sol @@ -0,0 +1,7 @@ +contract test { + function f() public pure returns (bytes32) { + bytes32 escapeCharacters = "first" "second" "third"; + return escapeCharacters; + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/types/library_function_selector.sol b/test/libsolidity/syntaxTests/types/library_function_selector.sol new file mode 100644 index 000000000..b24f134a6 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_function_selector.sol @@ -0,0 +1,13 @@ +library L { + function f(uint256) external {} + function g(uint256[] storage) external {} + function h(uint256[] memory) public {} +} +contract C { + function f() public pure returns (bytes4 a, bytes4 b, bytes4 c, bytes4 d) { + a = L.f.selector; + b = L.g.selector; + c = L.h.selector; + d = L.h.selector; + } +} \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/types/library_function_selector_internal.sol b/test/libsolidity/syntaxTests/types/library_function_selector_internal.sol new file mode 100644 index 000000000..8f155867e --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_function_selector_internal.sol @@ -0,0 +1,10 @@ +library L { + function f(uint256) internal {} +} +contract C { + function f() public pure returns (bytes4) { + return L.f.selector; + } +} +// ---- +// TypeError: (126-138): Member "selector" not found or not visible after argument-dependent lookup in function (uint256). diff --git a/test/libsolidity/syntaxTests/types/library_function_selector_private_inside.sol b/test/libsolidity/syntaxTests/types/library_function_selector_private_inside.sol new file mode 100644 index 000000000..825953196 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_function_selector_private_inside.sol @@ -0,0 +1,8 @@ +library L { + function f(uint256) private {} + function g(uint256) public returns (uint256) { + return f.selector; + } +} +// ---- +// TypeError: (113-123): Member "selector" not found or not visible after argument-dependent lookup in function (uint256). diff --git a/test/libsolidity/syntaxTests/types/library_function_selector_private_outside.sol b/test/libsolidity/syntaxTests/types/library_function_selector_private_outside.sol new file mode 100644 index 000000000..b5785f88f --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_function_selector_private_outside.sol @@ -0,0 +1,10 @@ +library L { + function f(uint256) private {} +} +contract C { + function f() public pure returns (bytes4) { + return L.f.selector; + } +} +// ---- +// TypeError: (125-128): Member "f" not found or not visible after argument-dependent lookup in type(library L). diff --git a/test/libsolidity/syntaxTests/types/library_function_selector_view_pure.sol b/test/libsolidity/syntaxTests/types/library_function_selector_view_pure.sol new file mode 100644 index 000000000..02ace71f0 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/library_function_selector_view_pure.sol @@ -0,0 +1,10 @@ +library L { + function f(uint256) external pure {} + function g(uint256) external view {} +} +contract C { + function f() public pure returns (bytes4, bytes4) { + return (L.f.selector, L.g.selector); + } +} +// ---- diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index c8948e6c3..aa3492cb0 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -37,6 +37,8 @@ #include +#include + using namespace std; using namespace langutil; using namespace yul; @@ -75,7 +77,7 @@ pair, shared_ptr> yul::test::parse(strin yul::Block yul::test::disambiguate(string const& _source, bool _yul) { auto result = parse(_source, _yul); - return boost::get(Disambiguator(defaultDialect(_yul), *result.second, {})(*result.first)); + return std::get(Disambiguator(defaultDialect(_yul), *result.second, {})(*result.first)); } string yul::test::format(string const& _source, bool _yul) diff --git a/test/libyul/EWasmTranslationTest.cpp b/test/libyul/EWasmTranslationTest.cpp new file mode 100644 index 000000000..dc174ff7b --- /dev/null +++ b/test/libyul/EWasmTranslationTest.cpp @@ -0,0 +1,156 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +using namespace dev; +using namespace langutil; +using namespace yul; +using namespace yul::test; +using namespace dev::solidity; +using namespace dev::solidity::test; +using namespace std; + + +EWasmTranslationTest::EWasmTranslationTest(string const& _filename) +{ + boost::filesystem::path path(_filename); + + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + m_source = parseSourceAndSettings(file); + m_expectation = parseSimpleExpectations(file); +} + +TestCase::TestResult EWasmTranslationTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + if (!parse(_stream, _linePrefix, _formatted)) + return TestResult::FatalError; + + *m_object = EVMToEWasmTranslator( + EVMDialect::strictAssemblyForEVMObjects(dev::test::Options::get().evmVersion()) + ).run(*m_object); + + // Add call to "main()". + m_object->code->statements.emplace_back( + ExpressionStatement{{}, FunctionCall{{}, Identifier{{}, "main"_yulstring}, {}}} + ); + + m_obtainedResult = interpret(); + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + // TODO could compute a simple diff with highlighted lines + printIndented(_stream, m_expectation, nextIndentLevel); + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl; + printIndented(_stream, m_obtainedResult, nextIndentLevel); + return TestResult::Failure; + } + return TestResult::Success; +} + +void EWasmTranslationTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void EWasmTranslationTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void EWasmTranslationTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + _stream << _linePrefix << line << endl; +} + +bool EWasmTranslationTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) +{ + AssemblyStack stack( + dev::test::Options::get().evmVersion(), + AssemblyStack::Language::StrictAssembly, + dev::solidity::OptimiserSettings::none() + ); + if (stack.parseAndAnalyze("", m_source)) + { + m_object = stack.parserResult(); + return true; + } + else + { + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; + printErrors(_stream, stack.errors()); + return false; + } +} + +string EWasmTranslationTest::interpret() +{ + InterpreterState state; + state.maxTraceSize = 10000; + state.maxSteps = 10000; + WasmDialect dialect; + Interpreter interpreter(state, dialect); + try + { + interpreter(*m_object->code); + } + catch (InterpreterTerminatedGeneric const&) + { + } + + stringstream result; + state.dumpTraceAndState(result); + return result.str(); +} + +void EWasmTranslationTest::printErrors(ostream& _stream, ErrorList const& _errors) +{ + SourceReferenceFormatter formatter(_stream); + + for (auto const& error: _errors) + formatter.printErrorInformation(*error); +} diff --git a/test/libyul/EWasmTranslationTest.h b/test/libyul/EWasmTranslationTest.h new file mode 100644 index 000000000..c63c305f9 --- /dev/null +++ b/test/libyul/EWasmTranslationTest.h @@ -0,0 +1,65 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include +#include + +namespace langutil +{ +class Scanner; +class Error; +using ErrorList = std::vector>; +} + +namespace yul +{ +namespace test +{ + +class EWasmTranslationTest: public dev::solidity::test::EVMVersionRestrictedTestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { + return std::make_unique(_config.filename); + } + + explicit EWasmTranslationTest(std::string const& _filename); + + TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; + + void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override; + void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override; + +private: + void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const; + bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted); + std::string interpret(); + + static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); + + std::string m_source; + std::string m_expectation; + + std::shared_ptr m_object; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/FunctionSideEffects.cpp b/test/libyul/FunctionSideEffects.cpp index 6ae6df8ab..ba0a17599 100644 --- a/test/libyul/FunctionSideEffects.cpp +++ b/test/libyul/FunctionSideEffects.cpp @@ -87,7 +87,7 @@ TestCase::TestResult FunctionSideEffects::run(ostream& _stream, string const& _l m_obtainedResult.clear(); for (auto const& fun: functionSideEffectsStr) - m_obtainedResult += fun.first + ": " + fun.second + "\n"; + m_obtainedResult += fun.first + ":" + (fun.second.empty() ? "" : " ") + fun.second + "\n"; if (m_expectation != m_obtainedResult) { diff --git a/test/libyul/FunctionSideEffects.h b/test/libyul/FunctionSideEffects.h index 8fad2961c..582b45432 100644 --- a/test/libyul/FunctionSideEffects.h +++ b/test/libyul/FunctionSideEffects.h @@ -34,7 +34,7 @@ class FunctionSideEffects: public dev::solidity::test::TestCase { public: static std::unique_ptr create(Config const& _config) - { return std::unique_ptr(new FunctionSideEffects(_config.filename)); } + { return std::make_unique(_config.filename); } explicit FunctionSideEffects(std::string const& _filename); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override; diff --git a/test/libyul/ObjectCompilerTest.h b/test/libyul/ObjectCompilerTest.h index df7fd0905..6e89e059d 100644 --- a/test/libyul/ObjectCompilerTest.h +++ b/test/libyul/ObjectCompilerTest.h @@ -42,7 +42,7 @@ class ObjectCompilerTest: public dev::solidity::test::TestCase public: static std::unique_ptr create(Config const& _config) { - return std::unique_ptr(new ObjectCompilerTest(_config.filename)); + return std::make_unique(_config.filename); } explicit ObjectCompilerTest(std::string const& _filename); diff --git a/test/libyul/YulInterpreterTest.h b/test/libyul/YulInterpreterTest.h index 4263b7336..eb456f1d0 100644 --- a/test/libyul/YulInterpreterTest.h +++ b/test/libyul/YulInterpreterTest.h @@ -42,7 +42,7 @@ class YulInterpreterTest: public dev::solidity::test::EVMVersionRestrictedTestCa public: static std::unique_ptr create(Config const& _config) { - return std::unique_ptr(new YulInterpreterTest(_config.filename)); + return std::make_unique(_config.filename); } explicit YulInterpreterTest(std::string const& _filename); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index fb120ea03..221f3414d 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ #include #include +#include using namespace dev; using namespace langutil; @@ -278,6 +280,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line ExpressionJoiner::run(*m_context, *m_ast); ExpressionJoiner::run(*m_context, *m_ast); } + else if (m_optimizerStep == "loopInvariantCodeMotion") + { + disambiguate(); + ForLoopInitRewriter::run(*m_context, *m_ast); + LoopInvariantCodeMotion::run(*m_context, *m_ast); + } else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); @@ -415,7 +423,7 @@ bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool c void YulOptimizerTest::disambiguate() { - *m_ast = boost::get(Disambiguator(*m_dialect, *m_analysisInfo)(*m_ast)); + *m_ast = std::get(Disambiguator(*m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); updateContext(); } @@ -423,7 +431,11 @@ void YulOptimizerTest::disambiguate() void YulOptimizerTest::updateContext() { m_nameDispenser = make_unique(*m_dialect, *m_ast, m_reservedIdentifiers); - m_context = unique_ptr(new OptimiserStepContext{*m_dialect, *m_nameDispenser, m_reservedIdentifiers}); + m_context = make_unique(OptimiserStepContext{ + *m_dialect, + *m_nameDispenser, + m_reservedIdentifiers + }); } void YulOptimizerTest::printErrors(ostream& _stream, ErrorList const& _errors) diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index 87248cfad..a9cc04ab1 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -51,7 +51,7 @@ class YulOptimizerTest: public dev::solidity::test::EVMVersionRestrictedTestCase public: static std::unique_ptr create(Config const& _config) { - return std::unique_ptr(new YulOptimizerTest(_config.filename)); + return std::make_unique(_config.filename); } explicit YulOptimizerTest(std::string const& _filename); diff --git a/test/libyul/ewasmTranslationTests/datacopy.yul b/test/libyul/ewasmTranslationTests/datacopy.yul new file mode 100644 index 000000000..8812e7c9b --- /dev/null +++ b/test/libyul/ewasmTranslationTests/datacopy.yul @@ -0,0 +1,20 @@ +object "main" +{ + code { + datacopy(0, and(dataoffset("main"), 15), and(datasize("main"), 15)) + datacopy(32, and(dataoffset("sub"), 15), and(datasize("sub"), 15)) + sstore(0, mload(0)) + sstore(1, mload(32)) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 636f6465636f6465000000000000000000000000000000000000000000000000 +// 40: 6465636f00000000000000000000000000000000000000000000000000000000 +// 60: 636f6465636f6465000000000000000000000000000000000000000000000000 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 6465636f00000000000000000000000000000000000000000000000000000000 +// 0000000000000000000000000000000000000000000000000000000000000001: 636f6465636f6465000000000000000000000000000000000000000000000000 diff --git a/test/libyul/ewasmTranslationTests/dataoffset.yul b/test/libyul/ewasmTranslationTests/dataoffset.yul new file mode 100644 index 000000000..d9dc12c9c --- /dev/null +++ b/test/libyul/ewasmTranslationTests/dataoffset.yul @@ -0,0 +1,16 @@ +object "main" +{ + code { + sstore(0, dataoffset("main")) + sstore(1, dataoffset("sub")) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 000000000000000000000000000000000000000000000000000000000000070c +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 000000000000000000000000000000000000000000000000000000000000006e +// 0000000000000000000000000000000000000000000000000000000000000001: 000000000000000000000000000000000000000000000000000000000000070c diff --git a/test/libyul/ewasmTranslationTests/datasize.yul b/test/libyul/ewasmTranslationTests/datasize.yul new file mode 100644 index 000000000..637c2c36d --- /dev/null +++ b/test/libyul/ewasmTranslationTests/datasize.yul @@ -0,0 +1,16 @@ +object "main" +{ + code { + sstore(0, datasize("main")) + sstore(1, datasize("sub")) + } + object "sub" { code { sstore(0, 1) } } +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0000000000000000000000000000000000000000000000000000000000000109 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000b64 +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000000109 diff --git a/test/libyul/ewasmTranslationTests/mstore_mload.yul b/test/libyul/ewasmTranslationTests/mstore_mload.yul new file mode 100644 index 000000000..95646abe2 --- /dev/null +++ b/test/libyul/ewasmTranslationTests/mstore_mload.yul @@ -0,0 +1,14 @@ +{ + mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20) + mstore(0x40, mload(0x20)) + sstore(1, mload(0x40)) +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// 80: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000001: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 diff --git a/test/libyul/ewasmTranslationTests/shl.yul b/test/libyul/ewasmTranslationTests/shl.yul new file mode 100644 index 000000000..f49092a30 --- /dev/null +++ b/test/libyul/ewasmTranslationTests/shl.yul @@ -0,0 +1,17 @@ +{ + let x := 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 + let y := shl(120, x) + let z := shr(136, y) + sstore(0, y) + sstore(1, z) +} +// ==== +// EVMVersion: >=constantinople +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000001 +// 20: 0000000000000000000000000000000000101112131415161718191a1b1c1d1e +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 101112131415161718191a1b1c1d1e1f20000000000000000000000000000000 +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000101112131415161718191a1b1c1d1e diff --git a/test/libyul/ewasmTranslationTests/simple_mstore.yul b/test/libyul/ewasmTranslationTests/simple_mstore.yul new file mode 100644 index 000000000..dd9ac1d5e --- /dev/null +++ b/test/libyul/ewasmTranslationTests/simple_mstore.yul @@ -0,0 +1,8 @@ +{ + mstore(0x20, 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20) +} +// ---- +// Trace: +// Memory dump: +// 60: 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20 +// Storage dump: diff --git a/test/libyul/ewasmTranslationTests/simple_sstore.yul b/test/libyul/ewasmTranslationTests/simple_sstore.yul new file mode 100644 index 000000000..8fbb4923a --- /dev/null +++ b/test/libyul/ewasmTranslationTests/simple_sstore.yul @@ -0,0 +1,12 @@ +{ + sstore(1, 7) + sstore(2, sub(0, 1)) +} +// ---- +// Trace: +// Memory dump: +// 0: 0000000000000000000000000000000000000000000000000000000000000002 +// 20: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000000007 +// 0000000000000000000000000000000000000000000000000000000000000002: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libyul/ewasmTranslationTests/smoke.yul b/test/libyul/ewasmTranslationTests/smoke.yul new file mode 100644 index 000000000..f7f1a1aef --- /dev/null +++ b/test/libyul/ewasmTranslationTests/smoke.yul @@ -0,0 +1,5 @@ +{} +// ---- +// Trace: +// Memory dump: +// Storage dump: diff --git a/test/libyul/functionSideEffects/cyclic_graph.yul b/test/libyul/functionSideEffects/cyclic_graph.yul index 742d74087..58eba0acf 100644 --- a/test/libyul/functionSideEffects/cyclic_graph.yul +++ b/test/libyul/functionSideEffects/cyclic_graph.yul @@ -5,6 +5,6 @@ } // ---- // : movable, sideEffectFree, sideEffectFreeIfNoMSize -// a: movable, sideEffectFree, sideEffectFreeIfNoMSize -// b: movable, sideEffectFree, sideEffectFreeIfNoMSize -// c: movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: +// b: +// c: diff --git a/test/libyul/functionSideEffects/doubly_recursive_function.yul b/test/libyul/functionSideEffects/doubly_recursive_function.yul index cbc594f38..9afcfb0af 100644 --- a/test/libyul/functionSideEffects/doubly_recursive_function.yul +++ b/test/libyul/functionSideEffects/doubly_recursive_function.yul @@ -4,5 +4,5 @@ } // ---- // : movable, sideEffectFree, sideEffectFreeIfNoMSize -// a: movable, sideEffectFree, sideEffectFreeIfNoMSize -// b: movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: +// b: diff --git a/test/libyul/functionSideEffects/recursive_function.yul b/test/libyul/functionSideEffects/recursive_function.yul index cacefefb4..875339cba 100644 --- a/test/libyul/functionSideEffects/recursive_function.yul +++ b/test/libyul/functionSideEffects/recursive_function.yul @@ -3,4 +3,4 @@ } // ---- // : movable, sideEffectFree, sideEffectFreeIfNoMSize -// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: diff --git a/test/libyul/functionSideEffects/with_loop.yul b/test/libyul/functionSideEffects/with_loop.yul new file mode 100644 index 000000000..d68aa1fec --- /dev/null +++ b/test/libyul/functionSideEffects/with_loop.yul @@ -0,0 +1,9 @@ +{ + function f() -> x { x := g() } + function g() -> x { for {} 1 {} {} } + pop(f()) +} +// ---- +// : +// f: +// g: diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index 4838eab02..ab1ebb10d 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -477,16 +477,15 @@ // pos := add(pos, 0x60) // } // let _3 := mload(64) -// let _4 := mload(0x20) -// if slt(sub(_3, _4), 128) { revert(_1, _1) } -// let offset := calldataload(add(_4, 64)) -// let _5 := 0xffffffffffffffff -// if gt(offset, _5) { revert(_1, _1) } -// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_4, offset), _3) -// let offset_1 := calldataload(add(_4, 96)) -// if gt(offset_1, _5) { revert(_1, _1) } -// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_4, offset_1), _3) -// sstore(calldataload(_4), calldataload(add(_4, 0x20))) +// if slt(sub(_3, length), 128) { revert(_1, _1) } +// let offset := calldataload(add(length, 64)) +// let _4 := 0xffffffffffffffff +// if gt(offset, _4) { revert(_1, _1) } +// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(length, offset), _3) +// let offset_1 := calldataload(add(length, 0x60)) +// if gt(offset_1, _4) { revert(_1, _1) } +// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(length, offset_1), _3) +// sstore(calldataload(length), calldataload(add(length, 0x20))) // sstore(value2, value3) // sstore(_1, pos) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 0b82de7e9..35e1bb7b3 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -311,7 +311,7 @@ // } // b := add(b, _5) // } -// if lt(m, n) { validatePairing(0x64) } +// if lt(m, n) { validatePairing(100) } // if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), _2), challenge)) // { // mstore(0, 404) diff --git a/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul b/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul index 19816b445..d958a318f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul @@ -12,7 +12,7 @@ // { // { // let y := mload(0x20) -// for { } and(y, 8) { if y { revert(0, 0) } } +// for { } iszero(iszero(and(y, 8))) { if y { revert(0, 0) } } // { // if y { continue } // sstore(1, 0) diff --git a/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul b/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul new file mode 100644 index 000000000..f570d2ecf --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul @@ -0,0 +1,34 @@ +{ + sstore(0, array_sum(calldataload(0))) + + function array_sum(x) -> sum { + let length := calldataload(x) + for { let i := 0 } lt(i, length) { i := add(i, 1) } { + sum := add(sum, array_load(x, i)) + } + } + function array_load(x, i) -> v { + let len := calldataload(x) + if iszero(lt(i, len)) { revert(0, 0) } + let data := add(x, 0x20) + v := calldataload(add(data, mul(i, 0x20))) + // this is just to have some additional code that + // can be moved out of the loop. + v := add(v, calldataload(7)) + } +} +// ==== +// step: fullSuite +// ---- +// { +// { +// let _1 := calldataload(0) +// let sum := 0 +// let i := sum +// for { } lt(i, calldataload(_1)) { i := add(i, 1) } +// { +// sum := add(sum, add(calldataload(add(add(_1, mul(i, 0x20)), 0x20)), calldataload(7))) +// } +// sstore(0, sum) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/no_move_loop_orig.yul b/test/libyul/yulOptimizerTests/fullSuite/no_move_loop_orig.yul new file mode 100644 index 000000000..5562bd85e --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/no_move_loop_orig.yul @@ -0,0 +1,24 @@ +{ + for {} msize() { + function foo_s_0() -> x_1 { for {} caller() {} {} } + // x_3 used to be a movable loop invariant because `foo_s_0()` used to be movable + let x_3 := foo_s_0() + mstore(192, x_3) + } + {} +} +// ==== +// step: fullSuite +// ---- +// { +// { +// for { } +// 1 +// { +// for { } iszero(iszero(caller())) { } +// { } +// mstore(192, 0) +// } +// { if iszero(msize()) { break } } +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/remove_redundant_assignments_in_switch.yul b/test/libyul/yulOptimizerTests/fullSuite/remove_redundant_assignments_in_switch.yul new file mode 100644 index 000000000..fa1364297 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/remove_redundant_assignments_in_switch.yul @@ -0,0 +1,20 @@ + { + let x := 0 + switch mload(x) + case 0 { x := x } + case 1 { x := 1 } + default { invalid() } + mstore(1, 1) +} +// ==== +// step: fullSuite +// ---- +// { +// { +// switch mload(0) +// case 0 { } +// case 1 { } +// default { invalid() } +// mstore(1, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul new file mode 100644 index 000000000..82ea2bf78 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let c := mload(3) // c cannot be moved because non-movable + let not_inv := add(b, c) // no_inv cannot be moved because its value depends on c + a := add(a, 1) + mstore(a, not_inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let c := mload(3) +// let not_inv := add(b, c) +// a := add(a, 1) +// mstore(a, not_inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul new file mode 100644 index 000000000..c7e39cc70 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul @@ -0,0 +1,26 @@ +{ + let b := 1 + // tests if c, d, and inv can be moved outside in single pass + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let c := b + let d := mul(c, 2) + let inv := add(c, d) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let c := b +// let d := mul(c, 2) +// let inv := add(c, d) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_loop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_loop.yul new file mode 100644 index 000000000..7f842bbdd --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_loop.yul @@ -0,0 +1,29 @@ +{ + function f() -> x { x := g() } + function g() -> x { for {} 1 {} {} } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := f() + let q := g() + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// for { } 1 { } +// { } +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := f() +// let q := g() +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_recursive_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_recursive_function.yul new file mode 100644 index 000000000..5f3eeb7ac --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_recursive_function.yul @@ -0,0 +1,26 @@ +{ + function f() -> x { x := g() } + function g() -> x { x := g() } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := f() + let q := g() + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { x_1 := g() } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := f() +// let q := g() +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul new file mode 100644 index 000000000..86cf1274e --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let not_inv := add(b, 42) + not_inv := add(not_inv, 1) + a := add(a, 1) + mstore(a, not_inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let not_inv := add(b, 42) +// not_inv := add(not_inv, 1) +// a := add(a, 1) +// mstore(a, not_inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul new file mode 100644 index 000000000..787c1756b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul @@ -0,0 +1,21 @@ +{ + let b := 0 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := mload(b) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 0 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let inv := mload(b) +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul new file mode 100644 index 000000000..a489b134b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul @@ -0,0 +1,25 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + for { let a2 := 1 } iszero(eq(a2, 10)) { a2 := add(a2, 1) } { + let inv := add(b, 42) + mstore(a, inv) + } + a := add(a, 1) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let a2 := 1 +// for { } iszero(eq(a2, 10)) { a2 := add(a2, 1) } +// { mstore(a, inv) } +// a := add(a, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul new file mode 100644 index 000000000..970fec59f --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul @@ -0,0 +1,21 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/solcjsTests.sh b/test/solcjsTests.sh index df2554c21..d525a3f21 100755 --- a/test/solcjsTests.sh +++ b/test/solcjsTests.sh @@ -40,7 +40,7 @@ VERSION="$2" DIR=$(mktemp -d) ( echo "Preparing solc-js (master)..." - git clone --depth 1 --branch master https://github.com/ethereum/solc-js "$DIR" + git clone --depth 1 --branch v0.5.0 https://github.com/ethereum/solc-js "$DIR" cd "$DIR" # disable "prepublish" script which downloads the latest version # (we will replace it anyway and it is often incorrectly cached diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index f02b092bd..70a79ffdf 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(isoltest ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerJSONTest.cpp ../libyul/Common.cpp + ../libyul/EWasmTranslationTest.cpp ../libyul/FunctionSideEffects.cpp ../libyul/ObjectCompilerTest.cpp ../libyul/YulOptimizerTest.cpp diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index edeb1e554..5cd64a6c7 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -34,7 +34,8 @@ static vector s_evmVersions = { "spuriousDragon", "byzantium", "constantinople", - "petersburg" + "petersburg", + "istanbul" }; void FuzzerUtil::runCompiler(string const& _input, bool _quiet) diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 2481c63c3..83543b9f0 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -71,7 +71,8 @@ if (OSSFUZZ) /usr/include/libprotobuf-mutator ) target_link_libraries(abiv2_proto_ossfuzz PRIVATE solidity - evmc evmone intx ethash evmc-instructions + evmc + evmone-standalone protobuf-mutator-libfuzzer.a protobuf-mutator.a protobuf.a diff --git a/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp b/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp index 91bd4b8e2..07e062b8f 100644 --- a/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp +++ b/test/tools/ossfuzz/abiV2ProtoFuzzer.cpp @@ -24,7 +24,7 @@ #include -static evmc::vm evmone = evmc::vm{evmc_create_evmone()}; +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; using namespace dev::test::abiv2fuzzer; using namespace dev::test; @@ -142,7 +142,7 @@ DEFINE_PROTO_FUZZER(Contract const& _input) // We target the default EVM which is the latest langutil::EVMVersion version = {}; - EVMHost hostContext(version, &evmone); + EVMHost hostContext(version, evmone); // Deploy contract and signal failure if deploy failed evmc::result createResult = deployContract(hostContext, byteCode); diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 345797afb..f79d1f4da 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -90,12 +90,35 @@ string ProtoConverter::visit(Literal const& _x) } } +void ProtoConverter::consolidateVarDeclsInFunctionDef() +{ + m_currentFuncVars.clear(); + auto const& scopes = m_funcVars.back(); + for (auto const& s: scopes) + for (auto const& var: s) + m_currentFuncVars.push_back(&var); +} + +void ProtoConverter::consolidateGlobalVarDecls() +{ + m_currentGlobalVars.clear(); + for (auto const& scope: m_globalVars) + for (auto const& var: scope) + m_currentGlobalVars.push_back(&var); +} + bool ProtoConverter::varDeclAvailable() { if (m_inFunctionDef) - return m_scopeVars.top().size() > 0; + { + consolidateVarDeclsInFunctionDef(); + return m_currentFuncVars.size() > 0; + } else - return m_variables.size() > 0; + { + consolidateGlobalVarDecls(); + return m_currentGlobalVars.size() > 0; + } } bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type) @@ -109,14 +132,14 @@ void ProtoConverter::visit(VarRef const& _x) if (m_inFunctionDef) { // Ensure that there is at least one variable declaration to reference in function scope. - yulAssert(m_scopeVars.top().size() > 0, "Proto fuzzer: No variables to reference."); - m_output << m_scopeVars.top()[_x.varnum() % m_scopeVars.top().size()]; + yulAssert(m_currentFuncVars.size() > 0, "Proto fuzzer: No variables to reference."); + m_output << *m_currentFuncVars[_x.varnum() % m_currentFuncVars.size()]; } else { // Ensure that there is at least one variable declaration to reference in nested scopes. - yulAssert(m_variables.size() > 0, "Proto fuzzer: No variables to reference."); - m_output << m_variables[_x.varnum() % m_variables.size()]; + yulAssert(m_currentGlobalVars.size() > 0, "Proto fuzzer: No global variables to reference."); + m_output << *m_currentGlobalVars[_x.varnum() % m_currentGlobalVars.size()]; } } @@ -258,8 +281,10 @@ void ProtoConverter::visit(VarDecl const& _x) m_output << "let " << varName << " := "; visit(_x.expr()); m_output << "\n"; - m_scopeVars.top().push_back(varName); - m_variables.push_back(varName); + if (m_inFunctionDef) + m_funcVars.back().back().push_back(varName); + else + m_globalVars.back().push_back(varName); } void ProtoConverter::visit(TypedVarDecl const& _x) @@ -324,8 +349,10 @@ void ProtoConverter::visit(TypedVarDecl const& _x) m_output << " : u256\n"; break; } - m_scopeVars.top().push_back(varName); - m_variables.push_back(varName); + if (m_inFunctionDef) + m_funcVars.back().back().push_back(varName); + else + m_globalVars.back().push_back(varName); } void ProtoConverter::visit(UnaryOp const& _x) @@ -437,6 +464,12 @@ void ProtoConverter::visit(NullaryOp const& _x) case NullaryOp::GASLIMIT: m_output << "gaslimit()"; break; + case NullaryOp::SELFBALANCE: + m_output << "selfbalance()"; + break; + case NullaryOp::CHAINID: + m_output << "chainid()"; + break; } } @@ -1096,12 +1129,19 @@ void ProtoConverter::visit(Statement const& _x) } } -void ProtoConverter::openScope(vector const& _funcParams) +void ProtoConverter::openBlockScope() { - m_scopeVars.push({}); - m_scopeFuncs.push({}); - if (!_funcParams.empty()) - addVarsToScope(_funcParams); + m_scopeFuncs.push_back({}); + // Create new block scope inside current function scope + if (m_inFunctionDef) + m_funcVars.back().push_back(vector{}); + else + m_globalVars.push_back(vector{}); +} + +void ProtoConverter::openFunctionScope(vector const& _funcParams) +{ + m_funcVars.push_back(vector>({_funcParams})); } void ProtoConverter::updateFunctionMaps(string const& _var) @@ -1118,21 +1158,9 @@ void ProtoConverter::updateFunctionMaps(string const& _var) yulAssert(erased == 2, "Proto fuzzer: Function maps not updated"); } -void ProtoConverter::closeScope() +void ProtoConverter::closeBlockScope() { - for (auto const& var: m_scopeVars.top()) - { - unsigned numVarsRemoved = m_variables.size(); - m_variables.erase(remove(m_variables.begin(), m_variables.end(), var), m_variables.end()); - numVarsRemoved -= m_variables.size(); - yulAssert( - numVarsRemoved == 1, - "Proto fuzzer: Nothing or too much went out of scope" - ); - } - m_scopeVars.pop(); - - for (auto const& f: m_scopeFuncs.top()) + for (auto const& f: m_scopeFuncs.back()) { unsigned numFuncsRemoved = m_functions.size(); m_functions.erase(remove(m_functions.begin(), m_functions.end(), f), m_functions.end()); @@ -1143,21 +1171,40 @@ void ProtoConverter::closeScope() ); updateFunctionMaps(f); } - m_scopeFuncs.pop(); + if (!m_scopeFuncs.empty()) + m_scopeFuncs.pop_back(); + if (!m_inFunctionDef) + { + if (!m_globalVars.empty()) + m_globalVars.pop_back(); + } + else + { + // Variables that have been declared in a + // function block, go out of scope + if (!m_funcVars.empty()) + if (!m_funcVars.back().empty()) + m_funcVars.back().pop_back(); + } +} + +void ProtoConverter::closeFunctionScope() +{ + if (!m_funcVars.empty()) + m_funcVars.pop_back(); } void ProtoConverter::addVarsToScope(vector const& _vars) { - for (string const& i: _vars) - { - m_variables.push_back(i); - m_scopeVars.top().push_back(i); - } + if (m_inFunctionDef) + m_funcVars.back().back().insert(m_funcVars.back().back().end(), _vars.begin(), _vars.end()); + else + m_globalVars.back().insert(m_globalVars.back().end(), _vars.begin(), _vars.end()); } -void ProtoConverter::visit(Block const& _x, vector _funcParams) +void ProtoConverter::visit(Block const& _x) { - openScope(_funcParams); + openBlockScope(); // Register function declarations in this scope unless this // scope belongs to for-init (in which function declarations @@ -1175,7 +1222,7 @@ void ProtoConverter::visit(Block const& _x, vector _funcParams) } else m_output << "{}\n"; - closeScope(); + closeBlockScope(); } vector ProtoConverter::createVars(unsigned _startIdx, unsigned _endIdx) @@ -1218,7 +1265,7 @@ void ProtoConverter::registerFunction(FunctionDef const* _x) auto ret = m_functionSigMap.emplace(make_pair(funcName, make_pair(numInParams, numOutParams))); yulAssert(ret.second, "Proto fuzzer: Function already exists."); m_functions.push_back(funcName); - m_scopeFuncs.top().push_back(funcName); + m_scopeFuncs.back().push_back(funcName); m_functionDefMap.emplace(make_pair(_x, funcName)); } @@ -1357,8 +1404,12 @@ void ProtoConverter::createFunctionDefAndCall( bool wasInFunctionDef = m_inFunctionDef; m_inFunctionDef = true; - // Body - visit(_x.block(), varsVec); + // Create new function scope and add function input and return + // parameters to it. + openFunctionScope(varsVec); + // Visit function body + visit(_x.block()); + closeFunctionScope(); m_inForBodyScope = wasInForBody; m_inFunctionDef = wasInFunctionDef; diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index ef149d198..a0a7240ab 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -42,6 +42,8 @@ class ProtoConverter public: ProtoConverter() { + m_funcVars = std::vector>>{}; + m_globalVars = std::vector>{}; m_inForBodyScope = false; m_inForInitScope = false; m_numNestedForLoops = 0; @@ -62,7 +64,7 @@ private: /// @param _block Reference to a basic block of yul statements. /// @param _funcParams List of function parameter names, defaults to /// an empty vector. - void visit(Block const& _block, std::vector _funcParams = {}); + void visit(Block const& _block); std::string visit(Literal const&); void visit(VarRef const&); @@ -98,11 +100,15 @@ private: void visit(Code const&); void visit(Program const&); - /// Creates a new scope, and adds @a _funcParams to it if it + /// Creates a new block scope. + void openBlockScope(); + /// Creates a new function scope, and adds @a _funcParams to it if it /// is non-empty. - void openScope(std::vector const& _funcParams); - /// Closes current scope - void closeScope(); + void openFunctionScope(std::vector const& _funcParams); + /// Closes current block scope + void closeBlockScope(); + /// Closes current function scope + void closeFunctionScope(); /// Adds @a _vars to current scope void addVarsToScope(std::vector const& _vars); @@ -138,6 +144,14 @@ private: /// Multiple -> "m" std::string functionTypeToString(NumFunctionReturns _type); + /// Builds a single vector containing variables declared in + /// function scope. + void consolidateVarDeclsInFunctionDef(); + + /// Builds a single vector containing variables declared in + /// global scope. + void consolidateGlobalVarDecls(); + /// Return true if at least one variable declaration is in scope, /// false otherwise. /// @return True in the following cases: @@ -295,12 +309,16 @@ private: } std::ostringstream m_output; - /// Variables in current scope - std::stack> m_scopeVars; + /// Variables in all function definitions + std::vector>> m_funcVars; + /// Variables in current function definition + std::vector m_currentFuncVars; + /// Variables in global scope + std::vector m_currentGlobalVars; /// Functions in current scope - std::stack> m_scopeFuncs; + std::vector> m_scopeFuncs; /// Variables - std::vector m_variables; + std::vector> m_globalVars; /// Functions std::vector m_functions; /// Maps FunctionDef object to its name diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 6871a63ac..b7ce8c57a 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -237,6 +237,8 @@ message NullaryOp { NUMBER = 14; DIFFICULTY = 15; GASLIMIT = 16; + SELFBALANCE = 17; + CHAINID = 18; } required NOp op = 1; } diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 557073766..689085240 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -55,8 +55,7 @@ void printErrors(ostream& _stream, ErrorList const& _errors) DEFINE_PROTO_FUZZER(Program const& _input) { - ProtoConverter converter; - string yul_source = converter.programToString(_input); + string yul_source = ProtoConverter().programToString(_input); if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { @@ -70,24 +69,17 @@ DEFINE_PROTO_FUZZER(Program const& _input) // AssemblyStack entry point AssemblyStack stack( - langutil::EVMVersion(), + langutil::EVMVersion::istanbul(), AssemblyStack::Language::StrictAssembly, dev::solidity::OptimiserSettings::full() ); - try + // Parse protobuf mutated YUL code + if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code || + !stack.parserResult()->analysisInfo) { - // Parse protobuf mutated YUL code - if (!stack.parseAndAnalyze("source", yul_source) || !stack.parserResult()->code || - !stack.parserResult()->analysisInfo) - { - printErrors(std::cout, stack.errors()); - return; - } - } - catch (Exception const&) - { - return; + printErrors(std::cout, stack.errors()); + yulAssert(false, "Proto fuzzer generated malformed program"); } ostringstream os1; @@ -95,7 +87,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret( os1, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()) + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::istanbul()) ); if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached) @@ -105,7 +97,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) termReason = yulFuzzerUtil::interpret( os2, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion()), + EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::istanbul()), (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4) ); diff --git a/test/tools/yulInterpreter/CMakeLists.txt b/test/tools/yulInterpreter/CMakeLists.txt index 52fe0e3c6..02d53d697 100644 --- a/test/tools/yulInterpreter/CMakeLists.txt +++ b/test/tools/yulInterpreter/CMakeLists.txt @@ -1,6 +1,8 @@ set(sources EVMInstructionInterpreter.h EVMInstructionInterpreter.cpp + EWasmBuiltinInterpreter.h + EWasmBuiltinInterpreter.cpp Interpreter.h Interpreter.cpp ) diff --git a/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp new file mode 100644 index 000000000..c52ee7e10 --- /dev/null +++ b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.cpp @@ -0,0 +1,376 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Yul interpreter module that evaluates EWasm builtins. + */ + +#include + +#include + +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; +using namespace yul::test; + +namespace +{ + +/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to +/// @a _target at offset @a _targetOffset. Behaves as if @a _source would +/// continue with an infinite sequence of zero bytes beyond its end. +void copyZeroExtended( + map& _target, bytes const& _source, + size_t _targetOffset, size_t _sourceOffset, size_t _size +) +{ + for (size_t i = 0; i < _size; ++i) + _target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0; +} + +} + +using u512 = boost::multiprecision::number>; + +u256 EWasmBuiltinInterpreter::evalBuiltin(YulString _fun, vector const& _arguments) +{ + vector arg; + for (u256 const& a: _arguments) + arg.emplace_back(uint64_t(a & uint64_t(-1))); + + if (_fun == "datasize"_yulstring) + return u256(keccak256(h256(_arguments.at(0)))) & 0xfff; + else if (_fun == "dataoffset"_yulstring) + return u256(keccak256(h256(_arguments.at(0) + 2))) & 0xfff; + else if (_fun == "datacopy"_yulstring) + { + // This is identical to codecopy. + if (accessMemory(_arguments.at(0), _arguments.at(2))) + copyZeroExtended( + m_state.memory, + m_state.code, + size_t(_arguments.at(0)), + size_t(_arguments.at(1) & size_t(-1)), + size_t(_arguments.at(2)) + ); + return 0; + } + else if (_fun == "drop"_yulstring) + return {}; + else if (_fun == "unreachable"_yulstring) + throw ExplicitlyTerminated(); + else if (_fun == "i64.add"_yulstring) + return arg[0] + arg[1]; + else if (_fun == "i64.sub"_yulstring) + return arg[0] - arg[1]; + else if (_fun == "i64.mul"_yulstring) + return arg[0] * arg[1]; + else if (_fun == "i64.div_u"_yulstring) + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] / arg[1]; + } + else if (_fun == "i64.rem_u"_yulstring) + { + if (arg[1] == 0) + throw ExplicitlyTerminated(); + else + return arg[0] % arg[1]; + } + else if (_fun == "i64.and"_yulstring) + return arg[0] & arg[1]; + else if (_fun == "i64.or"_yulstring) + return arg[0] | arg[1]; + else if (_fun == "i64.xor"_yulstring) + return arg[0] ^ arg[1]; + else if (_fun == "i64.shl"_yulstring) + return arg[0] << arg[1]; + else if (_fun == "i64.shr_u"_yulstring) + return arg[0] >> arg[1]; + else if (_fun == "i64.eq"_yulstring) + return arg[0] == arg[1] ? 1 : 0; + else if (_fun == "i64.ne"_yulstring) + return arg[0] != arg[1] ? 1 : 0; + else if (_fun == "i64.eqz"_yulstring) + return arg[0] == 0 ? 1 : 0; + else if (_fun == "i64.lt_u"_yulstring) + return arg[0] < arg[1] ? 1 : 0; + else if (_fun == "i64.gt_u"_yulstring) + return arg[0] > arg[1] ? 1 : 0; + else if (_fun == "i64.le_u"_yulstring) + return arg[0] <= arg[1] ? 1 : 0; + else if (_fun == "i64.ge_u"_yulstring) + return arg[0] >= arg[1] ? 1 : 0; + else if (_fun == "i64.store"_yulstring) + { + accessMemory(arg[0], 8); + writeMemoryWord(arg[0], arg[1]); + return 0; + } + else if (_fun == "i64.load"_yulstring) + { + accessMemory(arg[0], 8); + return readMemoryWord(arg[0]); + } + else if (_fun == "eth.getAddress"_yulstring) + return writeAddress(arg[0], m_state.address); + else if (_fun == "eth.getExternalBalance"_yulstring) + // TODO this does not read the address, but is consistent with + // EVM interpreter implementation. + // If we take the address into account, this needs to use readAddress. + return writeU128(arg[0], m_state.balance); + else if (_fun == "eth.getBlockHash"_yulstring) + { + if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber) + return 1; + else + return writeU256(arg[1], 0xaaaaaaaa + u256(arg[0] - m_state.blockNumber - 256)); + } + else if (_fun == "eth.call"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::CALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callDataCopy"_yulstring) + { + if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.calldata.size()) + throw ExplicitlyTerminated(); + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.calldata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return {}; + } + else if (_fun == "eth.getCallDataSize"_yulstring) + return m_state.calldata.size(); + else if (_fun == "eth.callCode"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::CALLCODE, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callDelegate"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::DELEGATECALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.callStatic"_yulstring) + { + // TODO read args from memory + // TODO use readAddress to read address. + logTrace(eth::Instruction::STATICCALL, {}); + return arg[0] & 1; + } + else if (_fun == "eth.storageStore"_yulstring) + { + m_state.storage[h256(readU256(arg[0]))] = readU256((arg[1])); + return 0; + } + else if (_fun == "eth.storageLoad"_yulstring) + return writeU256(arg[1], m_state.storage[h256(readU256(arg[0]))]); + else if (_fun == "eth.getCaller"_yulstring) + // TODO should this only write 20 bytes? + return writeAddress(arg[0], m_state.caller); + else if (_fun == "eth.getCallValue"_yulstring) + return writeU128(arg[0], m_state.callvalue); + else if (_fun == "eth.codeCopy"_yulstring) + { + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return 0; + } + else if (_fun == "eth.getCodeSize"_yulstring) + return writeU256(arg[0], m_state.code.size()); + else if (_fun == "eth.getBlockCoinbase"_yulstring) + return writeAddress(arg[0], m_state.coinbase); + else if (_fun == "eth.create"_yulstring) + { + // TODO access memory + // TODO use writeAddress to store resulting address + logTrace(eth::Instruction::CREATE, {}); + return 0xcccccc + arg[1]; + } + else if (_fun == "eth.getBlockDifficulty"_yulstring) + return writeU256(arg[0], m_state.difficulty); + else if (_fun == "eth.externalCodeCopy"_yulstring) + { + // TODO use readAddress to read address. + if (accessMemory(arg[1], arg[3])) + // TODO this way extcodecopy and codecopy do the same thing. + copyZeroExtended( + m_state.memory, m_state.code, + size_t(arg[1]), size_t(arg[2]), size_t(arg[3]) + ); + return 0; + } + else if (_fun == "eth.getExternalCodeSize"_yulstring) + return u256(keccak256(h256(readAddress(arg[0])))) & 0xffffff; + else if (_fun == "eth.getGasLeft"_yulstring) + return 0x99; + else if (_fun == "eth.getBlockGasLimit"_yulstring) + return uint64_t(m_state.gaslimit); + else if (_fun == "eth.getTxGasPrice"_yulstring) + return writeU128(arg[0], m_state.gasprice); + else if (_fun == "eth.log"_yulstring) + { + logTrace(eth::Instruction::LOG0, {}); + return 0; + } + else if (_fun == "eth.getBlockNumber"_yulstring) + return m_state.blockNumber; + else if (_fun == "eth.getTxOrigin"_yulstring) + return writeAddress(arg[0], m_state.origin); + else if (_fun == "eth.finish"_yulstring) + { + bytes data; + if (accessMemory(arg[0], arg[1])) + data = readMemory(arg[0], arg[1]); + logTrace(eth::Instruction::RETURN, {}, data); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.revert"_yulstring) + { + bytes data; + if (accessMemory(arg[0], arg[1])) + data = readMemory(arg[0], arg[1]); + logTrace(eth::Instruction::REVERT, {}, data); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.getReturnDataSize"_yulstring) + return m_state.returndata.size(); + else if (_fun == "eth.returnDataCopy"_yulstring) + { + if (arg[1] + arg[2] < arg[1] || arg[1] + arg[2] > m_state.returndata.size()) + throw ExplicitlyTerminated(); + if (accessMemory(arg[0], arg[2])) + copyZeroExtended( + m_state.memory, m_state.calldata, + size_t(arg[0]), size_t(arg[1]), size_t(arg[2]) + ); + return {}; + } + else if (_fun == "eth.selfDestruct"_yulstring) + { + // TODO use readAddress to read address. + logTrace(eth::Instruction::SELFDESTRUCT, {}); + throw ExplicitlyTerminated(); + } + else if (_fun == "eth.getBlockTimestamp"_yulstring) + return m_state.timestamp; + + yulAssert(false, "Unknown builtin: " + _fun.str() + " (or implementation did not return)"); + + return 0; +} + +bool EWasmBuiltinInterpreter::accessMemory(u256 const& _offset, u256 const& _size) +{ + if (((_offset + _size) >= _offset) && ((_offset + _size + 0x1f) >= (_offset + _size))) + { + u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f); + m_state.msize = max(m_state.msize, newSize); + return _size <= 0xffff; + } + else + m_state.msize = u256(-1); + + return false; +} + +bytes EWasmBuiltinInterpreter::readMemory(uint64_t _offset, uint64_t _size) +{ + yulAssert(_size <= 0xffff, "Too large read."); + bytes data(size_t(_size), uint8_t(0)); + for (size_t i = 0; i < data.size(); ++i) + data[i] = m_state.memory[_offset + i]; + return data; +} + +uint64_t EWasmBuiltinInterpreter::readMemoryWord(uint64_t _offset) +{ + uint64_t r = 0; + for (size_t i = 0; i < 8; i++) + r |= uint64_t(m_state.memory[_offset + i]) << (i * 8); + return r; +} + +void EWasmBuiltinInterpreter::writeMemoryWord(uint64_t _offset, uint64_t _value) +{ + for (size_t i = 0; i < 8; i++) + m_state.memory[_offset + i] = uint8_t((_value >> (i * 8)) & 0xff); +} + +u256 EWasmBuiltinInterpreter::writeU256(uint64_t _offset, u256 _value, size_t _croppedTo) +{ + accessMemory(_offset, _croppedTo); + for (size_t i = 0; i < _croppedTo; i++) + { + m_state.memory[_offset + _croppedTo - 1 - i] = uint8_t(_value & 0xff); + _value >>= 8; + } + + return {}; +} + +u256 EWasmBuiltinInterpreter::readU256(uint64_t _offset, size_t _croppedTo) +{ + accessMemory(_offset, _croppedTo); + u256 value; + for (size_t i = 0; i < _croppedTo; i++) + value = (value << 8) | m_state.memory[_offset + i]; + + return value; +} + +void EWasmBuiltinInterpreter::logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments, bytes const& _data) +{ + logTrace(dev::eth::instructionInfo(_instruction).name, _arguments, _data); +} + +void EWasmBuiltinInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments, bytes const& _data) +{ + string message = _pseudoInstruction + "("; + for (size_t i = 0; i < _arguments.size(); ++i) + message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]); + message += ")"; + if (!_data.empty()) + message += " [" + toHex(_data) + "]"; + m_state.trace.emplace_back(std::move(message)); + if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize) + { + m_state.trace.emplace_back("Trace size limit reached."); + throw TraceLimitReached(); + } +} diff --git a/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h new file mode 100644 index 000000000..e96cbbae2 --- /dev/null +++ b/test/tools/yulInterpreter/EWasmBuiltinInterpreter.h @@ -0,0 +1,108 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Yul interpreter module that evaluates EWasm builtins. + */ + +#pragma once + +#include + +#include + +#include + +namespace dev +{ +namespace eth +{ +enum class Instruction: uint8_t; +} +} + +namespace yul +{ +class YulString; +struct BuiltinFunctionForEVM; + +namespace test +{ + +struct InterpreterState; + +/** + * Interprets EWasm builtins based on the current state and logs instructions with + * side-effects. + * + * Since this is mainly meant to be used for differential fuzz testing, it is focused + * on a single contract only, does not do any gas counting and differs from the correct + * implementation in many ways: + * + * - If memory access to a "large" memory position is performed, a deterministic + * value is returned. Data that is stored in a "large" memory position is not + * retained. + * - The blockhash instruction returns a fixed value if the argument is in range. + * - Extcodesize returns a deterministic value depending on the address. + * - Extcodecopy copies a deterministic value depending on the address. + * - And many other things + * + * The main focus is that the generated execution trace is the same for equivalent executions + * and likely to be different for non-equivalent executions. + */ +class EWasmBuiltinInterpreter +{ +public: + explicit EWasmBuiltinInterpreter(InterpreterState& _state): + m_state(_state) + {} + /// Evaluate builtin function + dev::u256 evalBuiltin(YulString _fun, std::vector const& _arguments); + +private: + /// Checks if the memory access is not too large for the interpreter and adjusts + /// msize accordingly. + /// @returns false if the amount of bytes read is lager than 0xffff + bool accessMemory(dev::u256 const& _offset, dev::u256 const& _size = 32); + /// @returns the memory contents at the provided address. + /// Does not adjust msize, use @a accessMemory for that + dev::bytes readMemory(uint64_t _offset, uint64_t _size = 32); + /// @returns the memory contents at the provided address (little-endian). + /// Does not adjust msize, use @a accessMemory for that + uint64_t readMemoryWord(uint64_t _offset); + /// Writes a word to memory (little-endian) + /// Does not adjust msize, use @a accessMemory for that + void writeMemoryWord(uint64_t _offset, uint64_t _value); + + /// Helper for eth.* builtins. Writes to memory (big-endian) and always returns zero. + dev::u256 writeU256(uint64_t _offset, dev::u256 _value, size_t _croppedTo = 32); + dev::u256 writeU128(uint64_t _offset, dev::u256 _value) { return writeU256(_offset, std::move(_value), 16); } + dev::u256 writeAddress(uint64_t _offset, dev::u256 _value) { return writeU256(_offset, std::move(_value), 20); } + /// Helper for eth.* builtins. Reads from memory (big-endian) and returns the value; + dev::u256 readU256(uint64_t _offset, size_t _croppedTo = 32); + dev::u256 readU128(uint64_t _offset) { return readU256(_offset, 16); } + dev::u256 readAddress(uint64_t _offset) { return readU256(_offset, 20); } + + void logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + /// Appends a log to the trace representing an instruction or similar operation by string, + /// with arguments and auxiliary data (if nonempty). + void logTrace(std::string const& _pseudoInstruction, std::vector const& _arguments = {}, dev::bytes const& _data = {}); + + InterpreterState& m_state; +}; + +} +} diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 390c5bf05..93eee5f80 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -21,11 +21,13 @@ #include #include +#include #include #include #include #include +#include #include @@ -35,6 +37,7 @@ #include #include +#include using namespace std; using namespace dev; @@ -161,9 +164,9 @@ void Interpreter::operator()(Block const& _block) openScope(); // Register functions. for (auto const& statement: _block.statements) - if (statement.type() == typeid(FunctionDefinition)) + if (holds_alternative(statement)) { - FunctionDefinition const& funDef = boost::get(statement); + FunctionDefinition const& funDef = std::get(statement); solAssert(!m_scopes.back().count(funDef.name), ""); m_scopes.back().emplace(funDef.name, &funDef); } @@ -228,12 +231,21 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) evaluateArgs(_funCall.arguments); if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + { if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) { EVMInstructionInterpreter interpreter(m_state); setValue(interpreter.evalBuiltin(*fun, values())); return; } + } + else if (WasmDialect const* dialect = dynamic_cast(&m_dialect)) + if (dialect->builtin(_funCall.functionName.name)) + { + EWasmBuiltinInterpreter interpreter(m_state); + setValue(interpreter.evalBuiltin(_funCall.functionName.name, values())); + return; + } auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name); diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index aaaeab511..8534115c5 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include @@ -72,6 +73,7 @@ #include #include #include +#include using namespace std; using namespace dev; @@ -131,7 +133,7 @@ public: set reservedIdentifiers; if (!disambiguated) { - *m_ast = boost::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); + *m_ast = std::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); m_nameDispenser = make_shared(m_dialect, *m_ast, reservedIdentifiers); disambiguated = true; @@ -140,8 +142,8 @@ public: cout << " (e)xpr inline/(i)nline/(s)implify/varname c(l)eaner/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl; cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl; - cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/(L)oad resolver/ " << endl; - cout << " (C)onditional simplifier?" << endl; + cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/(L)oad resolver/" << endl; + cout << " (C)onditional simplifier/loop-invariant code (M)otion?" << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -236,6 +238,9 @@ public: case 'L': LoadResolver::run(context, *m_ast); break; + case 'M': + LoopInvariantCodeMotion::run(context, *m_ast); + break; default: cout << "Unknown option." << endl; }