diff --git a/.circleci/README.md b/.circleci/README.md index 48cc6e363..7f7b01455 100644 --- a/.circleci/README.md +++ b/.circleci/README.md @@ -7,8 +7,8 @@ The docker images are build locally on the developer machine: ```!sh cd .circleci/docker/ -docker build -t ethereum/solc-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 . -docker push solidity/solc-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: @@ -16,3 +16,13 @@ which you can find on Dockerhub after the push at: https://hub.docker.com/r/ethereum/solidity-buildpack-deps where the image tag reflects the target OS to build Solidity and run its test on. + +### Testing docker images locally + +```!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 +cd /src/solidity + +``` \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index e4b4d0654..47c6518ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,7 @@ defaults: name: Build command: | set -ex - if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi + if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" -o -n "$FORCE_RELEASE" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi echo -n "$CIRCLE_SHA1" > commit_hash.txt mkdir -p build cd build @@ -96,22 +96,46 @@ defaults: name: soltest command: ./.circleci/soltest.sh + - run_soltest_all: &run_soltest_all + name: soltest_all + command: ./.circleci/soltest_all.sh + - run_cmdline_tests: &run_cmdline_tests name: command line tests command: ./test/cmdlineTests.sh - - test_steps: &test_steps - - checkout - - attach_workspace: - at: build - - run: *run_soltest - - store_test_results: *store_test_results - - store_artifacts: *artifacts_test_results - - test_ubuntu1904: &test_ubuntu1904 docker: - image: ethereum/solidity-buildpack-deps:ubuntu1904 - steps: *test_steps + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_ubuntu1904_clang: &test_ubuntu1904_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_ubuntu1904_all: &test_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest_all + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results - test_asan: &test_asan <<: *test_ubuntu1904 @@ -138,6 +162,16 @@ defaults: requires: - b_ubu + - workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang + <<: *workflow_trigger_on_tags + requires: + - b_ubu_clang + + - workflow_ubuntu1904_release: &workflow_ubuntu1904_release + <<: *workflow_trigger_on_tags + requires: + - b_ubu_release + - workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov <<: *workflow_trigger_on_tags requires: @@ -163,6 +197,28 @@ defaults: requires: - b_ubu_ossfuzz + # -------------------------------------------------------------------------- + # Notification Templates + - gitter_notify_failure: &gitter_notify_failure + name: Gitter notify failure + command: >- + curl -X POST -i + -i -H "Content-Type: application/json" + -H "Accept: application/json" + -H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages" + -d '{"text":" ❌ Nightly job **'$CIRCLE_JOB'** failed on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}' + when: on_fail + + - gitter_notify_success: &gitter_notify_success + name: Gitter notify success + command: >- + curl -X POST -i + -i -H "Content-Type: application/json" + -H "Accept: application/json" + -H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages" + -d '{"text":" ✅ Nightly job **'$CIRCLE_JOB'** succeeded on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}' + when: on_success + # ----------------------------------------------------------------------------------------------- jobs: @@ -240,6 +296,18 @@ jobs: pip install --user z3-solver - run: *run_proofs + b_ubu_clang: &build_ubuntu1904_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + environment: + CC: clang + CXX: clang++ + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + b_ubu: &build_ubuntu1904 docker: - image: ethereum/solidity-buildpack-deps:ubuntu1904 @@ -249,6 +317,11 @@ jobs: - store_artifacts: *artifacts_solc - persist_to_workspace: *artifacts_executables + b_ubu_release: &build_ubuntu1904_release + <<: *build_ubuntu1904 + environment: + FORCE_RELEASE: ON + b_ubu18: &build_ubuntu1804 docker: - image: ethereum/solidity-buildpack-deps:ubuntu1804 @@ -292,24 +365,24 @@ jobs: command: codecov --flags all --gcov-root build - store_artifacts: *artifacts_test_results - # Builds in C++17 mode and uses debug build in order to speed up. + # Builds in C++20 mode and uses debug build in order to speed up. # Do *NOT* store any artifacts or workspace as we don't run tests on this build. - b_ubu_cxx17: + b_ubu_cxx20: <<: *build_ubuntu1904 environment: CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake -DUSE_CVC4=OFF + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF steps: - checkout - run: *run_build b_ubu_ossfuzz: - <<: *build_ubuntu1904 + <<: *build_ubuntu1904_clang environment: TERM: xterm - CC: /usr/bin/clang-8 - CXX: /usr/bin/clang++-8 - CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake + CC: clang + CXX: clang++ + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake steps: - checkout - run: *setup_prerelease_commit_hash @@ -317,7 +390,7 @@ jobs: - persist_to_workspace: *artifacts_executables_ossfuzz t_ubu_ossfuzz: &t_ubu_ossfuzz - <<: *test_ubuntu1904 + <<: *test_ubuntu1904_clang steps: - checkout - attach_workspace: @@ -328,6 +401,8 @@ jobs: mkdir -p test_results export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2" scripts/regressions.py -o test_results + - run: *gitter_notify_failure + - run: *gitter_notify_success - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results @@ -445,6 +520,18 @@ jobs: path: docs/_build/html/ destination: docs-html + t_ubu_soltest: &t_ubu_soltest + <<: *test_ubuntu1904 + + t_ubu_clang_soltest: &t_ubu_clang_soltest + <<: *test_ubuntu1904_clang + environment: + EVM: constantinople + OPTIMIZE: 0 + + t_ubu_release_soltest: &t_ubu_release_soltest + <<: *t_ubu_soltest + t_ubu_cli: &t_ubu_cli docker: - image: ethereum/solidity-buildpack-deps:ubuntu1904 @@ -458,6 +545,9 @@ jobs: - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results + t_ubu_release_cli: &t_ubu_release_cli + <<: *t_ubu_cli + t_ubu_asan_cli: <<: *t_ubu_cli environment: @@ -478,63 +568,9 @@ jobs: environment: EVM: constantinople OPTIMIZE: 0 + flags: --no-smt ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 - t_ubu_homestead: - <<: *test_ubuntu1904 - environment: - EVM: homestead - OPTIMIZE: 0 - - t_ubu_homestead_opt: - <<: *test_ubuntu1904 - environment: - EVM: homestead - OPTIMIZE: 1 - - t_ubu_byzantium: - <<: *test_ubuntu1904 - environment: - EVM: byzantium - OPTIMIZE: 0 - - t_ubu_byzantium_opt: - <<: *test_ubuntu1904 - environment: - EVM: byzantium - OPTIMIZE: 1 - - t_ubu_constantinople: - <<: *test_ubuntu1904 - environment: - EVM: constantinople - OPTIMIZE: 0 - - t_ubu_constantinople_opt: - <<: *test_ubuntu1904 - environment: - EVM: constantinople - OPTIMIZE: 1 - - t_ubu_constantinople_opt_abiv2: - <<: *test_ubuntu1904 - environment: - EVM: constantinople - OPTIMIZE: 1 - ABI_ENCODER_V2: 1 - - t_ubu_petersburg: - <<: *test_ubuntu1904 - environment: - EVM: petersburg - OPTIMIZE: 0 - - t_ubu_petersburg_opt: - <<: *test_ubuntu1904 - environment: - EVM: petersburg - OPTIMIZE: 1 - t_ems_solcjs: docker: - image: circleci/node:10 @@ -564,6 +600,8 @@ jobs: name: External GnosisSafe tests command: | test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success t_ems_external_zeppelin: docker: @@ -578,6 +616,8 @@ jobs: name: External Zeppelin tests command: | test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success t_ems_external_colony: docker: @@ -596,6 +636,8 @@ jobs: name: External ColonyNetworks tests command: | test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success workflows: version: 2 @@ -612,26 +654,25 @@ workflows: # build-only - b_docs: *workflow_trigger_on_tags - b_archlinux: *workflow_trigger_on_tags - - b_ubu_cxx17: *workflow_trigger_on_tags + - b_ubu_cxx20: *workflow_trigger_on_tags - b_ubu_ossfuzz: *workflow_trigger_on_tags # OS/X build and tests - b_osx: *workflow_trigger_on_tags - t_osx_cli: *workflow_osx - # Ubuntu 18.10 build and tests + # Ubuntu build and tests - b_ubu: *workflow_trigger_on_tags - b_ubu18: *workflow_trigger_on_tags - t_ubu_cli: *workflow_ubuntu1904 - - t_ubu_homestead: *workflow_ubuntu1904 - - t_ubu_homestead_opt: *workflow_ubuntu1904 - - t_ubu_byzantium: *workflow_ubuntu1904 - - t_ubu_byzantium_opt: *workflow_ubuntu1904 - - t_ubu_constantinople: *workflow_ubuntu1904 - - t_ubu_constantinople_opt: *workflow_ubuntu1904 - - t_ubu_constantinople_opt_abiv2: *workflow_ubuntu1904 - - t_ubu_petersburg: *workflow_ubuntu1904 - - t_ubu_petersburg_opt: *workflow_ubuntu1904 + - t_ubu_soltest: *workflow_ubuntu1904 + - b_ubu_clang: *workflow_trigger_on_tags + - t_ubu_clang_soltest: *workflow_ubuntu1904_clang + + # Ubuntu fake release build and tests + - b_ubu_release: *workflow_trigger_on_tags + - t_ubu_release_cli: *workflow_ubuntu1904_release + - t_ubu_release_soltest: *workflow_ubuntu1904_release # ASan build and tests - b_ubu_asan: *workflow_trigger_on_tags @@ -651,6 +692,7 @@ workflows: branches: only: - develop + - develop_060 jobs: # Emscripten builds and external tests diff --git a/.circleci/docker/Dockerfile.clang.ubuntu1904 b/.circleci/docker/Dockerfile.clang.ubuntu1904 new file mode 100644 index 000000000..d21d26bcb --- /dev/null +++ b/.circleci/docker/Dockerfile.clang.ubuntu1904 @@ -0,0 +1,131 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) Clang variant +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# 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 +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:disco AS base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build \ + clang++-8 llvm-8-dev \ + libjsoncpp-dev \ + libleveldb1d \ + ; \ + apt-get install -qy python-pip python-sphinx; \ + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \ + update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 1; \ + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 1; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +ENV CC clang +ENV CXX clang++ + +# Boost +RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git \ + /usr/src/boost; \ + cd /usr/src/boost; \ + ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ + ./b2 toolset=clang headers; \ + ./b2 toolset=clang variant=release \ + system regex filesystem unit_test_framework program_options \ + install -j $(($(nproc)/2)); \ + rm -rf /usr/src/boost + +# Z3 +RUN git clone --depth 1 -b Z3-4.8.5 https://github.com/Z3Prover/z3.git \ + /usr/src/z3; \ + cd /usr/src/z3; \ + python scripts/mk_make.py --prefix=/usr ; \ + cd build; \ + make -j; \ + make install; \ + rm -rf /usr/src/z3; + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + 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; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=ON -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 diff --git a/.circleci/docker/Dockerfile.ubuntu1804 b/.circleci/docker/Dockerfile.ubuntu1804 new file mode 100644 index 000000000..8d6e25289 --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1804 @@ -0,0 +1,117 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 18.04 (Bionic Beaver) +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# 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 +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:bionic AS base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build clang++-8 \ + libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + libjsoncpp-dev \ + llvm-8-dev libz3-static-dev \ + ; \ + apt-get install -qy python-pip python-sphinx; \ + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip; \ + rm -rf /usr/src/libprotobuf-mutator + +# OSSFUZZ: libfuzzer +RUN set -ex; \ + cd /var/tmp; \ + svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \ + mkdir -p build-libfuzzer; \ + cd build-libfuzzer; \ + clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \ + 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_MAJOR="0" +ARG EVMONE_MINOR="1" +ARG EVMONE_MICRO="0" +RUN set -ex; \ + EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \ + TGZFILE="evmone-$EVMONE_VERSION-linux-x86_64.tar.gz"; \ + wget https://github.com/ethereum/evmone/releases/download/v$EVMONE_VERSION/$TGZFILE; \ + sha256sum $TGZFILE; \ + tar xzpf $TGZFILE -C /usr; \ + rm -f $TGZFILE; + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 index 4b7f131de..f356d0bde 100644 --- a/.circleci/docker/Dockerfile.ubuntu1904 +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -33,7 +33,7 @@ RUN set -ex; \ apt-get install -qqy --no-install-recommends \ build-essential \ software-properties-common \ - cmake ninja-build clang++-8 \ + cmake ninja-build clang++-8 libc++-8-dev libc++abi-8-dev \ libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \ libboost-program-options-dev \ libjsoncpp-dev \ @@ -46,16 +46,6 @@ RUN set -ex; \ FROM base AS libraries -# Z3 -RUN set -ex; \ - git clone --depth=1 --branch="Z3-4.8.5" https://github.com/Z3Prover/z3.git /usr/src/z3; \ - mkdir /usr/src/z3/build; \ - cd /usr/src/z3/build; \ - cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="/usr" -G "Ninja" ..; \ - ninja; \ - ninja install/strip; \ - rm -rf /usr/src/z3 - # OSSFUZZ: libprotobuf-mutator RUN set -ex; \ git clone https://github.com/google/libprotobuf-mutator.git \ diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh index dda335102..41f08db77 100755 --- a/.circleci/soltest.sh +++ b/.circleci/soltest.sh @@ -45,7 +45,15 @@ mkdir -p test_results # in case we run with ASAN enabled, we must increase stck size. ulimit -s 16384 -BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/$EVM.xml" +get_logfile_basename() { + local filename="${EVM}" + test "${OPTIMIZE}" = "1" && filename="${filename}_opt" + test "${ABI_ENCODER_V2}" = "1" && filename="${filename}_abiv2" + + echo -ne "${filename}" +} + +BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/`get_logfile_basename`.xml" SOLTEST_ARGS="--evm-version=$EVM --evmonepath /usr/lib/libevmone.so $flags" test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize" test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul" diff --git a/.circleci/soltest_all.sh b/.circleci/soltest_all.sh new file mode 100755 index 000000000..7b4564ee7 --- /dev/null +++ b/.circleci/soltest_all.sh @@ -0,0 +1,37 @@ +#! /bin/bash +#------------------------------------------------------------------------------ +# Bash script to execute the Solidity tests by CircleCI. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# 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 +# +# (c) 2016-2019 solidity contributors. +# ------------------------------------------------------------------------------ +set -e + +REPODIR="$(realpath $(dirname $0)/..)" + +for OPTIMIZE in 0 1; do + for EVM in homestead byzantium constantinople petersburg; 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 diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..f9953c17b --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +# Formatting approximately used in Solidity's C++ +# +# See https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# For an online formatter to test settings, see +# https://zed0.co.uk/clang-format-configurator/ +# Note that clang-format cannot express the style that closing parentheses +# behave similar to closing curly braces in a multi-line setting in that +# they have to be on a line of their own at the same indentation level +# as the opening part. + +Language: Cpp +BasedOnStyle: LLVM +AlignEscapedNewlinesLeft: true +AlwaysBreakAfterReturnType: None +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Allman +ColumnLimit: 120 +ContinuationIndentWidth: 4 +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 +PenaltyBreakBeforeFirstCallParameter: 2000 +SpaceAfterCStyleCast: true +SpaceBeforeParens: ControlStatements +TabWidth: 4 +UseTab: ForIndentation + +# Local Variables: +# mode: yaml +# End: diff --git a/.travis.yml b/.travis.yml index 7e1114974..5754fa0a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -188,7 +188,7 @@ cache: install: - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) - - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) + - test "$TRAVIS_OS_NAME" != "linux" || (sudo scripts/install_cmake.sh) before_script: # Disable tests unless run on the release branch, on tags or with daily cron diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f545697f..a1927bf33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.0) +cmake_minimum_required(VERSION 3.9.0) set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) @@ -10,14 +10,9 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.11") +set(PROJECT_VERSION "0.5.12") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX) -if (${CMAKE_VERSION} VERSION_LESS "3.9.0") - # needed for the big endian test for older cmake versions - enable_language(C) -endif() - include(TestBigEndian) TEST_BIG_ENDIAN(IS_BIG_ENDIAN) if (IS_BIG_ENDIAN) diff --git a/Changelog.md b/Changelog.md index 65ed18b96..f184c0568 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,25 @@ +### 0.5.12 (2019-10-01) + +Language Features: + * Type Checker: Allow assignment to external function arguments except for reference types. + + +Compiler Features: + * ABI Output: Change sorting order of functions from selector to kind, name. + * Optimizer: Add rule that replaces the BYTE opcode by 0 if the first argument is larger than 31. + * SMTChecker: Add loop support to the CHC engine. + * Yul Optimizer: Take side-effect-freeness of user-defined functions into account. + * Yul Optimizer: Remove redundant mload/sload operations. + + +Bugfixes: + * Code Generator: Fix internal error when popping a dynamic storage array of mappings. + * Name Resolver: Fix wrong source location when warning on shadowed aliases in import declarations. + * Scanner: Fix multi-line natspec comment parsing with triple slashes when file is encoded with CRLF instead of LF. + * Type System: Fix arrays of recursive structs. + * Yul Optimizer: Fix reordering bug in connection with shifted one and mul/div-instructions in for loop conditions. + + ### 0.5.11 (2019-08-12) @@ -12,9 +34,11 @@ Compiler Features: * Standard JSON Interface: Provide secondary error locations (e.g. the source position of other conflicting declarations). * SMTChecker: Do not erase knowledge about storage pointers if another storage pointer is assigned. * SMTChecker: Support string literal type. + * SMTChecker: New Horn-based algorithm that proves assertions via multi-transaction contract invariants. * Standard JSON Interface: Provide AST even on errors if ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` is true. * Yul Optimizer: Do not inline function if it would result in expressions being duplicated that are not cheap. + Bugfixes: * ABI decoder: Ensure that decoded arrays always point to distinct memory locations. * Code Generator: Treat dynamically encoded but statically sized arrays and structs in calldata properly. diff --git a/cmake/FindZ3.cmake b/cmake/FindZ3.cmake index 4b63ed4cb..092b8636b 100644 --- a/cmake/FindZ3.cmake +++ b/cmake/FindZ3.cmake @@ -19,15 +19,20 @@ if (USE_Z3) find_library(Z3_LIBRARY NAMES z3) find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin) - if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE) - execute_process (COMMAND ${Z3_EXECUTABLE} -version - OUTPUT_VARIABLE libz3_version_str - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) + if(Z3_INCLUDE_DIR AND Z3_LIBRARY) + if(Z3_EXECUTABLE) + execute_process (COMMAND ${Z3_EXECUTABLE} -version + OUTPUT_VARIABLE libz3_version_str + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) - string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" - Z3_VERSION_STRING "${libz3_version_str}") - unset(libz3_version_str) + string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" + Z3_VERSION_STRING "${libz3_version_str}") + unset(libz3_version_str) + else() + message(WARNING "Could not determine the version of z3, since the z3 executable was not found.") + set(Z3_VERSION_STRING "0.0.0") + endif() endif() mark_as_advanced(Z3_VERSION_STRING z3_DIR) diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index 4ca8581dc..3e72a8be2 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -15,11 +15,18 @@ set(JSONCPP_INCLUDE_DIR "${prefix}/include") # versions used in the CI runs. if(EMSCRIPTEN) # Do not include all flags in CMAKE_CXX_FLAGS for emscripten, - # but only use -std=c++14. Using all flags causes build failures + # but only use -std=c++17. Using all flags causes build failures # at the moment. - set(JSONCPP_CXX_FLAGS -std=c++14) + set(JSONCPP_CXX_FLAGS -std=c++17) else() - set(JSONCPP_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + # jsoncpp uses implicit casts for comparing integer and + # floating point numbers. This causes clang-10 (used by ossfuzz builder) + # to error on the implicit conversions. Here, we request jsoncpp + # to unconditionally use static casts for these conversions by defining the + # JSON_USE_INT64_DOUBLE_CONVERSION preprocessor macro. Doing so, + # not only gets rid of the implicit conversion error that clang-10 produces + # but also forces safer behavior in general. + set(JSONCPP_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJSON_USE_INT64_DOUBLE_CONVERSION") endif() set(byproducts "") diff --git a/cmake/toolchains/cxx17.cmake b/cmake/toolchains/cxx17.cmake deleted file mode 100644 index 04a865ebd..000000000 --- a/cmake/toolchains/cxx17.cmake +++ /dev/null @@ -1,4 +0,0 @@ -# Require C++17. -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED TRUE) -set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/cxx20.cmake b/cmake/toolchains/cxx20.cmake new file mode 100644 index 000000000..ad34e5749 --- /dev/null +++ b/cmake/toolchains/cxx20.cmake @@ -0,0 +1,4 @@ +# Require C++20. +set(CMAKE_CXX_STANDARD 20) # This requires at least CMake 3.12 to understand this C++20 flag +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/default.cmake b/cmake/toolchains/default.cmake index baf859b70..07fc80e8e 100644 --- a/cmake/toolchains/default.cmake +++ b/cmake/toolchains/default.cmake @@ -1,4 +1,4 @@ -# Require C++14. -set(CMAKE_CXX_STANDARD 14) +# Require C++17. +set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag. set(CMAKE_CXX_STANDARD_REQUIRED TRUE) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/libfuzzer.cmake b/cmake/toolchains/libfuzzer.cmake index 86d45d304..354e6d09b 100644 --- a/cmake/toolchains/libfuzzer.cmake +++ b/cmake/toolchains/libfuzzer.cmake @@ -1,2 +1,11 @@ -# Require libfuzzer specific flags -set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++") +# Inherit default options +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +# Enable Z3, disable CVC4 +set(USE_Z3 ON CACHE BOOL "Enable Z3" FORCE) +set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) +# Build fuzzing binaries +set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) +# Use libfuzzer as the fuzzing back-end +set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE) +# clang/libfuzzer specific flags for ASan instrumentation +set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE) diff --git a/cmake/toolchains/ossfuzz.cmake b/cmake/toolchains/ossfuzz.cmake new file mode 100644 index 000000000..113b6b453 --- /dev/null +++ b/cmake/toolchains/ossfuzz.cmake @@ -0,0 +1,11 @@ +# Inherit default options +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +# Disable CVC4. +set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) +# Enable fuzzers +set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) +set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE STRING "Use fuzzer back-end defined by environment variable" FORCE) +# Link statically against boost libraries +set(BOOST_FOUND ON CACHE BOOL "" FORCE) +set(Boost_USE_STATIC_LIBS ON CACHE BOOL "Link against static Boost libraries" FORCE) +set(Boost_USE_STATIC_RUNTIME ON CACHE BOOL "Link against static Boost runtime library" FORCE) diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 3a6d077d4..3d3e846c8 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -444,17 +444,17 @@ JSON The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields: -- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function `); -- ``name``: the name of the function; +- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function `). +- ``name``: the name of the function. - ``inputs``: an array of objects, each of which contains: - * ``name``: the name of the parameter; + * ``name``: the name of the parameter. * ``type``: the canonical type of the parameter (more below). * ``components``: used for tuple types (more below). -- ``outputs``: an array of objects similar to ``inputs``, can be omitted if function doesn't return anything; -- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state `), ``view`` (:ref:`specified to not modify the blockchain state `), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether); -- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise; +- ``outputs``: an array of objects similar to ``inputs``. +- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state `), ``view`` (:ref:`specified to not modify the blockchain state `), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether). +- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise. - ``constant``: ``true`` if function is either ``pure`` or ``view``, ``false`` otherwise. ``type`` can be omitted, defaulting to ``"function"``, likewise ``payable`` and ``constant`` can be omitted, both defaulting to ``false``. @@ -470,10 +470,10 @@ Constructor and fallback function never have ``name`` or ``outputs``. Fallback f An event description is a JSON object with fairly similar fields: - ``type``: always ``"event"`` -- ``name``: the name of the event; +- ``name``: the name of the event. - ``inputs``: an array of objects, each of which contains: - * ``name``: the name of the parameter; + * ``name``: the name of the parameter. * ``type``: the canonical type of the parameter (more below). * ``components``: used for tuple types (more below). * ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment. diff --git a/docs/assembly.rst b/docs/assembly.rst index 5015a18bb..119242aea 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -47,7 +47,7 @@ usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inli these curly braces, you can use the following (see the later sections for more details): - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - - opcodes in functional style, e.g. ``add(1, mlod(0))`` + - opcodes in functional style, e.g. ``add(1, mload(0))`` - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of empty (0) is assigned) - identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)`` - assignments, e.g. ``x := add(y, 3)`` @@ -163,7 +163,8 @@ If an opcode takes arguments (always from the top of the stack), they are given Note that the order of arguments can be seen to be reversed in non-functional style (explained below). Opcodes marked with ``-`` do not push an item onto the stack (do not return a result), those marked with ``*`` are special and all others push exactly one item onto the stack (their "return value"). -Opcodes marked with ``F``, ``H``, ``B`` or ``C`` are present since Frontier, Homestead, Byzantium or Constantinople, respectively. +Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, +Byzantium, Constantinople or Istanbul, respectively. In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to but not including position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``. @@ -259,6 +260,8 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+-----+---+-----------------------------------------------------------------+ | balance(a) | | F | wei balance at address a | +-------------------------+-----+---+-----------------------------------------------------------------+ +| selfbalance() | | I | equivalent to balance(address()), but cheaper | ++-------------------------+-----+---+-----------------------------------------------------------------+ | caller | | F | call sender (excluding ``delegatecall``) | +-------------------------+-----+---+-----------------------------------------------------------------+ | callvalue | | F | wei sent together with the current call | @@ -325,6 +328,8 @@ In the grammar, opcodes are represented as pre-defined identifiers. | log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | | t4) | | | | +-------------------------+-----+---+-----------------------------------------------------------------+ +| chainid | | I | ID of the executing chain (EIP 1344) | ++-------------------------+-----+---+-----------------------------------------------------------------+ | origin | | F | transaction sender | +-------------------------+-----+---+-----------------------------------------------------------------+ | gasprice | | F | gas price of the transaction | diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index cd615fbc4..d79aca112 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -750,6 +750,10 @@ "bugs": [], "released": "2019-08-12" }, + "0.5.12": { + "bugs": [], + "released": "2019-10-01" + }, "0.5.2": { "bugs": [ "SignedArrayStorageCopy", diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index 5f15e3893..494f22dd4 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -41,15 +41,11 @@ become the new richest. mostSent = msg.value; } - function becomeRichest() public payable returns (bool) { - if (msg.value > mostSent) { - pendingWithdrawals[richest] += msg.value; - richest = msg.sender; - mostSent = msg.value; - return true; - } else { - return false; - } + function becomeRichest() public payable { + require(msg.value > mostSent, "Not enough money sent."); + pendingWithdrawals[richest] += msg.value; + richest = msg.sender; + mostSent = msg.value; } function withdraw() public { @@ -76,16 +72,12 @@ This is as opposed to the more intuitive sending pattern: mostSent = msg.value; } - function becomeRichest() public payable returns (bool) { - if (msg.value > mostSent) { - // This line can cause problems (explained below). - richest.transfer(msg.value); - richest = msg.sender; - mostSent = msg.value; - return true; - } else { - return false; - } + function becomeRichest() public payable { + require(msg.value > mostSent, "Not enough money sent."); + // This line can cause problems (explained below). + richest.transfer(msg.value); + richest = msg.sender; + mostSent = msg.value; } } diff --git a/docs/contracts/events.rst b/docs/contracts/events.rst index b1651e234..c780da8a9 100644 --- a/docs/contracts/events.rst +++ b/docs/contracts/events.rst @@ -132,7 +132,7 @@ Low-Level Interface to Logs It is also possible to access the low-level interface to the logging mechanism via the functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4``. -``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first +Each function ``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first argument will be used for the data part of the log and the others as topics. The event call above can be performed in the same way as diff --git a/docs/contributing.rst b/docs/contributing.rst index 4dfd18610..a78d2e5d7 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -88,12 +88,13 @@ 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. -Some tests require the ``libevmone.so`` library, others require ``libz3``. +Some tests require the ``evmone`` library, others require ``libz3``. -The test system will automatically try to discover the location of ``libevmone.so`` -starting from the current directory. If it does not find it, the relevant tests +The test system will automatically try to discover the location of the ``evmone`` library +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: diff --git a/docs/grammar.txt b/docs/grammar.txt index 3d2c9bc37..cf0ddd104 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -171,7 +171,7 @@ AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpres AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression AssemblyExpression = AssemblyFunctionCall | Identifier | Literal AssemblyIf = 'if' AssemblyExpression AssemblyBlock -AssemblySwitch = 'switch' AssemblyExpression ( Case+ AssemblyDefault? | AssemblyDefault ) +AssemblySwitch = 'switch' AssemblyExpression ( AssemblyCase+ AssemblyDefault? | AssemblyDefault ) AssemblyCase = 'case' Literal AssemblyBlock AssemblyDefault = 'default' AssemblyBlock AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 2d8f257d4..ade678087 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -184,7 +184,7 @@ The following are dependencies for all builds of Solidity: +-----------------------------------+-------------------------------------------------------+ | Software | Notes | +===================================+=======================================================+ -| `CMake`_ (version 3.5+) | Cross-platform build file generator. | +| `CMake`_ (version 3.9+) | Cross-platform build file generator. | +-----------------------------------+-------------------------------------------------------+ | `Boost`_ (version 1.65+) | C++ libraries. | +-----------------------------------+-------------------------------------------------------+ diff --git a/docs/metadata.rst b/docs/metadata.rst index 185834122..0250a06d1 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -124,7 +124,7 @@ Encoding of the Metadata Hash in the Bytecode ============================================= Because we might support other ways to retrieve the metadata file in the future, -the mapping ``{"bzzr0": , "solc": }`` is stored +the mapping ``{"bzzr1": , "solc": }`` is stored `CBOR `_-encoded. Since the mapping might contain more keys (see below) and the beginning of that encoding is not easy to find, its length is added in a two-byte big-endian @@ -132,7 +132,7 @@ encoding. The current version of the Solidity compiler usually adds the followin to the end of the deployed bytecode:: 0xa2 - 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> + 0x65 'b' 'z' 'z' 'r' '1' 0x58 0x20 <32 bytes swarm hash> 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> 0x00 0x32 @@ -150,9 +150,9 @@ will instead use a complete version string including commit hash and build date. are used, the mapping will also contain ``"experimental": true``. .. note:: - The compiler currently uses the "swarm version 0" hash of the metadata, + The compiler currently uses the "swarm version 1" hash of the metadata, but this might change in the future, so do not rely on this sequence - to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '0'``. We might also + to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also add additional data to this CBOR structure, so the best option is to use a proper CBOR parser. diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 989a6d830..3f18bf3b5 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -254,6 +254,61 @@ if you want all overflows to cause a revert. Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also help you check if values are what you expect. +.. _clearing-mappings: + +Clearing Mappings +================= + +The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure that +does not keep track of the keys that were assigned a non-zero value. +Because of that, cleaning a mapping without extra information about the written +keys is not possible. +If a ``mapping`` is used as the base type of a dynamic storage array, deleting +or popping the array will have no effect over the ``mapping`` elements. +The same happens, for example, if a ``mapping`` is used as the type of a member +field of a ``struct`` that is the base type of a dynamic storage array. +The ``mapping`` is also ignored in assignments of structs or arrays containing +a ``mapping``. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Map { + mapping (uint => uint)[] array; + + function allocate(uint _newMaps) public { + array.length += _newMaps; + } + + function writeMap(uint _map, uint _key, uint _value) public { + array[_map][_key] = _value; + } + + function readMap(uint _map, uint _key) public view returns (uint) { + return array[_map][_key]; + } + + function eraseMaps() public { + delete array; + } + } + +Consider the example above and the following sequence of calls: ``allocate(10)``, +``writeMap(4, 128, 256)``. +At this point, calling ``readMap(4, 128)`` returns 256. +If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but +since its ``mapping`` elements cannot be zeroed, their information stays alive +in the contract's storage. +After deleting ``array``, calling ``allocate(5)`` allows us to access +``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without +another call to ``writeMap``. + +If your ``mapping`` information must be deleted, consider using a library similar to +`iterable mapping `_, +allowing you to traverse the keys and delete their values in the appropriate +``mapping``. + Minor Details ============= diff --git a/docs/style-guide.rst b/docs/style-guide.rst index fb38f2d93..aa2c1c305 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -407,7 +407,7 @@ should: * open on the same line as the declaration * close on their own line at the same indentation level as the beginning of the declaration. -* The opening brace should be proceeded by a single space. +* The opening brace should be preceded by a single space. Yes:: @@ -953,7 +953,7 @@ naming styles. * ``mixedCase`` (differs from CapitalizedWords by initial lowercase character!) * ``Capitalized_Words_With_Underscores`` -.. note:: When using initialisms in CapWords, capitalize all the letters of the initialisms. Thus HTTPServerError is better than HttpServerError. When using initialisms is mixedCase, capitalize all the letters of the initialisms, except keep the first one lower case if it is the beginning of the name. Thus xmlHTTPRequest is better than XMLHTTPRequest. +.. note:: When using initialisms in CapWords, capitalize all the letters of the initialisms. Thus HTTPServerError is better than HttpServerError. When using initialisms in mixedCase, capitalize all the letters of the initialisms, except keep the first one lower case if it is the beginning of the name. Thus xmlHTTPRequest is better than XMLHTTPRequest. Names to Avoid @@ -1139,6 +1139,6 @@ added looks like the one below:: } } -It is recommended that Solidity contracts are fully annontated using `NatSpec `_ for all public interfaces (everything in the ABI). +It is recommended that Solidity contracts are fully annotated using `NatSpec `_ for all public interfaces (everything in the ABI). -Please see the section about `NatSpec `_ for a detailed explanation. \ No newline at end of file +Please see the section about `NatSpec `_ for a detailed explanation. diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index afee61202..a7d607786 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -17,7 +17,8 @@ byte-representation is all zeros, a type's :ref:`default value `. mapping, only its ``keccak256`` hash is used to look up the value. Because of this, mappings do not have a length or a concept of a key or -value being set. +value being set, and therefore cannot be erased without extra information +regarding the assigned keys (see :ref:`clearing-mappings`). Mappings can only have a data location of ``storage`` and thus are allowed for state variables, as storage reference types diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 0b9d6ad90..053160701 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -110,7 +110,7 @@ Modulo The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a`` by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo -results in the same sign as its left operand (or zero) and ``a % n == -(abs(a) % n)`` holds for negative ``a``: +results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`` holds for negative ``a``: * ``int256(5) % int256(2) == int256(1)`` * ``int256(5) % int256(-2) == int256(1)`` diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index 8754da9f2..12df74e84 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -122,6 +122,9 @@ at each version. Backward compatibility is not guaranteed between each version. - Shifting operators use shifting opcodes and thus need less gas. - ``petersburg`` (**default**) - The compiler behaves the same way as with constantinople. +- ``istanbul`` + - Opcodes ``chainid`` and ``selfbalance`` are available in assembly. +- ``berlin`` (**experimental**) .. _compiler-api: @@ -229,7 +232,7 @@ Input Description }, // Version of the EVM to compile for. // Affects type checking and code generation. Can be homestead, - // tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg + // tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin "evmVersion": "byzantium", // Metadata settings (optional) "metadata": { @@ -363,7 +366,7 @@ Output Description // If the language used has no contract names, this field should equal to an empty string. "ContractName": { // The Ethereum Contract ABI. If empty, it is represented as an empty array. - // See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // See https://solidity.readthedocs.io/en/develop/abi-spec.html "abi": [], // See the Metadata Output documentation (serialised JSON string) "metadata": "{...}", diff --git a/docs/yul.rst b/docs/yul.rst index a40bd906e..ae8eb1f9e 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -244,14 +244,14 @@ We will use a destructuring notation for the AST nodes. G1, L1, mode E(G, L, FunctionDefinition) = G, L, regular - E(G, L, : VariableDeclaration) = - E(G, L, : Assignment) - E(G, L, : VariableDeclaration) = - let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n + E(G, L, : VariableDeclaration) = + E(G, L, : Assignment) + E(G, L, : VariableDeclaration) = + let L1 be a copy of L where L1[$var_i] = 0 for i = 1, ..., n G, L1, regular - E(G, L, : Assignment) = + E(G, L, : Assignment) = let G1, L1, v1, ..., vn = E(G, L, rhs) - let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n + let L2 be a copy of L1 where L2[$var_i] = vi for i = 1, ..., n G, L2, regular E(G, L, : ForLoop) = if n >= 1: diff --git a/libdevcore/Algorithms.h b/libdevcore/Algorithms.h index 34a4dfe5a..b39939928 100644 --- a/libdevcore/Algorithms.h +++ b/libdevcore/Algorithms.h @@ -78,17 +78,18 @@ private: /** * Generic breadth first search. * + * Note that V needs to be a comparable value type. If it is not, use a pointer type, + * but note that this might lead to non-deterministic traversal. + * * Example: Gather all (recursive) children in a graph starting at (and including) ``root``: * * Node const* root = ...; - * std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const& _node, auto&& _addChild) { + * std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const* _node, auto&& _addChild) { * // Potentially process ``_node``. - * for (Node const& _child: _node.children()) + * for (Node const& _child: _node->children()) * // Potentially filter the children to be visited. - * _addChild(_child); + * _addChild(&_child); * }).visited; - * - * Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers). */ template struct BreadthFirstSearch @@ -102,20 +103,20 @@ struct BreadthFirstSearch { while (!verticesToTraverse.empty()) { - V const* v = *verticesToTraverse.begin(); + V v = *verticesToTraverse.begin(); verticesToTraverse.erase(verticesToTraverse.begin()); visited.insert(v); - _forEachChild(*v, [this](V const& _vertex) { - if (!visited.count(&_vertex)) - verticesToTraverse.insert(&_vertex); + _forEachChild(v, [this](V _vertex) { + if (!visited.count(_vertex)) + verticesToTraverse.emplace(std::move(_vertex)); }); } return *this; } - std::set verticesToTraverse; - std::set visited{}; + std::set verticesToTraverse; + std::set visited{}; }; } diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index a092e3140..8bc3bb525 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -34,6 +34,6 @@ set(sources ) add_library(devcore ${sources}) -target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system Threads::Threads) +target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system) target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") add_dependencies(devcore solidity_BuildInfo.h) diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 1a5a627cc..bc71c88c4 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -98,6 +98,14 @@ inline std::set operator+(std::set&& _a, U&& _b) ret += std::forward(_b); return ret; } +/// Remove one set from another one. +template +inline std::set& operator-=(std::set& _a, std::set const& _b) +{ + for (auto const& x: _b) + _a.erase(x); + return _a; +} namespace dev { diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 494b2ed93..183cba930 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -596,12 +596,14 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); break; case PushSub: + assertThrow(i.data() <= size_t(-1), AssemblyException, ""); ret.bytecode.push_back(dataRefPush); subRef.insert(make_pair(size_t(i.data()), ret.bytecode.size())); ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); break; case PushSubSize: { + assertThrow(i.data() <= size_t(-1), AssemblyException, ""); auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); uint8_t b = max(1, dev::bytesRequired(s)); diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index d98b3efa9..5ff3a905e 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -188,6 +188,12 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::BALANCE: gas = GasCosts::balanceGas(m_evmVersion); break; + case Instruction::CHAINID: + gas = runGas(Instruction::CHAINID); + break; + case Instruction::SELFBALANCE: + gas = runGas(Instruction::SELFBALANCE); + break; default: gas = runGas(_item.instruction()); break; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 6dd9da6b9..cc5561daf 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -81,6 +81,8 @@ std::map const dev::eth::c_instructions = { "NUMBER", Instruction::NUMBER }, { "DIFFICULTY", Instruction::DIFFICULTY }, { "GASLIMIT", Instruction::GASLIMIT }, + { "CHAINID", Instruction::CHAINID }, + { "SELFBALANCE", Instruction::SELFBALANCE }, { "POP", Instruction::POP }, { "MLOAD", Instruction::MLOAD }, { "MSTORE", Instruction::MSTORE }, @@ -225,6 +227,8 @@ static std::map const c_instructionInfo = { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } }, { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } }, { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } }, + { Instruction::CHAINID, { "CHAINID", 0, 0, 1, false, Tier::Base } }, + { Instruction::SELFBALANCE, { "SELFBALANCE", 0, 0, 1, false, Tier::Low } }, { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } }, { Instruction::MLOAD, { "MLOAD", 0, 1, 1, true, Tier::VeryLow } }, { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } }, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 9a0a5d4e3..f2f5879d9 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -90,6 +90,8 @@ enum class Instruction: uint8_t NUMBER, ///< get the block's number DIFFICULTY, ///< get the block's difficulty GASLIMIT, ///< get the block's gas limit + CHAINID, ///< get the config's chainid param + SELFBALANCE, ///< get balance of the current account POP = 0x50, ///< remove item from stack MLOAD, ///< load word from memory diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 70f136d9d..58e773f30 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -259,13 +259,19 @@ std::vector> simplificationRuleListPart5( [=]() { return A.d() >= 256; } }); + // Replace BYTE(A, X), A >= 32 with 0 + rules.push_back({ + {Instruction::BYTE, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 32; } + }); + for (auto const& op: std::vector{ Instruction::ADDRESS, Instruction::CALLER, Instruction::ORIGIN, - Instruction::COINBASE, - Instruction::CREATE, - Instruction::CREATE2 + Instruction::COINBASE }) { u256 const mask = (u256(1) << 160) - 1; @@ -280,6 +286,7 @@ std::vector> simplificationRuleListPart5( false }); } + return rules; } @@ -468,7 +475,8 @@ std::vector> simplificationRuleListPart7( [=]() -> Pattern { return {Instruction::SHL, {Y, X}}; }, - false + // Actually only changes the order, does not remove. + true }); rules.push_back({ // MUL(SHL(X, 1), Y) -> SHL(X, Y) @@ -485,7 +493,8 @@ std::vector> simplificationRuleListPart7( [=]() -> Pattern { return {Instruction::SHR, {Y, X}}; }, - false + // Actually only changes the order, does not remove. + true }); std::function feasibilityFunction = [=]() { @@ -557,8 +566,48 @@ std::vector> simplificationRuleListPart8( return rules; } +template +std::vector> simplificationRuleListPart9( + Pattern, + Pattern, + Pattern, + Pattern W, + Pattern X, + Pattern Y, + Pattern Z +) +{ + std::vector> rules; + + u256 const mask = (u256(1) << 160) - 1; + // CREATE + rules.push_back({ + {Instruction::AND, {{Instruction::CREATE, {W, X, Y}}, mask}}, + [=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; }, + false + }); + rules.push_back({ + {Instruction::AND, {{mask, {Instruction::CREATE, {W, X, Y}}}}}, + [=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; }, + false + }); + // CREATE2 + rules.push_back({ + {Instruction::AND, {{Instruction::CREATE2, {W, X, Y, Z}}, mask}}, + [=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; }, + false + }); + rules.push_back({ + {Instruction::AND, {{mask, {Instruction::CREATE2, {W, X, Y, Z}}}}}, + [=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; }, + false + }); + + return rules; +} + /// @returns a list of simplification rules given certain match placeholders. -/// A, B and C should represent constants, X and Y arbitrary expressions. +/// A, B and C should represent constants, W, X, Y, and Z arbitrary expressions. /// The simplifications should never change the order of evaluation of /// arbitrary operations. template @@ -566,19 +615,22 @@ std::vector> simplificationRuleList( Pattern A, Pattern B, Pattern C, + Pattern W, Pattern X, - Pattern Y + Pattern Y, + Pattern Z ) { std::vector> rules; - rules += simplificationRuleListPart1(A, B, C, X, Y); - rules += simplificationRuleListPart2(A, B, C, X, Y); - rules += simplificationRuleListPart3(A, B, C, X, Y); - rules += simplificationRuleListPart4(A, B, C, X, Y); - rules += simplificationRuleListPart5(A, B, C, X, Y); - rules += simplificationRuleListPart6(A, B, C, X, Y); - rules += simplificationRuleListPart7(A, B, C, X, Y); - rules += simplificationRuleListPart8(A, B, C, X, Y); + rules += simplificationRuleListPart1(A, B, C, W, X); + rules += simplificationRuleListPart2(A, B, C, W, X); + rules += simplificationRuleListPart3(A, B, C, W, X); + rules += simplificationRuleListPart4(A, B, C, W, X); + rules += simplificationRuleListPart5(A, B, C, W, X); + rules += simplificationRuleListPart6(A, B, C, W, X); + rules += simplificationRuleListPart7(A, B, C, W, X); + rules += simplificationRuleListPart8(A, B, C, W, X); + rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z); return rules; } diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 6b6b4611d..8932d4351 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -172,6 +172,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) case Instruction::PC: case Instruction::MSIZE: // depends on previous writes and reads, not only on content case Instruction::BALANCE: // depends on previous calls + case Instruction::SELFBALANCE: // depends on previous calls case Instruction::EXTCODESIZE: case Instruction::EXTCODEHASH: case Instruction::RETURNDATACOPY: // depends on previous calls @@ -194,6 +195,7 @@ bool SemanticInformation::movable(Instruction _instruction) { case Instruction::KECCAK256: case Instruction::BALANCE: + case Instruction::SELFBALANCE: case Instruction::EXTCODESIZE: case Instruction::EXTCODEHASH: case Instruction::RETURNDATASIZE: @@ -265,6 +267,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) switch (_instruction) { case Instruction::ADDRESS: + case Instruction::SELFBALANCE: case Instruction::BALANCE: case Instruction::ORIGIN: case Instruction::CALLER: diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index 9fee79cf4..ca201862a 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -83,15 +83,19 @@ Rules::Rules() Pattern B(Push); Pattern C(Push); // Anything. + Pattern W; Pattern X; Pattern Y; + Pattern Z; A.setMatchGroup(1, m_matchGroups); B.setMatchGroup(2, m_matchGroups); C.setMatchGroup(3, m_matchGroups); - X.setMatchGroup(4, m_matchGroups); - Y.setMatchGroup(5, m_matchGroups); + W.setMatchGroup(4, m_matchGroups); + X.setMatchGroup(5, m_matchGroups); + Y.setMatchGroup(6, m_matchGroups); + Z.setMatchGroup(7, m_matchGroups); - addRules(simplificationRuleList(A, B, C, X, Y)); + addRules(simplificationRuleList(A, B, C, W, X, Y, Z)); assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } diff --git a/liblangutil/Common.h b/liblangutil/Common.h index ed6aa2cbc..a8d935270 100644 --- a/liblangutil/Common.h +++ b/liblangutil/Common.h @@ -30,11 +30,6 @@ inline bool isHexDigit(char c) ('A' <= c && c <= 'F'); } -inline bool isLineTerminator(char c) -{ - return c == '\n'; -} - inline bool isWhiteSpace(char c) { return c == ' ' || c == '\n' || c == '\t' || c == '\r'; diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index eb87d4832..3d546a9e4 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -40,6 +40,10 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const return hasCreate2(); case Instruction::EXTCODEHASH: return hasExtCodeHash(); + case Instruction::CHAINID: + return hasChainID(); + case Instruction::SELFBALANCE: + return hasSelfBalance(); default: return true; } diff --git a/liblangutil/EVMVersion.h b/liblangutil/EVMVersion.h index f80a18708..3c67e304d 100644 --- a/liblangutil/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -48,10 +48,12 @@ public: static EVMVersion byzantium() { return {Version::Byzantium}; } static EVMVersion constantinople() { return {Version::Constantinople}; } static EVMVersion petersburg() { return {Version::Petersburg}; } + static EVMVersion istanbul() { return {Version::Istanbul}; } + static EVMVersion berlin() { return {Version::Berlin}; } static boost::optional fromString(std::string const& _version) { - for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg()}) + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg(), istanbul(), berlin()}) if (_version == v.name()) return v; return {}; @@ -70,6 +72,8 @@ public: case Version::Byzantium: return "byzantium"; case Version::Constantinople: return "constantinople"; case Version::Petersburg: return "petersburg"; + case Version::Istanbul: return "istanbul"; + case Version::Berlin: return "berlin"; } return "INVALID"; } @@ -80,6 +84,8 @@ public: bool hasBitwiseShifting() const { return *this >= constantinople(); } bool hasCreate2() const { return *this >= constantinople(); } bool hasExtCodeHash() const { return *this >= constantinople(); } + bool hasChainID() const { return *this >= istanbul(); } + bool hasSelfBalance() const { return *this >= istanbul(); } bool hasOpcode(dev::eth::Instruction _opcode) const; @@ -88,7 +94,7 @@ public: bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } private: - enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg }; + enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg, Istanbul, Berlin }; EVMVersion(Version _version): m_version(_version) {} diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index a317c79b6..131462289 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -280,6 +280,29 @@ Token Scanner::skipSingleLineComment() return Token::Whitespace; } +bool Scanner::atEndOfLine() const +{ + return m_char == '\n' || m_char == '\r'; +} + +bool Scanner::tryScanEndOfLine() +{ + if (m_char == '\n') + { + advance(); + return true; + } + + if (m_char == '\r') + { + if (advance() && m_char == '\n') + advance(); + return true; + } + + return false; +} + Token Scanner::scanSingleLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); @@ -289,7 +312,7 @@ Token Scanner::scanSingleLineDocComment() while (!isSourcePastEndOfInput()) { - if (isLineTerminator(m_char)) + if (tryScanEndOfLine()) { // check if next line is also a documentation comment skipWhitespace(); @@ -303,7 +326,6 @@ Token Scanner::scanSingleLineDocComment() } else break; // next line is not a documentation comment, we are done - } else if (isUnicodeLinebreak()) // Any line terminator that is not '\n' is considered to end the @@ -343,13 +365,13 @@ Token Scanner::scanMultiLineDocComment() bool endFound = false; bool charsAdded = false; - while (isWhiteSpace(m_char) && !isLineTerminator(m_char)) + while (isWhiteSpace(m_char) && !atEndOfLine()) advance(); while (!isSourcePastEndOfInput()) { //handle newlines in multline comments - if (isLineTerminator(m_char)) + if (atEndOfLine()) { skipWhitespace(); if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '*') @@ -664,10 +686,12 @@ void Scanner::scanToken() bool Scanner::scanEscape() { char c = m_char; - advance(); + // Skip escaped newlines. - if (isLineTerminator(c)) + if (tryScanEndOfLine()) return true; + advance(); + switch (c) { case '\'': // fall through diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 555575129..3914b85ac 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -219,6 +219,12 @@ private: Token skipSingleLineComment(); Token skipMultiLineComment(); + /// Tests if current source position is CR, LF or CRLF. + bool atEndOfLine() const; + + /// Tries to consume CR, LF or CRLF line terminators and returns success or failure. + bool tryScanEndOfLine(); + void scanDecimalDigits(); Token scanNumber(char _charSeen = 0); std::tuple scanIdentifierOrKeyword(); diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index ccc2e8a3c..e12a3b1b6 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -142,7 +142,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod ssl, string("This variable is of storage pointer type and can be ") + (variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") + - " without prior assignment." + " without prior assignment, which would lead to undefined behaviour." ); } } @@ -151,22 +151,22 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const { // collect all nodes reachable from the entry point - std::set reachable = BreadthFirstSearch{{_entry}}.run( - [](CFGNode const& _node, auto&& _addChild) { - for (CFGNode const* exit: _node.exits) - _addChild(*exit); + std::set reachable = BreadthFirstSearch{{_entry}}.run( + [](CFGNode const* _node, auto&& _addChild) { + for (CFGNode const* exit: _node->exits) + _addChild(exit); } ).visited; // traverse all paths backwards from exit and revert // and extract (valid) source locations of unreachable nodes into sorted set std::set unreachable; - BreadthFirstSearch{{_exit, _revert}}.run( - [&](CFGNode const& _node, auto&& _addChild) { - if (!reachable.count(&_node) && !_node.location.isEmpty()) - unreachable.insert(_node.location); - for (CFGNode const* entry: _node.entries) - _addChild(*entry); + BreadthFirstSearch{{_exit, _revert}}.run( + [&](CFGNode const* _node, auto&& _addChild) { + if (!reachable.count(_node) && !_node->location.isEmpty()) + unreachable.insert(_node->location); + for (CFGNode const* entry: _node->entries) + _addChild(entry); } ); diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 727dc5f35..e9792ddf2 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -91,13 +91,13 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, mapsymbolAliases().empty()) for (auto const& alias: imp->symbolAliases()) { - auto declarations = scope->second->resolveName(alias.first->name(), false); + auto declarations = scope->second->resolveName(alias.symbol->name(), false); if (declarations.empty()) { m_errorReporter.declarationError( imp->location(), "Declaration \"" + - alias.first->name() + + alias.symbol->name() + "\" not found in \"" + path + "\" (referenced as \"" + @@ -109,7 +109,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, maplocation(), true, false, m_errorReporter + target, *declaration, alias.alias.get(), &alias.location, true, false, m_errorReporter )) error = true; } @@ -523,7 +523,7 @@ bool DeclarationRegistrationHelper::registerDeclaration( { if (dynamic_cast(shadowedDeclaration)) _errorReporter.warning( - _declaration.location(), + *_errorLocation, "This declaration shadows a builtin symbol." ); else diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 5402d7ff4..7bd917d0d 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -262,10 +262,7 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly) if (!m_useYulOptimizer) return false; - if (yul::SideEffectsCollector( - _inlineAssembly.dialect(), - _inlineAssembly.operations() - ).containsMSize()) + if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations())) m_errorReporter.syntaxError( _inlineAssembly.location(), "The msize instruction cannot be used when the Yul optimizer is activated because " diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index c1d42409d..b5abe59f4 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2515,8 +2515,47 @@ void TypeChecker::requireLValue(Expression const& _expression) _expression.annotation().lValueRequested = true; _expression.accept(*this); - if (_expression.annotation().isConstant) - m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable."); - else if (!_expression.annotation().isLValue) - m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue."); + if (_expression.annotation().isLValue) + return; + + return m_errorReporter.typeError(_expression.location(), [&]() { + if (_expression.annotation().isConstant) + return "Cannot assign to a constant variable."; + + if (auto indexAccess = dynamic_cast(&_expression)) + { + if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes) + return "Single bytes in fixed bytes arrays cannot be modified."; + else if (auto arrayType = dynamic_cast(type(indexAccess->baseExpression()))) + if (arrayType->dataStoredIn(DataLocation::CallData)) + return "Calldata arrays are read-only."; + } + + if (auto memberAccess = dynamic_cast(&_expression)) + { + if (auto structType = dynamic_cast(type(memberAccess->expression()))) + { + if (structType->dataStoredIn(DataLocation::CallData)) + return "Calldata structs are read-only."; + } + else if (auto arrayType = dynamic_cast(type(memberAccess->expression()))) + if (memberAccess->memberName() == "length") + switch (arrayType->location()) + { + case DataLocation::Memory: + return "Memory arrays cannot be resized."; + case DataLocation::CallData: + return "Calldata arrays cannot be resized."; + case DataLocation::Storage: + break; + } + } + + if (auto identifier = dynamic_cast(&_expression)) + if (auto varDecl = dynamic_cast(identifier->annotation().referencedDeclaration)) + if (varDecl->isExternalCallableParameter() && dynamic_cast(identifier->annotation().type)) + return "External function arguments of reference type are read-only."; + + return "Expression has to be an lvalue."; + }()); } diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 75166d654..60703f76e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -432,8 +432,13 @@ string Scopable::sourceUnitName() const bool VariableDeclaration::isLValue() const { - // External function parameters and constant declared variables are Read-Only - return !isExternalCallableParameter() && !m_isConstant; + // Constant declared variables are Read-Only + if (m_isConstant) + return false; + // External function arguments of reference type are Read-Only + if (isExternalCallableParameter() && dynamic_cast(type())) + return false; + return true; } bool VariableDeclaration::isLocalVariable() const diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index b84ad2c19..4d03bf4ec 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -280,22 +280,31 @@ private: class ImportDirective: public Declaration { public: + struct SymbolAlias + { + ASTPointer symbol; + ASTPointer alias; + SourceLocation location; + }; + + using SymbolAliasList = std::vector; + ImportDirective( SourceLocation const& _location, ASTPointer const& _path, ASTPointer const& _unitAlias, - std::vector, ASTPointer>>&& _symbolAliases + SymbolAliasList _symbolAliases ): Declaration(_location, _unitAlias), m_path(_path), - m_symbolAliases(_symbolAliases) + m_symbolAliases(move(_symbolAliases)) { } void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; ASTString const& path() const { return *m_path; } - std::vector, ASTPointer>> const& symbolAliases() const + SymbolAliasList const& symbolAliases() const { return m_symbolAliases; } @@ -306,9 +315,9 @@ public: private: ASTPointer m_path; /// The aliases for the specific symbols to import. If non-empty import the specific symbols. - /// If the second component is empty, import the identifier unchanged. + /// If the `alias` component is empty, import the identifier unchanged. /// If both m_unitAlias and m_symbolAlias are empty, import all symbols into the current scope. - std::vector, ASTPointer>> m_symbolAliases; + SymbolAliasList m_symbolAliases; }; /** diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index d791a8a06..5424ee5bc 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -27,6 +27,9 @@ #include #include +#include +#include + using namespace std; using namespace langutil; @@ -240,9 +243,9 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) for (auto const& symbolAlias: _node.symbolAliases()) { Json::Value tuple(Json::objectValue); - solAssert(symbolAlias.first, ""); - tuple["foreign"] = nodeId(*symbolAlias.first); - tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue; + solAssert(symbolAlias.symbol, ""); + tuple["foreign"] = nodeId(*symbolAlias.symbol); + tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue; symbolAliases.append(tuple); } attributes.emplace_back("symbolAliases", std::move(symbolAliases)); @@ -259,7 +262,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), - make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)), + make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)), make_pair("nodes", toJson(_node.subNodes())), make_pair("scope", idOrNull(_node.scope())) }); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index abc84f813..7cd1dde39 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -29,6 +29,8 @@ #include #include #include +#include +#include namespace langutil { @@ -148,15 +150,23 @@ private: return _node.id(); } template - static Json::Value getContainerIds(Container const& container) + static Json::Value getContainerIds(Container const& _container, bool _order = false) { - Json::Value tmp(Json::arrayValue); - for (auto const& element: container) + std::vector tmp; + + for (auto const& element: _container) { solAssert(element, ""); - tmp.append(nodeId(*element)); + tmp.push_back(nodeId(*element)); } - return tmp; + if (_order) + std::sort(tmp.begin(), tmp.end()); + Json::Value json(Json::arrayValue); + + for (int val: tmp) + json.append(val); + + return json; } static Json::Value typePointerToJson(TypePointer _tp, bool _short = false); static Json::Value typePointerToJson(boost::optional const& _tps); diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 556347980..25d31a857 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2050,6 +2050,8 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const bool StructType::isDynamicallyEncoded() const { + if (recursive()) + return true; solAssert(interfaceType(false).get(), ""); for (auto t: memoryMemberTypes()) { diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index b1294bcef..81d1fb991 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -896,11 +896,16 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const // Stack: ArrayReference oldLength m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; // Stack ArrayReference newLength - m_context << Instruction::DUP2 << Instruction::DUP2; - // Stack ArrayReference newLength ArrayReference newLength; - accessIndex(_type, false); - // Stack: ArrayReference newLength storage_slot byte_offset - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); + + if (_type.baseType()->category() != Type::Category::Mapping) + { + m_context << Instruction::DUP2 << Instruction::DUP2; + // Stack ArrayReference newLength ArrayReference newLength; + accessIndex(_type, false); + // Stack: ArrayReference newLength storage_slot byte_offset + StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + // Stack: ArrayReference newLength m_context << Instruction::SWAP1 << Instruction::SSTORE; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index c34684840..2f8bf9455 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -78,8 +78,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl) { solAssert(_varDecl.isConstant(), ""); - _varDecl.value()->accept(*this); - utils().convertType(*_varDecl.value()->annotation().type, *_varDecl.annotation().type); + acceptAndConvert(*_varDecl.value(), *_varDecl.annotation().type); // append return m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1); @@ -225,14 +224,12 @@ bool ExpressionCompiler::visit(Conditional const& _condition) CompilerContext::LocationSetter locationSetter(m_context, _condition); _condition.condition().accept(*this); eth::AssemblyItem trueTag = m_context.appendConditionalJump(); - _condition.falseExpression().accept(*this); - utils().convertType(*_condition.falseExpression().annotation().type, *_condition.annotation().type); + acceptAndConvert(_condition.falseExpression(), *_condition.annotation().type); eth::AssemblyItem endTag = m_context.appendJumpToNew(); m_context << trueTag; int offset = _condition.annotation().type->sizeOnStack(); m_context.adjustStackOffset(-offset); - _condition.trueExpression().accept(*this); - utils().convertType(*_condition.trueExpression().annotation().type, *_condition.annotation().type); + acceptAndConvert(_condition.trueExpression(), *_condition.annotation().type); m_context << endTag; return false; } @@ -322,8 +319,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) for (auto const& component: _tuple.components()) { - component->accept(*this); - utils().convertType(*component->annotation().type, *arrayType.baseType(), true); + acceptAndConvert(*component, *arrayType.baseType(), true); utils().storeInMemoryDynamic(*arrayType.baseType(), true); } @@ -451,17 +447,13 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool swap = m_optimiseOrderLiterals && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { - leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); - rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); + acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); + acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); } else { - rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); - leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); + acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); + acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); } if (TokenTraits::isShiftOp(c_op)) // shift only cares about the signedness of both sides @@ -483,9 +475,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { solAssert(_functionCall.arguments().size() == 1, ""); solAssert(_functionCall.names().empty(), ""); - Expression const& firstArgument = *_functionCall.arguments().front(); - firstArgument.accept(*this); - utils().convertType(*firstArgument.annotation().type, *_functionCall.annotation().type); + acceptAndConvert(*_functionCall.arguments().front(), *_functionCall.annotation().type); return false; } @@ -531,8 +521,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) for (unsigned i = 0; i < arguments.size(); ++i) { - arguments[i]->accept(*this); - utils().convertType(*arguments[i]->annotation().type, *functionType->parameterTypes()[i]); + acceptAndConvert(*arguments[i], *functionType->parameterTypes()[i]); utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]); } m_context << Instruction::POP; @@ -552,10 +541,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) eth::AssemblyItem returnLabel = m_context.pushNewTag(); for (unsigned i = 0; i < arguments.size(); ++i) - { - arguments[i]->accept(*this); - utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]); - } + acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); { bool shortcutTaken = false; @@ -647,8 +633,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *TypeProvider::uint256(), true); + acceptAndConvert(*arguments.front(), *TypeProvider::uint256(), true); // Note that function is not the original function, but the ".gas" function. // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); @@ -673,11 +658,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. m_context << u256(eth::GasCosts::callStipend); - arguments.front()->accept(*this); - utils().convertType( - *arguments.front()->annotation().type, - *function.parameterTypes().front(), true - ); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); // gas <- gas * !value m_context << Instruction::SWAP1 << Instruction::DUP2; m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; @@ -705,8 +686,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } break; case FunctionType::Kind::Selfdestruct: - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; case FunctionType::Kind::Revert: @@ -754,10 +734,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0); for (unsigned arg = logNumber; arg > 0; --arg) - { - arguments[arg]->accept(*this); - utils().convertType(*arguments[arg]->annotation().type, *function.parameterTypes()[arg], true); - } + acceptAndConvert(*arguments[arg], *function.parameterTypes()[arg], true); arguments.front()->accept(*this); utils().fetchFreeMemoryPointer(); solAssert(function.parameterTypes().front()->isValueType(), ""); @@ -827,23 +804,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::BlockHash: { - arguments[0]->accept(*this); - utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); + acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true); m_context << Instruction::BLOCKHASH; break; } case FunctionType::Kind::AddMod: case FunctionType::Kind::MulMod: { - arguments[2]->accept(*this); - utils().convertType(*arguments[2]->annotation().type, *TypeProvider::uint256()); + acceptAndConvert(*arguments[2], *TypeProvider::uint256()); m_context << Instruction::DUP1 << Instruction::ISZERO; m_context.appendConditionalInvalid(); for (unsigned i = 1; i < 3; i ++) - { - arguments[2 - i]->accept(*this); - utils().convertType(*arguments[2 - i]->annotation().type, *TypeProvider::uint256()); - } + acceptAndConvert(*arguments[2 - i], *TypeProvider::uint256()); if (function.kind() == FunctionType::Kind::AddMod) m_context << Instruction::ADDMOD; else @@ -927,8 +899,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(arguments.size() == 1, ""); // Fetch requested length. - arguments[0]->accept(*this); - utils().convertType(*arguments[0]->annotation().type, *TypeProvider::uint256()); + acceptAndConvert(*arguments[0], *TypeProvider::uint256()); // Stack: requested_length utils().fetchFreeMemoryPointer(); @@ -967,8 +938,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Assert: case FunctionType::Kind::Require: { - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); if (arguments.size() > 1) { // Users probably expect the second argument to be evaluated @@ -1149,12 +1119,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (auto funType = dynamic_cast(_memberAccess.annotation().type)) if (funType->bound()) { - _memberAccess.expression().accept(*this); - utils().convertType( - *_memberAccess.expression().annotation().type, - *funType->selfType(), - true - ); + acceptAndConvert(_memberAccess.expression(), *funType->selfType(), true); if (funType->kind() == FunctionType::Kind::Internal) { FunctionDefinition const& funDef = dynamic_cast(funType->declaration()); @@ -1568,8 +1533,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) ArrayType const& arrayType = dynamic_cast(baseType); solAssert(_indexAccess.indexExpression(), "Index expression expected."); - _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true); + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); // stack layout: [] switch (arrayType.location()) { @@ -1597,8 +1561,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) FixedBytesType const& fixedBytesType = dynamic_cast(baseType); solAssert(_indexAccess.indexExpression(), "Index expression expected."); - _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true); + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); // stack layout: // check out-of-bounds access m_context << u256(fixedBytesType.numBytes()); @@ -2207,8 +2170,7 @@ void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types."); - _expression.accept(*this); - utils().convertType(*_expression.annotation().type, _expectedType, true); + acceptAndConvert(_expression, _expectedType, true); utils().storeInMemoryDynamic(_expectedType); } @@ -2217,10 +2179,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex if (!_variable.isConstant()) setLValueFromDeclaration(_variable, _expression); else - { - _variable.value()->accept(*this); - utils().convertType(*_variable.value()->annotation().type, *_variable.annotation().type); - } + acceptAndConvert(*_variable.value(), *_variable.annotation().type); } void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression) @@ -2252,6 +2211,12 @@ bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op) return false; } +void ExpressionCompiler::acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded) +{ + _expression.accept(*this); + utils().convertType(*_expression.annotation().type, _type, _cleanupNeeded); +} + CompilerUtils ExpressionCompiler::utils() { return CompilerUtils(m_context); diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index ca4e05b5f..645d67067 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -121,6 +121,8 @@ private: /// operation. static bool cleanupNeededForOp(Type::Category _type, Token _op); + void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false); + /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp index e3a740b53..867be77dd 100644 --- a/libsolidity/formal/CHC.cpp +++ b/libsolidity/formal/CHC.cpp @@ -74,8 +74,10 @@ bool CHC::visit(ContractDefinition const& _contract) else m_stateSorts.push_back(smt::smtSort(*var->type())); + clearIndices(); + string interfaceName = "interface_" + _contract.name() + "_" + to_string(_contract.id()); - m_interfacePredicate = createBlock(interfaceSort(), interfaceName); + m_interfacePredicate = createSymbolicBlock(interfaceSort(), interfaceName); // TODO create static instances for Bool/Int sorts in SolverInterface. auto boolSort = make_shared(smt::Kind::Bool); @@ -83,7 +85,7 @@ bool CHC::visit(ContractDefinition const& _contract) vector(), boolSort ); - m_errorPredicate = createBlock(errorFunctionSort, "error"); + m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error"); // If the contract has a constructor it is handled as a function. // Otherwise we zero-initialize all state vars. @@ -91,7 +93,9 @@ bool CHC::visit(ContractDefinition const& _contract) if (!_contract.constructor()) { string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id()); - m_constructorPredicate = createBlock(constructorSort(), constructorName); + m_constructorPredicate = createSymbolicBlock(constructorSort(), constructorName); + smt::Expression constructorPred = (*m_constructorPredicate)({}); + addRule(constructorPred, constructorName); for (auto const& var: m_stateVariables) { @@ -101,14 +105,7 @@ bool CHC::visit(ContractDefinition const& _contract) m_context.setZeroValue(*symbVar); } - smt::Expression constructorAppl = (*m_constructorPredicate)({}); - m_interface->addRule(constructorAppl, constructorName); - - smt::Expression constructorInterface = smt::Expression::implies( - constructorAppl && m_context.assertions(), - interface() - ); - m_interface->addRule(constructorInterface, constructorName + "_to_" + interfaceName); + connectBlocks(constructorPred, interface()); } return true; @@ -119,10 +116,13 @@ void CHC::endVisit(ContractDefinition const& _contract) if (!shouldVisit(_contract)) return; - auto errorAppl = (*m_errorPredicate)({}); - for (auto const& target: m_verificationTargets) + for (unsigned i = 0; i < m_verificationTargets.size(); ++i) + { + auto const& target = m_verificationTargets.at(i); + auto errorAppl = error(i + 1); if (query(errorAppl, target->location())) m_safeAssertions.insert(target); + } SMTEncoder::endVisit(_contract); } @@ -135,6 +135,30 @@ bool CHC::visit(FunctionDefinition const& _function) solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented"); m_currentFunction = &_function; + initFunction(_function); + + // Store the constraints related to variable initialization. + smt::Expression const& initAssertions = m_context.assertions(); + m_context.pushSolver(); + + solAssert(m_functionBlocks == 0, ""); + + createBlock(m_currentFunction); + createBlock(&m_currentFunction->body(), "block_"); + + auto functionPred = predicate(m_currentFunction); + auto bodyPred = predicate(&m_currentFunction->body()); + + connectBlocks(interface(), functionPred); + connectBlocks(functionPred, bodyPred); + + m_context.popSolver(); + + pushBlock(&m_currentFunction->body()); + + // We need to re-add the constraints that were created for initialization of variables. + m_context.addAssertion(initAssertions); + SMTEncoder::visit(*m_currentFunction); return false; @@ -146,7 +170,23 @@ void CHC::endVisit(FunctionDefinition const& _function) return; solAssert(m_currentFunction == &_function, "Inlining internal function calls not yet implemented"); + + // Function Exit block. + createBlock(m_currentFunction); + connectBlocks(m_path.back(), predicate(&_function)); + + // Rule FunctionExit -> Interface, uses no constraints. + clearIndices(); + m_context.pushSolver(); + connectBlocks(predicate(&_function), interface()); + m_context.popSolver(); + m_currentFunction = nullptr; + solAssert(m_path.size() == m_functionBlocks, ""); + while (m_functionBlocks > 0) + popBlock(); + + solAssert(m_path.empty(), ""); SMTEncoder::endVisit(_function); } @@ -155,8 +195,66 @@ bool CHC::visit(IfStatement const& _if) { solAssert(m_currentFunction, ""); + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + SMTEncoder::visit(_if); + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(WhileStatement const& _while) +{ + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + + if (_while.isDoWhile()) + _while.body().accept(*this); + + visitLoop( + _while, + &_while.condition(), + _while.body(), + nullptr + ); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(ForStatement const& _for) +{ + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + + if (auto init = _for.initializationExpression()) + init->accept(*this); + + visitLoop( + _for, + _for.condition(), + _for.body(), + _for.loopExpression() + ); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + return false; } @@ -164,20 +262,162 @@ void CHC::endVisit(FunctionCall const& _funCall) { solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); - if (_funCall.annotation().kind == FunctionCallKind::FunctionCall) + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) { - FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); - if (funType.kind() == FunctionType::Kind::Assert) - visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + return; } - SMTEncoder::endVisit(_funCall); + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + SMTEncoder::endVisit(_funCall); + unknownFunctionCall(_funCall); + break; + default: + SMTEncoder::endVisit(_funCall); + break; + } createReturnedExpressions(_funCall); } -void CHC::visitAssert(FunctionCall const&) +void CHC::endVisit(Break const&) { + solAssert(m_breakDest, ""); + m_breakSeen = true; +} + +void CHC::endVisit(Continue const&) +{ + solAssert(m_continueDest, ""); + m_continueSeen = true; +} + +void CHC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + + solAssert(!m_path.empty(), ""); + + createErrorBlock(); + + smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue()); + connectBlocks(m_path.back(), error(), currentPathConditions() && assertNeg); + + m_verificationTargets.push_back(&_funCall); +} + +void CHC::unknownFunctionCall(FunctionCall const&) +{ + /// Function calls are not handled at the moment, + /// so always erase knowledge. + /// TODO remove when function calls get predicates/blocks. + eraseKnowledge(); + + /// Used to erase outer scope knowledge in loops and ifs. + /// TODO remove when function calls get predicates/blocks. + m_unknownFunctionCallSeen = true; +} + +void CHC::visitLoop( + BreakableStatement const& _loop, + Expression const* _condition, + Statement const& _body, + ASTNode const* _postLoop +) +{ + bool breakWasSeen = m_breakSeen; + bool continueWasSeen = m_continueSeen; + m_breakSeen = false; + m_continueSeen = false; + + solAssert(m_currentFunction, ""); + auto const& functionBody = m_currentFunction->body(); + + createBlock(&_loop, "loop_header_"); + createBlock(&_body, "loop_body_"); + createBlock(&functionBody, "block_"); + + connectBlocks(m_path.back(), predicate(&_loop)); + + // We need to save the next block here because new blocks + // might be created inside the loop body. + // This will be m_path.back() in the end of this function. + pushBlock(&functionBody); + + smt::Expression loopHeader = predicate(&_loop); + pushBlock(&_loop); + + if (_condition) + _condition->accept(*this); + auto condition = _condition ? expr(*_condition) : smt::Expression(true); + + connectBlocks(loopHeader, predicate(&_body), condition); + connectBlocks(loopHeader, predicate(&functionBody), !condition); + + // Loop body visit. + pushBlock(&_body); + + m_breakDest = &functionBody; + m_continueDest = _postLoop ? _postLoop : &_loop; + + auto functionBlocks = m_functionBlocks; + _body.accept(*this); + if (_postLoop) + { + createBlock(_postLoop, "loop_post_"); + connectBlocks(m_path.back(), predicate(_postLoop)); + pushBlock(_postLoop); + _postLoop->accept(*this); + } + + // Back edge. + connectBlocks(m_path.back(), predicate(&_loop)); + + // Pop all function blocks created by nested inner loops + // to adjust the assertion context. + for (unsigned i = m_functionBlocks; i > functionBlocks; --i) + popBlock(); + m_functionBlocks = functionBlocks; + + // Loop body + popBlock(); + // Loop header + popBlock(); + + // New function block starts with indices = 0 + clearIndices(); + + if (m_breakSeen || m_continueSeen) + { + eraseKnowledge(); + m_context.resetVariables([](VariableDeclaration const&) { return true; }); + } + + m_breakSeen = breakWasSeen; + m_continueSeen = continueWasSeen; } void CHC::reset() @@ -186,6 +426,15 @@ void CHC::reset() m_stateVariables.clear(); m_verificationTargets.clear(); m_safeAssertions.clear(); + m_unknownFunctionCallSeen = false; + m_breakSeen = false; + m_continueSeen = false; +} + +void CHC::eraseKnowledge() +{ + resetStateVariables(); + m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); } bool CHC::shouldVisit(ContractDefinition const& _contract) const @@ -202,19 +451,35 @@ bool CHC::shouldVisit(FunctionDefinition const& _function) const { if ( _function.isPublic() && - _function.isImplemented() + _function.isImplemented() && + !_function.isConstructor() ) return true; return false; } +void CHC::pushBlock(ASTNode const* _node) +{ + clearIndices(); + m_context.pushSolver(); + m_path.push_back(predicate(_node)); + ++m_functionBlocks; +} + +void CHC::popBlock() +{ + m_context.popSolver(); + m_path.pop_back(); + --m_functionBlocks; +} + smt::SortPointer CHC::constructorSort() { solAssert(m_currentContract, ""); auto boolSort = make_shared(smt::Kind::Bool); if (!m_currentContract->constructor()) return make_shared(vector{}, boolSort); - return functionSort(*m_currentContract->constructor()); + return sort(*m_currentContract->constructor()); } smt::SortPointer CHC::interfaceSort() @@ -226,17 +491,45 @@ smt::SortPointer CHC::interfaceSort() ); } -smt::SortPointer CHC::functionSort(FunctionDefinition const& _function) +smt::SortPointer CHC::sort(FunctionDefinition const& _function) { + if (m_nodeSorts.count(&_function)) + return m_nodeSorts.at(&_function); + auto boolSort = make_shared(smt::Kind::Bool); - auto const& funType = dynamic_cast(*_function.type()); - return make_shared( - smt::smtSort(funType.parameterTypes()), + vector varSorts; + for (auto const& var: _function.parameters() + _function.returnParameters()) + varSorts.push_back(smt::smtSort(*var->type())); + auto sort = make_shared( + m_stateSorts + varSorts, boolSort ); + return m_nodeSorts[&_function] = move(sort); } -unique_ptr CHC::createBlock(smt::SortPointer _sort, string const& _name) +smt::SortPointer CHC::sort(ASTNode const* _node) +{ + if (m_nodeSorts.count(_node)) + return m_nodeSorts.at(_node); + + if (auto funDef = dynamic_cast(_node)) + return sort(*funDef); + + auto fSort = dynamic_pointer_cast(sort(*m_currentFunction)); + solAssert(fSort, ""); + + auto boolSort = make_shared(smt::Kind::Bool); + vector varSorts; + for (auto const& var: m_currentFunction->localVariables()) + varSorts.push_back(smt::smtSort(*var->type())); + auto functionBodySort = make_shared( + fSort->domain + varSorts, + boolSort + ); + return m_nodeSorts[_node] = move(functionBodySort); +} + +unique_ptr CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name) { auto block = make_unique( _sort, @@ -273,6 +566,98 @@ smt::Expression CHC::error() return (*m_errorPredicate)({}); } +smt::Expression CHC::error(unsigned _idx) +{ + return m_errorPredicate->valueAtIndex(_idx)({}); +} + +void CHC::createBlock(ASTNode const* _node, string const& _prefix) +{ + if (m_predicates.count(_node)) + { + m_predicates.at(_node)->increaseIndex(); + m_interface->registerRelation(m_predicates.at(_node)->currentValue()); + } + else + m_predicates[_node] = createSymbolicBlock(sort(_node), _prefix + predicateName(_node)); +} + +void CHC::createErrorBlock() +{ + solAssert(m_errorPredicate, ""); + m_errorPredicate->increaseIndex(); + m_interface->registerRelation(m_errorPredicate->currentValue()); +} + +void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints) +{ + smt::Expression edge = smt::Expression::implies( + _from && m_context.assertions() && _constraints, + _to + ); + addRule(edge, _from.name + "_to_" + _to.name); +} + +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; +} + +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(); + } +} + +string CHC::predicateName(ASTNode const* _node) +{ + string prefix; + if (auto funDef = dynamic_cast(_node)) + { + prefix = funDef->isConstructor() ? + "constructor" : + funDef->isFallback() ? + "fallback" : + "function_" + funDef->name(); + prefix += "_"; + } + return prefix + to_string(_node->id()); +} + +smt::Expression CHC::predicate(ASTNode const* _node) +{ + if (dynamic_cast(_node)) + return (*m_predicates.at(_node))(currentFunctionVariables()); + return (*m_predicates.at(_node))(currentBlockVariables()); +} + +void CHC::addRule(smt::Expression const& _rule, string const& _ruleName) +{ + m_interface->addRule(_rule, _ruleName); +} + bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location) { smt::CheckResult result; diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h index d8108933a..5b40b4c8e 100644 --- a/libsolidity/formal/CHC.h +++ b/libsolidity/formal/CHC.h @@ -58,29 +58,44 @@ private: bool visit(FunctionDefinition const& _node) override; void endVisit(FunctionDefinition const& _node) override; bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const&) override; + bool visit(ForStatement const&) override; void endVisit(FunctionCall const& _node) override; + void endVisit(Break const& _node) override; + void endVisit(Continue const& _node) override; void visitAssert(FunctionCall const& _funCall); + void unknownFunctionCall(FunctionCall const& _funCall); + void visitLoop( + BreakableStatement const& _loop, + Expression const* _condition, + Statement const& _body, + ASTNode const* _postLoop + ); //@} /// Helpers. //@{ void reset(); + void eraseKnowledge(); bool shouldVisit(ContractDefinition const& _contract) const; bool shouldVisit(FunctionDefinition const& _function) const; + void pushBlock(ASTNode const* _node); + void popBlock(); //@} /// Sort helpers. //@{ smt::SortPointer constructorSort(); smt::SortPointer interfaceSort(); - smt::SortPointer functionSort(FunctionDefinition const& _function); + smt::SortPointer sort(FunctionDefinition const& _function); + smt::SortPointer sort(ASTNode const* _block); //@} /// Predicate helpers. //@{ /// @returns a new block of given _sort and _name. - std::unique_ptr createBlock(smt::SortPointer _sort, std::string const& _name); + std::unique_ptr createSymbolicBlock(smt::SortPointer _sort, std::string const& _name); /// Constructor predicate over current variables. smt::Expression constructor(); @@ -88,10 +103,39 @@ private: smt::Expression interface(); /// Error predicate over current variables. smt::Expression error(); + smt::Expression error(unsigned _idx); + + /// Creates a block for the given _node or increases its SSA index + /// if the block already exists which in practice creates a new function. + void createBlock(ASTNode const* _node, std::string const& _prefix = ""); + + /// Creates a new error block to be used by an assertion. + /// Also registers the predicate. + void createErrorBlock(); + + 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 function's + /// input and output parameters. + std::vector currentFunctionVariables(); + /// @returns the samve 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. + smt::Expression predicate(ASTNode const* _node); //@} /// Solver related. //@{ + /// Adds Horn rule to the solver. + void addRule(smt::Expression const& _rule, std::string const& _ruleName); /// @returns true if query is unsatisfiable (safe). bool query(smt::Expression const& _query, langutil::SourceLocation const& _location); //@} @@ -109,6 +153,9 @@ private: /// Artificial Error predicate. /// Single error block for all assertions. std::unique_ptr m_errorPredicate; + + /// Maps AST nodes to their predicates. + std::unordered_map> m_predicates; //@} /// Variables. @@ -119,6 +166,9 @@ private: /// State variables. /// Used to create all predicates. std::vector m_stateVariables; + + /// Input sorts for AST nodes. + std::map m_nodeSorts; //@} /// Verification targets. @@ -132,6 +182,22 @@ private: /// Control-flow. //@{ FunctionDefinition const* m_currentFunction = nullptr; + + /// Number of basic blocks created for the body of the current function. + unsigned m_functionBlocks = 0; + /// The current control flow path. + std::vector m_path; + /// Whether a function call was seen in the current scope. + bool m_unknownFunctionCallSeen = false; + /// Whether a break statement was seen in the current scope. + bool m_breakSeen = false; + /// Whether a continue statement was seen in the current scope. + bool m_continueSeen = false; + + /// Block where a loop break should go to. + ASTNode const* m_breakDest; + /// Block where a loop continue should go to. + ASTNode const* m_continueDest; //@} /// CHC solver. diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 909202094..dfe0cb913 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -137,6 +137,8 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) return m_context.mkConst(true); else if (n == "false") return m_context.mkConst(false); + else if (auto sortSort = dynamic_pointer_cast(_expr.sort)) + return m_context.mkVar(n, cvc4Sort(*sortSort->inner)); else try { @@ -187,6 +189,12 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]); else if (n == "store") return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]); + else if (n == "const_array") + { + shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + solAssert(sortSort, ""); + return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1])); + } solAssert(false, ""); } diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index 85ec5e776..8f4b369da 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -621,8 +621,8 @@ void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier) auto const& fType = dynamic_cast(*_identifier.annotation().type); if (fType.returnParameterTypes().size() == 1) { - defineGlobalVariable(fType.richIdentifier(), _identifier); - m_context.createExpression(_identifier, m_context.globalSymbol(fType.richIdentifier())); + defineGlobalVariable(fType.identifier(), _identifier); + m_context.createExpression(_identifier, m_context.globalSymbol(fType.identifier())); } } @@ -748,6 +748,12 @@ 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(), @@ -947,18 +953,21 @@ pair SMTEncoder::arithmeticOperation( smt::Expression intValueRange = (0 - smt::minValue(intType)) + smt::maxValue(intType) + 1; auto value = smt::Expression::ite( - valueNoMod > smt::maxValue(intType) || valueNoMod < smt::minValue(intType), + valueNoMod > smt::maxValue(intType), valueNoMod % intValueRange, - valueNoMod + smt::Expression::ite( + valueNoMod < smt::minValue(intType), + valueNoMod % intValueRange, + valueNoMod + ) ); + if (intType.isSigned()) - { value = smt::Expression::ite( value > smt::maxValue(intType), value - intValueRange, value ); - } return {value, valueNoMod}; } diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 6ccbe9bcc..04984d090 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -87,6 +87,8 @@ protected: bool visit(MemberAccess const& _node) override; void endVisit(IndexAccess const& _node) override; bool visit(InlineAssembly const& _node) override; + void endVisit(Break const&) override {} + void endVisit(Continue const&) override {} /// Do not visit subtree if node is a RationalNumber. /// Symbolic _expr is the rational literal. diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index a23dbe559..c0d37431e 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -17,8 +17,6 @@ #include -#include -#include #include #include @@ -30,7 +28,6 @@ #include #include #include -#include using namespace std; using namespace dev; @@ -49,7 +46,7 @@ void SMTLib2Interface::reset() m_accumulatedOutput.emplace_back(); m_variables.clear(); write("(set-option :produce-models true)"); - write("(set-logic QF_UFLIA)"); + write("(set-logic ALL)"); } void SMTLib2Interface::push() @@ -96,12 +93,12 @@ void SMTLib2Interface::declareFunction(string const& _name, Sort const& _sort) } } -void SMTLib2Interface::addAssertion(Expression const& _expr) +void SMTLib2Interface::addAssertion(smt::Expression const& _expr) { write("(assert " + toSExpr(_expr) + ")"); } -pair> SMTLib2Interface::check(vector const& _expressionsToEvaluate) +pair> SMTLib2Interface::check(vector const& _expressionsToEvaluate) { string response = querySolver( boost::algorithm::join(m_accumulatedOutput, "\n") + @@ -125,13 +122,28 @@ pair> SMTLib2Interface::check(vector con return make_pair(result, values); } -string SMTLib2Interface::toSExpr(Expression const& _expr) +string SMTLib2Interface::toSExpr(smt::Expression const& _expr) { if (_expr.arguments.empty()) return _expr.name; - std::string sexpr = "(" + _expr.name; - for (auto const& arg: _expr.arguments) - sexpr += " " + toSExpr(arg); + + std::string sexpr = "("; + if (_expr.name == "const_array") + { + solAssert(_expr.arguments.size() == 2, ""); + auto sortSort = std::dynamic_pointer_cast(_expr.arguments.at(0).sort); + solAssert(sortSort, ""); + auto arraySort = dynamic_pointer_cast(sortSort->inner); + solAssert(arraySort, ""); + sexpr += "(as const " + toSmtLibSort(*arraySort) + ") "; + sexpr += toSExpr(_expr.arguments.at(1)); + } + else + { + sexpr += _expr.name; + for (auto const& arg: _expr.arguments) + sexpr += " " + toSExpr(arg); + } sexpr += ")"; return sexpr; } @@ -169,7 +181,7 @@ void SMTLib2Interface::write(string _data) m_accumulatedOutput.back() += move(_data) + "\n"; } -string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _expressionsToEvaluate) +string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _expressionsToEvaluate) { string command; if (_expressionsToEvaluate.empty()) diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index d0bf4702d..a39c861c6 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -50,21 +50,21 @@ public: void declareVariable(std::string const&, Sort const&) override; - void addAssertion(Expression const& _expr) override; - std::pair> check(std::vector const& _expressionsToEvaluate) override; + void addAssertion(smt::Expression const& _expr) override; + std::pair> check(std::vector const& _expressionsToEvaluate) override; std::vector unhandledQueries() override { return m_unhandledQueries; } private: void declareFunction(std::string const&, Sort const&); - std::string toSExpr(Expression const& _expr); + std::string toSExpr(smt::Expression const& _expr); std::string toSmtLibSort(Sort const& _sort); std::string toSmtLibSort(std::vector const& _sort); void write(std::string _data); - std::string checkSatAndGetValuesCommand(std::vector const& _expressionsToEvaluate); + std::string checkSatAndGetValuesCommand(std::vector const& _expressionsToEvaluate); std::vector parseValues(std::string::const_iterator _start, std::string::const_iterator _end); /// Communicates with the solver via the callback. Throws SMTSolverError on error. diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index 09a311f4f..73c194a01 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -65,7 +65,7 @@ void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort) s->declareVariable(_name, _sort); } -void SMTPortfolio::addAssertion(Expression const& _expr) +void SMTPortfolio::addAssertion(smt::Expression const& _expr) { for (auto const& s: m_solvers) s->addAssertion(_expr); @@ -101,7 +101,7 @@ void SMTPortfolio::addAssertion(Expression const& _expr) * * If all solvers return ERROR, the result is ERROR. */ -pair> SMTPortfolio::check(vector const& _expressionsToEvaluate) +pair> SMTPortfolio::check(vector const& _expressionsToEvaluate) { CheckResult lastResult = CheckResult::ERROR; vector finalValues; diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index 3eb0a793b..ded7985f9 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -51,9 +51,9 @@ public: void declareVariable(std::string const&, Sort const&) override; - void addAssertion(Expression const& _expr) override; + void addAssertion(smt::Expression const& _expr) override; - std::pair> check(std::vector const& _expressionsToEvaluate) override; + std::pair> check(std::vector const& _expressionsToEvaluate) override; std::vector unhandledQueries() override; unsigned solvers() override { return m_solvers.size(); } @@ -62,7 +62,7 @@ private: std::vector> m_solvers; - std::vector m_assertions; + std::vector m_assertions; }; } diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 5610a5fd6..0004e1726 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include #include @@ -45,7 +46,8 @@ enum class Kind Int, Bool, Function, - Array + Array, + Sort }; struct Sort @@ -110,12 +112,33 @@ struct ArraySort: public Sort SortPointer range; }; +struct SortSort: public Sort +{ + SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {} + bool operator==(Sort const& _other) const override + { + if (!Sort::operator==(_other)) + return false; + auto _otherSort = dynamic_cast(&_other); + solAssert(_otherSort, ""); + solAssert(_otherSort->inner, ""); + solAssert(inner, ""); + return *inner == *_otherSort->inner; + } + + SortPointer inner; +}; + +// Forward declaration. +SortPointer smtSort(solidity::Type const& _type); + /// C++ representation of an SMTLIB2 expression. class Expression { friend class SolverInterface; public: explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {} + explicit Expression(solidity::TypePointer _type): Expression(_type->toString(), {}, std::make_shared(smtSort(*_type))) {} Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {} Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {} Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {} @@ -145,7 +168,8 @@ public: {"/", 2}, {"mod", 2}, {"select", 2}, - {"store", 3} + {"store", 3}, + {"const_array", 2} }; return operatorsArity.count(name) && operatorsArity.at(name) == arguments.size(); } @@ -202,6 +226,21 @@ public: ); } + static Expression const_array(Expression _sort, Expression _value) + { + solAssert(_sort.sort->kind == Kind::Sort, ""); + auto sortSort = std::dynamic_pointer_cast(_sort.sort); + auto arraySort = std::dynamic_pointer_cast(sortSort->inner); + solAssert(sortSort && arraySort, ""); + solAssert(_value.sort, ""); + solAssert(*arraySort->range == *_value.sort, ""); + return Expression( + "const_array", + std::vector{std::move(_sort), std::move(_value)}, + arraySort + ); + } + friend Expression operator!(Expression _a) { return Expression("not", std::move(_a), Kind::Bool); diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 6b9f26d82..ab23b8712 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -276,10 +276,30 @@ void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _c void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context) { solAssert(_type, ""); - if (isNumber(_type->category())) - _context.addAssertion(_expr == 0); - else if (isBool(_type->category())) - _context.addAssertion(_expr == Expression(false)); + _context.addAssertion(_expr == zeroValue(_type)); +} + +Expression zeroValue(solidity::TypePointer const& _type) +{ + solAssert(_type, ""); + if (isSupportedType(_type->category())) + { + if (isNumber(_type->category())) + return 0; + if (isBool(_type->category())) + return Expression(false); + if (isArray(_type->category()) || isMapping(_type->category())) + { + if (auto arrayType = dynamic_cast(_type)) + return Expression::const_array(Expression(arrayType), zeroValue(arrayType->baseType())); + auto mappingType = dynamic_cast(_type); + solAssert(mappingType, ""); + return Expression::const_array(Expression(mappingType), zeroValue(mappingType->valueType())); + } + solAssert(false, ""); + } + // Unsupported types are abstracted as Int. + return 0; } void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context) diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 355c25328..30a341431 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -63,6 +63,7 @@ std::pair> newSymbolicVariable(solidity: Expression minValue(solidity::IntegerType const& _type); Expression maxValue(solidity::IntegerType const& _type); +Expression zeroValue(solidity::TypePointer const& _type); void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context); void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context); diff --git a/libsolidity/formal/Z3CHCInterface.cpp b/libsolidity/formal/Z3CHCInterface.cpp index 7b8de2c91..100a1fb7d 100644 --- a/libsolidity/formal/Z3CHCInterface.cpp +++ b/libsolidity/formal/Z3CHCInterface.cpp @@ -33,6 +33,18 @@ Z3CHCInterface::Z3CHCInterface(): z3::set_param("rewriter.pull_cheap_ite", true); // This needs to be set in the context. m_context->set("timeout", queryTimeout); + + // Spacer options. + // These needs to be set in the solver. + // https://github.com/Z3Prover/z3/blob/master/src/muz/base/fp_params.pyg + z3::params p(*m_context); + // These are useful for solving problems with arrays and loops. + // Use quantified lemma generalizer. + p.set("fp.spacer.q3.use_qgen", true); + p.set("fp.spacer.mbqi", false); + // Ground pobs by using values from a model. + p.set("fp.spacer.ground_pobs", false); + m_solver.set(p); } void Z3CHCInterface::declareVariable(string const& _name, Sort const& _sort) @@ -82,12 +94,14 @@ pair> Z3CHCInterface::query(Expression const& _expr) break; } case z3::check_result::unknown: + { result = CheckResult::UNKNOWN; break; } + } // TODO retrieve model / invariants } - catch (z3::exception const& _e) + catch (z3::exception const&) { result = CheckResult::ERROR; values.clear(); diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 1039dd1f8..e07bacdb7 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -132,6 +132,12 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return m_context.bool_val(true); else if (n == "false") return m_context.bool_val(false); + else if (_expr.sort->kind == Kind::Sort) + { + auto sortSort = dynamic_pointer_cast(_expr.sort); + solAssert(sortSort, ""); + return m_context.constant(n.c_str(), z3Sort(*sortSort->inner)); + } else try { @@ -178,6 +184,14 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) return z3::select(arguments[0], arguments[1]); else if (n == "store") return z3::store(arguments[0], arguments[1], arguments[2]); + else if (n == "const_array") + { + shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + solAssert(sortSort, ""); + auto arraySort = dynamic_pointer_cast(sortSort->inner); + solAssert(arraySort && arraySort->domain, ""); + return z3::const_array(z3Sort(*arraySort->domain), arguments[1]); + } solAssert(false, ""); } diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index 6d450141d..a21053d9f 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -15,7 +15,7 @@ along with solidity. If not, see . */ /** - * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + * Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html) */ #include @@ -40,7 +40,10 @@ bool anyDataStoredInStorage(TypePointers const& _pointers) Json::Value ABI::generate(ContractDefinition const& _contractDef) { - Json::Value abi(Json::arrayValue); + auto compare = [](Json::Value const& _a, Json::Value const& _b) -> bool { + return make_tuple(_a["type"], _a["name"]) < make_tuple(_b["type"], _b["name"]); + }; + multiset abi(compare); for (auto it: _contractDef.interfaceFunctions()) { @@ -71,7 +74,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) it.second->returnParameterTypes(), _contractDef.isLibrary() ); - abi.append(std::move(method)); + abi.emplace(std::move(method)); } if (_contractDef.constructor()) { @@ -88,7 +91,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) constrType.parameterTypes(), _contractDef.isLibrary() ); - abi.append(std::move(method)); + abi.emplace(std::move(method)); } if (_contractDef.fallbackFunction()) { @@ -98,7 +101,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) method["type"] = "fallback"; method["payable"] = externalFunctionType->isPayable(); method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); - abi.append(std::move(method)); + abi.emplace(std::move(method)); } for (auto const& it: _contractDef.interfaceEvents()) { @@ -117,10 +120,13 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) params.append(std::move(param)); } event["inputs"] = std::move(params); - abi.append(std::move(event)); + abi.emplace(std::move(event)); } - return abi; + Json::Value abiJson{Json::arrayValue}; + for (auto& f: abi) + abiJson.append(std::move(f)); + return abiJson; } Json::Value ABI::formatTypeList( diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index f52081503..9e1ba294e 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -15,7 +15,7 @@ along with solidity. If not, see . */ /** - * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + * Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html) */ #pragma once diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index f91cb91ea..56a74f47d 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -180,7 +180,7 @@ ASTPointer Parser::parseImportDirective() expectToken(Token::Import); ASTPointer path; ASTPointer unitAlias = make_shared(); - vector, ASTPointer>> symbolAliases; + ImportDirective::SymbolAliasList symbolAliases; if (m_scanner->currentToken() == Token::StringLiteral) { @@ -198,14 +198,16 @@ ASTPointer Parser::parseImportDirective() m_scanner->next(); while (true) { - ASTPointer id = parseIdentifier(); ASTPointer alias; + SourceLocation aliasLocation = SourceLocation{position(), endPosition(), source()}; + ASTPointer id = parseIdentifier(); if (m_scanner->currentToken() == Token::As) { expectToken(Token::As); + aliasLocation = SourceLocation{position(), endPosition(), source()}; alias = expectIdentifierToken(); } - symbolAliases.emplace_back(move(id), move(alias)); + symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation}); if (m_scanner->currentToken() != Token::Comma) break; m_scanner->next(); diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 2622e413a..9c62737d8 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -732,6 +732,14 @@ void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocatio { errorForVM("only available for Constantinople-compatible"); } + else if (_instr == dev::eth::Instruction::CHAINID && !m_evmVersion.hasChainID()) + { + errorForVM("only available for Istanbul-compatible"); + } + else if (_instr == dev::eth::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance()) + { + errorForVM("only available for Istanbul-compatible"); + } else if ( _instr == dev::eth::Instruction::JUMP || _instr == dev::eth::Instruction::JUMPI || diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index bc686ddf3..0bffc5389 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -62,6 +62,8 @@ add_library(yul optimiser/BlockFlattener.h optimiser/BlockHasher.cpp optimiser/BlockHasher.h + optimiser/CallGraphGenerator.cpp + optimiser/CallGraphGenerator.h optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.h optimiser/ControlFlowSimplifier.cpp @@ -86,6 +88,8 @@ add_library(yul optimiser/ExpressionSplitter.h optimiser/ForLoopConditionIntoBody.cpp optimiser/ForLoopConditionIntoBody.h + optimiser/ForLoopConditionOutOfBody.cpp + optimiser/ForLoopConditionOutOfBody.h optimiser/ForLoopInitRewriter.cpp optimiser/ForLoopInitRewriter.h optimiser/FullInliner.cpp @@ -110,6 +114,7 @@ add_library(yul optimiser/NameDispenser.h optimiser/NameDisplacer.cpp optimiser/NameDisplacer.h + optimiser/OptimiserStep.h optimiser/OptimizerUtilities.cpp optimiser/OptimizerUtilities.h optimiser/RedundantAssignEliminator.cpp diff --git a/libyul/Dialect.h b/libyul/Dialect.h index bf1a352e1..5fd938c1e 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include @@ -45,24 +46,9 @@ struct BuiltinFunction YulString name; std::vector parameters; std::vector returns; - /// If true, calls to this function can be freely moved and copied (as long as their - /// arguments are either variables or also movable) without altering the semantics. - /// This means the function cannot depend on storage or memory, cannot have any side-effects, - /// but it can depend on state that is constant across an EVM-call. - bool movable = false; - /// If true, a call to this function can be omitted without changing semantics. - bool sideEffectFree = false; - /// If true, a call to this function can be omitted without changing semantics if the - /// program does not contain the msize instruction. - bool sideEffectFreeIfNoMSize = false; + SideEffects sideEffects; /// If true, this is the msize instruction. bool isMSize = false; - /// If false, storage of the current contract before and after the function is the same - /// under every circumstance. If the function does not return, this can be false. - bool invalidatesStorage = true; - /// If false, memory before and after the function is the same under every circumstance. - /// If the function does not return, this can be false. - bool invalidatesMemory = true; /// If true, can only accept literals as arguments and they cannot be moved to variables. bool literalArguments = false; }; @@ -75,6 +61,7 @@ struct Dialect: boost::noncopyable virtual BuiltinFunction const* discardFunction() const { return nullptr; } virtual BuiltinFunction const* equalityFunction() const { return nullptr; } + virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; } virtual std::set fixedFunctionNames() const { return {}; } diff --git a/libyul/SideEffects.h b/libyul/SideEffects.h new file mode 100644 index 000000000..0a8e1595e --- /dev/null +++ b/libyul/SideEffects.h @@ -0,0 +1,88 @@ +/* + 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 + +namespace yul +{ + +/** + * Side effects of code. + * + * The default-constructed value applies to the "empty code". + */ +struct SideEffects +{ + /// If true, expressions in this code can be freely moved and copied without altering the + /// semantics. + /// At statement level, it means that functions containing this code can be + /// called multiple times, their calls can be rearranged and calls can also be + /// deleted without changing the semantics. + /// This means it cannot depend on storage or memory, cannot have any side-effects, + /// but it can depend on state that is constant across an EVM-call. + bool movable = true; + /// If true, the code can be removed without changing the semantics. + bool sideEffectFree = true; + /// If true, the code can be removed without changing the semantics as long as + /// the whole program does not contain the msize instruction. + bool sideEffectFreeIfNoMSize = true; + /// If false, storage is guaranteed to be unchanged by the code under all + /// circumstances. + bool invalidatesStorage = false; + /// If false, memory is guaranteed to be unchanged by the code under all + /// circumstances. + bool invalidatesMemory = false; + + /// @returns the worst-case side effects. + static SideEffects worst() + { + return SideEffects{false, false, false, true, true}; + } + + /// @returns the combined side effects of two pieces of code. + SideEffects operator+(SideEffects const& _other) + { + return SideEffects{ + movable && _other.movable, + sideEffectFree && _other.sideEffectFree, + sideEffectFreeIfNoMSize && _other.sideEffectFreeIfNoMSize, + invalidatesStorage || _other.invalidatesStorage, + invalidatesMemory || _other.invalidatesMemory + }; + } + + /// Adds the side effects of another piece of code to this side effect. + SideEffects& operator+=(SideEffects const& _other) + { + *this = *this + _other; + return *this; + } + + bool operator==(SideEffects const& _other) const + { + return + movable == _other.movable && + sideEffectFree == _other.sideEffectFree && + sideEffectFreeIfNoMSize == _other.sideEffectFreeIfNoMSize && + invalidatesStorage == _other.invalidatesStorage && + invalidatesMemory == _other.invalidatesMemory; + } +}; + +} diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index b4a7cbd49..a9cbd2cbc 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -50,12 +50,8 @@ pair createEVMFunction( f.name = YulString{_name}; f.parameters.resize(info.args); f.returns.resize(info.ret); - f.movable = eth::SemanticInformation::movable(_instruction); - f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); - f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction); + f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction); f.isMSize = _instruction == dev::eth::Instruction::MSIZE; - f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction); - f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction); f.literalArguments = false; f.instruction = _instruction; f.generateCode = [_instruction]( @@ -75,11 +71,7 @@ pair createFunction( string _name, size_t _params, size_t _returns, - bool _movable, - bool _sideEffectFree, - bool _sideEffectFreeIfNoMSize, - bool _invalidatesStorage, - bool _invalidatesMemory, + SideEffects _sideEffects, bool _literalArguments, std::function)> _generateCode ) @@ -89,13 +81,9 @@ pair createFunction( f.name = name; f.parameters.resize(_params); f.returns.resize(_returns); - f.movable = _movable; + f.sideEffects = std::move(_sideEffects); f.literalArguments = _literalArguments; - f.sideEffectFree = _sideEffectFree; - f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize; f.isMSize = false; - f.invalidatesStorage = _invalidatesStorage; - f.invalidatesMemory = _invalidatesMemory; f.instruction = {}; f.generateCode = std::move(_generateCode); return {name, f}; @@ -116,7 +104,7 @@ map createBuiltins(langutil::EVMVersion _evmVe if (_objectAccess) { - builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, []( + builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -137,7 +125,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataSize(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, []( + builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -158,15 +146,22 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataOffset(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, []( - FunctionCall const&, - AbstractAssembly& _assembly, - BuiltinContext&, - std::function _visitArguments - ) { - _visitArguments(); - _assembly.appendInstruction(dev::eth::Instruction::CODECOPY); - })); + builtins.emplace(createFunction( + "datacopy", + 3, + 0, + SideEffects{false, false, false, false, true}, + false, + []( + FunctionCall const&, + AbstractAssembly& _assembly, + BuiltinContext&, + std::function _visitArguments + ) { + _visitArguments(); + _assembly.appendInstruction(dev::eth::Instruction::CODECOPY); + } + )); } return builtins; } @@ -225,3 +220,14 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version) dialects[_version] = make_unique(AsmFlavour::Yul, false, _version); return *dialects[_version]; } + +SideEffects EVMDialect::sideEffectsOfInstruction(eth::Instruction _instruction) +{ + return SideEffects{ + eth::SemanticInformation::movable(_instruction), + eth::SemanticInformation::sideEffectFree(_instruction), + eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction), + eth::SemanticInformation::invalidatesStorage(_instruction), + eth::SemanticInformation::invalidatesMemory(_instruction) + }; +} diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index bcea185f4..1c47c8005 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -70,6 +70,7 @@ struct EVMDialect: public Dialect BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); } BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); } + BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); } static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); @@ -80,6 +81,8 @@ struct EVMDialect: public Dialect bool providesObjectAccess() const { return m_objectAccess; } + static SideEffects sideEffectsOfInstruction(dev::eth::Instruction _instruction); + protected: bool const m_objectAccess; langutil::EVMVersion const m_evmVersion; diff --git a/libyul/backends/wasm/EVMToEWasmTranslator.cpp b/libyul/backends/wasm/EVMToEWasmTranslator.cpp index 75cbe242a..1be9a1a38 100644 --- a/libyul/backends/wasm/EVMToEWasmTranslator.cpp +++ b/libyul/backends/wasm/EVMToEWasmTranslator.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -637,11 +638,14 @@ Object EVMToEWasmTranslator::run(Object const& _object) parsePolyfill(); Block ast = boost::get(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code)); - NameDispenser nameDispenser{m_dialect, ast}; - FunctionHoister{}(ast); - FunctionGrouper{}(ast); + set reservedIdentifiers; + NameDispenser nameDispenser{m_dialect, ast, reservedIdentifiers}; + OptimiserStepContext context{m_dialect, nameDispenser, reservedIdentifiers}; + + FunctionHoister::run(context, ast); + FunctionGrouper::run(context, ast); MainFunction{}(ast); - ExpressionSplitter{m_dialect, nameDispenser}(ast); + ExpressionSplitter::run(context, ast); WordSizeTransform::run(m_dialect, ast, nameDispenser); NameDisplacer{nameDispenser, m_polyfillFunctions}(ast); diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index 3bcc18fe8..f9e28e94a 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -55,7 +55,7 @@ string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) std::vector imports; for (auto& imp: transform.m_functionsToImport) imports.emplace_back(std::move(imp.second)); - return EWasmToText{}.run( + return EWasmToText().run( transform.m_globalVariables, imports, functions diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index d06fe2edd..f781292ae 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -49,19 +49,19 @@ WasmDialect::WasmDialect(): addFunction("i64.eqz", 1, 1); addFunction("i64.store", 2, 0, false); - m_functions["i64.store"_yulstring].invalidatesStorage = false; + m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false; addFunction("i64.load", 1, 1, false); - m_functions["i64.load"_yulstring].invalidatesStorage = false; - m_functions["i64.load"_yulstring].invalidatesMemory = false; - m_functions["i64.load"_yulstring].sideEffectFree = true; - m_functions["i64.load"_yulstring].sideEffectFreeIfNoMSize = true; + m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false; + m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false; + m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true; + m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true; addFunction("drop", 1, 0); addFunction("unreachable", 0, 0, false); - m_functions["unreachable"_yulstring].invalidatesStorage = false; - m_functions["unreachable"_yulstring].invalidatesMemory = false; + m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false; + m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false; addFunction("datasize", 1, 4, true, true); addFunction("dataoffset", 1, 4, true, true); @@ -138,14 +138,10 @@ void WasmDialect::addEthereumExternals() f.parameters.emplace_back(YulString(p)); for (string const& p: ext.returns) f.returns.emplace_back(YulString(p)); - f.movable = false; // TODO some of them are side effect free. - f.sideEffectFree = false; - f.sideEffectFreeIfNoMSize = false; + f.sideEffects = SideEffects::worst(); f.isMSize = false; - f.invalidatesStorage = (ext.name == "storageStore"); - // TODO some of them do not invalidate memory - f.invalidatesMemory = true; + f.sideEffects.invalidatesStorage = (ext.name == "storageStore"); f.literalArguments = false; } } @@ -163,11 +159,7 @@ void WasmDialect::addFunction( f.name = name; f.parameters.resize(_params); f.returns.resize(_returns); - f.movable = _movable; - f.sideEffectFree = _movable; - f.sideEffectFreeIfNoMSize = _movable; + f.sideEffects = _movable ? SideEffects{} : SideEffects::worst(); f.isMSize = false; - f.invalidatesStorage = !_movable; - f.invalidatesMemory = !_movable; f.literalArguments = _literalArguments; } diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 45bea3bca..e77381a13 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -47,6 +47,7 @@ struct WasmDialect: public Dialect BuiltinFunction const* builtin(YulString _name) const override; BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); } BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); } + BuiltinFunction const* booleanNegationFunction() const override { return builtin("i64.eqz"_yulstring); } std::set fixedFunctionNames() const override { return {"main"_yulstring}; } diff --git a/libyul/optimiser/BlockFlattener.h b/libyul/optimiser/BlockFlattener.h index b732422d7..5895712e5 100644 --- a/libyul/optimiser/BlockFlattener.h +++ b/libyul/optimiser/BlockFlattener.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace yul { @@ -24,8 +25,14 @@ namespace yul class BlockFlattener: public ASTModifier { public: + static constexpr char const* name{"BlockFlattener"}; + static void run(OptimiserStepContext&, Block& _ast) { BlockFlattener{}(_ast); } + using ASTModifier::operator(); void operator()(Block& _block) override; + +private: + BlockFlattener() = default; }; } diff --git a/libyul/optimiser/CallGraphGenerator.cpp b/libyul/optimiser/CallGraphGenerator.cpp new file mode 100644 index 000000000..0c11464ee --- /dev/null +++ b/libyul/optimiser/CallGraphGenerator.cpp @@ -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 . +*/ +/** + * Specific AST walker that generates the call graph. + */ + +#include +#include + +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +map> CallGraphGenerator::callGraph(Block const& _ast) +{ + CallGraphGenerator gen; + gen(_ast); + return std::move(gen.m_callGraph); +} + +void CallGraphGenerator::operator()(FunctionalInstruction const& _functionalInstruction) +{ + 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}); + ASTWalker::operator()(_functionalInstruction); +} + +void CallGraphGenerator::operator()(FunctionCall const& _functionCall) +{ + m_callGraph[m_currentFunction].insert(_functionCall.functionName.name); + ASTWalker::operator()(_functionCall); +} + +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] = {}; + ASTWalker::operator()(_functionDefinition); + m_currentFunction = previousFunction; +} + +CallGraphGenerator::CallGraphGenerator() +{ + m_callGraph[YulString{}] = {}; +} + diff --git a/libyul/optimiser/CallGraphGenerator.h b/libyul/optimiser/CallGraphGenerator.h new file mode 100644 index 000000000..6946721bd --- /dev/null +++ b/libyul/optimiser/CallGraphGenerator.h @@ -0,0 +1,58 @@ +/* + 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 . +*/ +/** + * Specific AST walker that generates the call graph. + */ + +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace yul +{ + +/** + * Specific AST walker that generates the call graph. + * + * The outermost (non-function) context is denoted by the empty string. + */ +class CallGraphGenerator: public ASTWalker +{ +public: + static std::map> callGraph(Block const& _ast); + + using ASTWalker::operator(); + void operator()(FunctionalInstruction const& _functionalInstruction) override; + void operator()(FunctionCall const& _functionCall) override; + void operator()(FunctionDefinition const& _functionDefinition) override; + +private: + CallGraphGenerator(); + + std::map> 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 5f182ebaf..65e0bae02 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -23,6 +23,9 @@ #include #include +#include +#include +#include #include #include #include @@ -31,6 +34,23 @@ using namespace std; using namespace dev; using namespace yul; +void CommonSubexpressionEliminator::run(OptimiserStepContext& _context, Block& _ast) +{ + CommonSubexpressionEliminator cse{ + _context.dialect, + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)) + }; + cse(_ast); +} + +CommonSubexpressionEliminator::CommonSubexpressionEliminator( + Dialect const& _dialect, + map _functionSideEffects +): + DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)) +{ +} + void CommonSubexpressionEliminator::visit(Expression& _e) { bool descend = true; diff --git a/libyul/optimiser/CommonSubexpressionEliminator.h b/libyul/optimiser/CommonSubexpressionEliminator.h index 6499e6db3..8db5e7aed 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.h +++ b/libyul/optimiser/CommonSubexpressionEliminator.h @@ -22,11 +22,13 @@ #pragma once #include +#include namespace yul { struct Dialect; +struct SideEffects; /** * Optimisation stage that replaces expressions known to be the current value of a variable @@ -37,7 +39,14 @@ struct Dialect; class CommonSubexpressionEliminator: public DataFlowAnalyzer { public: - CommonSubexpressionEliminator(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + static constexpr char const* name{"CommonSubexpressionEliminator"}; + static void run(OptimiserStepContext&, Block& _ast); + +private: + CommonSubexpressionEliminator( + Dialect const& _dialect, + std::map _functionSideEffects + ); protected: using ASTModifier::visit; diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index 7c826aa28..96e399027 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -16,6 +16,7 @@ */ #include #include +#include #include #include #include @@ -125,6 +126,11 @@ OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _swit } +void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast) +{ + ControlFlowSimplifier{_context.dialect}(_ast); +} + void ControlFlowSimplifier::operator()(Block& _block) { simplify(_block.statements); diff --git a/libyul/optimiser/ControlFlowSimplifier.h b/libyul/optimiser/ControlFlowSimplifier.h index 23dc3075b..1f1c287ec 100644 --- a/libyul/optimiser/ControlFlowSimplifier.h +++ b/libyul/optimiser/ControlFlowSimplifier.h @@ -17,10 +17,12 @@ #pragma once #include +#include namespace yul { struct Dialect; +struct OptimiserStepContext; /** * Simplifies several control-flow structures: @@ -46,7 +48,8 @@ struct Dialect; class ControlFlowSimplifier: public ASTModifier { public: - ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {} + static constexpr char const* name{"ControlFlowSimplifier"}; + static void run(OptimiserStepContext&, Block& _ast); using ASTModifier::operator(); void operator()(Break&) override { ++m_numBreakStatements; } @@ -56,6 +59,8 @@ public: void visit(Statement& _st) override; private: + ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {} + void simplify(std::vector& _statements); Dialect const& m_dialect; diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index a927a7eca..c2042a19c 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -43,7 +43,6 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) if (auto vars = isSimpleStore(dev::eth::Instruction::SSTORE, _statement)) { ASTModifier::operator()(_statement); - m_storage.set(vars->first, vars->second); set keysToErase; for (auto const& item: m_storage.values) if (!( @@ -53,6 +52,7 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) keysToErase.insert(item.first); for (YulString const& key: keysToErase) m_storage.eraseKey(key); + m_storage.set(vars->first, vars->second); } else if (auto vars = isSimpleStore(dev::eth::Instruction::MSTORE, _statement)) { @@ -61,11 +61,9 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement) for (auto const& item: m_memory.values) if (!m_knowledgeBase.knownToBeDifferentByAtLeast32(vars->first, item.first)) keysToErase.insert(item.first); - // TODO is it fine to do that here? - // can we also move the storage above? - m_memory.set(vars->first, vars->second); for (YulString const& key: keysToErase) m_memory.eraseKey(key); + m_memory.set(vars->first, vars->second); } else { @@ -213,7 +211,7 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres { clearValues(_variables); - MovableChecker movableChecker{m_dialect}; + MovableChecker movableChecker{m_dialect, &m_functionSideEffects}; if (_value) movableChecker.visit(*_value); else @@ -299,7 +297,7 @@ void DataFlowAnalyzer::clearValues(set _variables) void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) { - SideEffectsCollector sideEffects(m_dialect, _block); + SideEffectsCollector sideEffects(m_dialect, _block, &m_functionSideEffects); if (sideEffects.invalidatesStorage()) m_storage.clear(); if (sideEffects.invalidatesMemory()) @@ -308,7 +306,7 @@ void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr) { - SideEffectsCollector sideEffects(m_dialect, _expr); + SideEffectsCollector sideEffects(m_dialect, _expr, &m_functionSideEffects); if (sideEffects.invalidatesStorage()) m_storage.clear(); if (sideEffects.invalidatesMemory()) diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 5df45dcb8..b8e455ea1 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -26,6 +26,7 @@ #include #include #include +#include // TODO avoid #include @@ -38,6 +39,7 @@ namespace yul { struct Dialect; +struct SideEffects; /** * Base class to perform data flow analysis during AST walks. @@ -67,8 +69,16 @@ struct Dialect; class DataFlowAnalyzer: public ASTModifier { public: - explicit DataFlowAnalyzer(Dialect const& _dialect): + /// @param _functionSideEffects + /// Side-effects of user-defined functions. Worst-case side-effects are assumed + /// if this is not provided or the function is not found. + /// The parameter is mostly used to determine movability of expressions. + explicit DataFlowAnalyzer( + Dialect const& _dialect, + std::map _functionSideEffects = {} + ): m_dialect(_dialect), + m_functionSideEffects(std::move(_functionSideEffects)), m_knowledgeBase(_dialect, m_value) {} @@ -124,6 +134,9 @@ protected: ) const; Dialect const& m_dialect; + /// Side-effects of user-defined functions. Worst-case side-effects are assumed + /// if this is not provided or the function is not found. + std::map m_functionSideEffects; /// Current values of variables, always movable. std::map m_value; diff --git a/libyul/optimiser/DeadCodeEliminator.cpp b/libyul/optimiser/DeadCodeEliminator.cpp index 5d123ca74..95e1cf284 100644 --- a/libyul/optimiser/DeadCodeEliminator.cpp +++ b/libyul/optimiser/DeadCodeEliminator.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -32,6 +33,11 @@ using namespace dev; using namespace yul; +void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast) +{ + DeadCodeEliminator{_context.dialect}(_ast); +} + void DeadCodeEliminator::operator()(ForLoop& _for) { yulAssert(_for.pre.statements.empty(), "DeadCodeEliminator needs ForLoopInitRewriter as a prerequisite."); diff --git a/libyul/optimiser/DeadCodeEliminator.h b/libyul/optimiser/DeadCodeEliminator.h index 50dbaaa75..f0fe9c647 100644 --- a/libyul/optimiser/DeadCodeEliminator.h +++ b/libyul/optimiser/DeadCodeEliminator.h @@ -29,6 +29,7 @@ namespace yul { struct Dialect; +struct OptimiserStepContext; /** * Optimisation stage that removes unreachable code @@ -47,13 +48,16 @@ struct Dialect; class DeadCodeEliminator: public ASTModifier { public: - DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {} + static constexpr char const* name{"DeadCodeEliminator"}; + static void run(OptimiserStepContext&, Block& _ast); using ASTModifier::operator(); void operator()(ForLoop& _for) override; void operator()(Block& _block) override; private: + DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {} + Dialect const& m_dialect; }; diff --git a/libyul/optimiser/EquivalentFunctionCombiner.cpp b/libyul/optimiser/EquivalentFunctionCombiner.cpp index c9b34436b..d7b3e62a6 100644 --- a/libyul/optimiser/EquivalentFunctionCombiner.cpp +++ b/libyul/optimiser/EquivalentFunctionCombiner.cpp @@ -26,7 +26,7 @@ using namespace std; using namespace dev; using namespace yul; -void EquivalentFunctionCombiner::run(Block& _ast) +void EquivalentFunctionCombiner::run(OptimiserStepContext&, Block& _ast) { EquivalentFunctionCombiner{EquivalentFunctionDetector::run(_ast)}(_ast); } diff --git a/libyul/optimiser/EquivalentFunctionCombiner.h b/libyul/optimiser/EquivalentFunctionCombiner.h index 0c766ded4..0d120d679 100644 --- a/libyul/optimiser/EquivalentFunctionCombiner.h +++ b/libyul/optimiser/EquivalentFunctionCombiner.h @@ -21,11 +21,14 @@ #include #include +#include #include namespace yul { +struct OptimiserStepContext; + /** * Optimiser component that detects syntactically equivalent functions and replaces all calls to any of them by calls * to one particular of them. @@ -35,7 +38,8 @@ namespace yul class EquivalentFunctionCombiner: public ASTModifier { public: - static void run(Block& _ast); + static constexpr char const* name{"EquivalentFunctionCombiner"}; + static void run(OptimiserStepContext&, Block& _ast); using ASTModifier::operator(); void operator()(FunctionCall& _funCall) override; diff --git a/libyul/optimiser/ExpressionInliner.cpp b/libyul/optimiser/ExpressionInliner.cpp index a4ce64e4a..5b1064cc8 100644 --- a/libyul/optimiser/ExpressionInliner.cpp +++ b/libyul/optimiser/ExpressionInliner.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -32,13 +33,12 @@ using namespace std; using namespace dev; using namespace yul; -void ExpressionInliner::run() +void ExpressionInliner::run(OptimiserStepContext& _context, Block& _ast) { InlinableExpressionFunctionFinder funFinder; - funFinder(m_block); - m_inlinableFunctions = funFinder.inlinableFunctions(); - - (*this)(m_block); + funFinder(_ast); + ExpressionInliner inliner{_context.dialect, funFinder.inlinableFunctions()}; + inliner(_ast); } void ExpressionInliner::operator()(FunctionDefinition& _fun) diff --git a/libyul/optimiser/ExpressionInliner.h b/libyul/optimiser/ExpressionInliner.h index 6101bac83..16e48ceb5 100644 --- a/libyul/optimiser/ExpressionInliner.h +++ b/libyul/optimiser/ExpressionInliner.h @@ -30,6 +30,7 @@ namespace yul { struct Dialect; +struct OptimiserStepContext; /** * Optimiser component that modifies an AST in place, inlining functions that can be @@ -48,11 +49,8 @@ struct Dialect; class ExpressionInliner: public ASTModifier { public: - ExpressionInliner(Dialect const& _dialect, Block& _block): - m_block(_block), m_dialect(_dialect) - {} - - void run(); + static constexpr char const* name{"ExpressionInliner"}; + static void run(OptimiserStepContext&, Block& _ast); using ASTModifier::operator(); void operator()(FunctionDefinition& _fun) override; @@ -60,13 +58,18 @@ public: void visit(Expression& _expression) override; private: - std::map m_inlinableFunctions; + ExpressionInliner( + Dialect const& _dialect, + std::map const& _inlinableFunctions + ): m_dialect(_dialect), m_inlinableFunctions(_inlinableFunctions) + {} + + Dialect const& m_dialect; + std::map const& m_inlinableFunctions; + std::map m_varReplacements; /// Set of functions we are currently visiting inside. std::set m_currentFunctions; - - Block& m_block; - Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/ExpressionJoiner.cpp b/libyul/optimiser/ExpressionJoiner.cpp index 654e9b5a8..911d44de0 100644 --- a/libyul/optimiser/ExpressionJoiner.cpp +++ b/libyul/optimiser/ExpressionJoiner.cpp @@ -34,6 +34,12 @@ using namespace std; using namespace dev; using namespace yul; +void ExpressionJoiner::run(OptimiserStepContext&, Block& _ast) +{ + ExpressionJoiner{_ast}(_ast); +} + + void ExpressionJoiner::operator()(FunctionalInstruction& _instruction) { handleArguments(_instruction.arguments); @@ -78,11 +84,6 @@ void ExpressionJoiner::visit(Expression& _e) ASTModifier::visit(_e); } -void ExpressionJoiner::run(Block& _ast) -{ - ExpressionJoiner{_ast}(_ast); -} - ExpressionJoiner::ExpressionJoiner(Block& _ast) { m_references = ReferencesCounter::countReferences(_ast); diff --git a/libyul/optimiser/ExpressionJoiner.h b/libyul/optimiser/ExpressionJoiner.h index 643d62b0a..e3b496b7c 100644 --- a/libyul/optimiser/ExpressionJoiner.h +++ b/libyul/optimiser/ExpressionJoiner.h @@ -29,6 +29,7 @@ namespace yul { class NameCollector; +struct OptimiserStepContext; /** @@ -70,7 +71,8 @@ class NameCollector; class ExpressionJoiner: public ASTModifier { public: - static void run(Block& _ast); + static constexpr char const* name{"ExpressionJoiner"}; + static void run(OptimiserStepContext&, Block& _ast); private: explicit ExpressionJoiner(Block& _ast); diff --git a/libyul/optimiser/ExpressionSimplifier.cpp b/libyul/optimiser/ExpressionSimplifier.cpp index defe3598a..ce896e649 100644 --- a/libyul/optimiser/ExpressionSimplifier.cpp +++ b/libyul/optimiser/ExpressionSimplifier.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include @@ -31,6 +31,11 @@ using namespace std; using namespace dev; using namespace yul; +void ExpressionSimplifier::run(OptimiserStepContext& _context, Block& _ast) +{ + ExpressionSimplifier{_context.dialect}(_ast); +} + void ExpressionSimplifier::visit(Expression& _expression) { ASTModifier::visit(_expression); @@ -48,8 +53,3 @@ void ExpressionSimplifier::visit(Expression& _expression) _expression = match->action().toExpression(locationOf(_expression)); } } - -void ExpressionSimplifier::run(Dialect const& _dialect, Block& _ast) -{ - ExpressionSimplifier{_dialect}(_ast); -} diff --git a/libyul/optimiser/ExpressionSimplifier.h b/libyul/optimiser/ExpressionSimplifier.h index 1f371a8fc..a7dbb3b41 100644 --- a/libyul/optimiser/ExpressionSimplifier.h +++ b/libyul/optimiser/ExpressionSimplifier.h @@ -27,6 +27,7 @@ namespace yul { struct Dialect; +struct OptimiserStepContext; /** * Applies simplification rules to all expressions. @@ -41,10 +42,12 @@ struct Dialect; class ExpressionSimplifier: public DataFlowAnalyzer { public: + static constexpr char const* name{"ExpressionSimplifier"}; + static void run(OptimiserStepContext&, Block& _ast); + using ASTModifier::operator(); virtual void visit(Expression& _expression); - static void run(Dialect const& _dialect, Block& _ast); private: explicit ExpressionSimplifier(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} }; diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index 7eb099b8e..4c857f0a3 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -35,6 +36,11 @@ using namespace dev; using namespace langutil; using namespace yul; +void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast) +{ + ExpressionSplitter{_context.dialect, _context.dispenser}(_ast); +} + void ExpressionSplitter::operator()(FunctionalInstruction& _instruction) { for (auto& arg: _instruction.arguments | boost::adaptors::reversed) diff --git a/libyul/optimiser/ExpressionSplitter.h b/libyul/optimiser/ExpressionSplitter.h index a72d14ba4..7a2a97464 100644 --- a/libyul/optimiser/ExpressionSplitter.h +++ b/libyul/optimiser/ExpressionSplitter.h @@ -32,7 +32,7 @@ namespace yul class NameCollector; struct Dialect; - +struct OptimiserStepContext; /** * Optimiser component that modifies an AST in place, turning complex @@ -58,9 +58,8 @@ struct Dialect; class ExpressionSplitter: public ASTModifier { public: - explicit ExpressionSplitter(Dialect const& _dialect, NameDispenser& _nameDispenser): - m_dialect(_dialect), m_nameDispenser(_nameDispenser) - { } + static constexpr char const* name{"ExpressionSplitter"}; + static void run(OptimiserStepContext&, Block& _ast); void operator()(FunctionalInstruction&) override; void operator()(FunctionCall&) override; @@ -70,6 +69,10 @@ public: void operator()(Block& _block) override; private: + explicit ExpressionSplitter(Dialect const& _dialect, NameDispenser& _nameDispenser): + m_dialect(_dialect), m_nameDispenser(_nameDispenser) + { } + /// Replaces the expression by a variable if it is a function call or functional /// instruction. The declaration of the variable is appended to m_statementsToPrefix. /// Recurses via visit(). diff --git a/libyul/optimiser/ForLoopConditionIntoBody.cpp b/libyul/optimiser/ForLoopConditionIntoBody.cpp index b169d0a51..ca53b8d6c 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.cpp +++ b/libyul/optimiser/ForLoopConditionIntoBody.cpp @@ -16,6 +16,7 @@ */ #include +#include #include #include @@ -23,9 +24,14 @@ using namespace std; using namespace dev; using namespace yul; +void ForLoopConditionIntoBody::run(OptimiserStepContext& _context, Block& _ast) +{ + ForLoopConditionIntoBody{_context.dialect}(_ast); +} + void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) { - if (_forLoop.condition->type() != typeid(Literal)) + if (m_dialect.booleanNegationFunction() && _forLoop.condition->type() != typeid(Literal)) { langutil::SourceLocation loc = locationOf(*_forLoop.condition); _forLoop.body.statements.insert( @@ -33,9 +39,9 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) If { loc, make_unique( - FunctionalInstruction { + FunctionCall { loc, - eth::Instruction::ISZERO, + {loc, m_dialect.booleanNegationFunction()->name}, make_vector(std::move(*_forLoop.condition)) } ), diff --git a/libyul/optimiser/ForLoopConditionIntoBody.h b/libyul/optimiser/ForLoopConditionIntoBody.h index bbb1b47c2..e15c502c8 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.h +++ b/libyul/optimiser/ForLoopConditionIntoBody.h @@ -17,10 +17,13 @@ #pragma once #include +#include namespace yul { +struct OptimiserStepContext; + /** * Rewrites ForLoop by moving iteration condition into the ForLoop body. * For example, `for {} lt(a, b) {} { mstore(1, 2) }` will become @@ -29,17 +32,26 @@ namespace yul * By moving the iteration check part into the ForLoop body, we can apply expression splitter * to the condition expression. * - * This rewritter will skip loops that already have literal constant as iteration condition. + * This rewriter will skip loops that already have literal constant as iteration condition. * * Requirements: * - The Disambiguator must be run upfront. * - To avoid unnecessary rewrite, it is recommended to run this rewriter after StructuralSimplifier. + * - Only works for dialects with a builtin boolean negation function. */ class ForLoopConditionIntoBody: public ASTModifier { public: + static constexpr char const* name{"ForLoopConditionIntoBody"}; + static void run(OptimiserStepContext&, Block& _ast); + using ASTModifier::operator(); void operator()(ForLoop& _forLoop) override; + +private: + ForLoopConditionIntoBody(Dialect const& _dialect): m_dialect(_dialect) {} + + Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/ForLoopConditionOutOfBody.cpp b/libyul/optimiser/ForLoopConditionOutOfBody.cpp new file mode 100644 index 000000000..e3d0f13ff --- /dev/null +++ b/libyul/optimiser/ForLoopConditionOutOfBody.cpp @@ -0,0 +1,74 @@ +/* + 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 + +using namespace std; +using namespace dev; +using namespace yul; + +void ForLoopConditionOutOfBody::run(OptimiserStepContext& _context, Block& _ast) +{ + ForLoopConditionOutOfBody{_context.dialect}(_ast); +} + +void ForLoopConditionOutOfBody::operator()(ForLoop& _forLoop) +{ + ASTModifier::operator()(_forLoop); + + if ( + !m_dialect.booleanNegationFunction() || + _forLoop.condition->type() != typeid(Literal) || + valueOfLiteral(boost::get(*_forLoop.condition)) == u256(0) || + _forLoop.body.statements.empty() || + _forLoop.body.statements.front().type() != typeid(If) + ) + return; + + If& firstStatement = boost::get(_forLoop.body.statements.front()); + if ( + firstStatement.body.statements.empty() || + firstStatement.body.statements.front().type() != typeid(Break) + ) + return; + if (!SideEffectsCollector(m_dialect, *firstStatement.condition).movable()) + return; + + YulString iszero = m_dialect.booleanNegationFunction()->name; + langutil::SourceLocation location = locationOf(*firstStatement.condition); + + if ( + firstStatement.condition->type() == typeid(FunctionCall) && + boost::get(*firstStatement.condition).functionName.name == iszero + ) + _forLoop.condition = make_unique(std::move(boost::get(*firstStatement.condition).arguments.front())); + else + _forLoop.condition = make_unique(FunctionCall{ + location, + Identifier{location, iszero}, + make_vector( + std::move(*firstStatement.condition) + ) + }); + + _forLoop.body.statements.erase(_forLoop.body.statements.begin()); +} + diff --git a/libyul/optimiser/ForLoopConditionOutOfBody.h b/libyul/optimiser/ForLoopConditionOutOfBody.h new file mode 100644 index 000000000..8945bf752 --- /dev/null +++ b/libyul/optimiser/ForLoopConditionOutOfBody.h @@ -0,0 +1,74 @@ +/* + 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 +{ + +/** + * Reverses the transformation of ForLoopConditionIntoBody. + * + * For any movable ``c``, it turns + * + * for { ... } 1 { ... } { + * if iszero(c) { break } + * ... + * } + * + * into + * + * for { ... } c { ... } { + * ... + * } + * + * and it turns + * + * for { ... } 1 { ... } { + * if c { break } + * ... + * } + * + * into + * + * for { ... } iszero(c) { ... } { + * ... + * } + * + * The LiteralRematerialiser should be run before this step. + */ +class ForLoopConditionOutOfBody: public ASTModifier +{ +public: + static constexpr char const* name{"ForLoopConditionOutOfBody"}; + static void run(OptimiserStepContext&, Block& _ast); + + using ASTModifier::operator(); + void operator()(ForLoop& _forLoop) override; + +private: + ForLoopConditionOutOfBody(Dialect const& _dialect): + m_dialect(_dialect) + {} + + Dialect const& m_dialect; +}; + +} diff --git a/libyul/optimiser/ForLoopInitRewriter.h b/libyul/optimiser/ForLoopInitRewriter.h index e925c6c2e..a36f76fdd 100644 --- a/libyul/optimiser/ForLoopInitRewriter.h +++ b/libyul/optimiser/ForLoopInitRewriter.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace yul { @@ -29,8 +30,17 @@ namespace yul class ForLoopInitRewriter: public ASTModifier { public: + static constexpr char const* name{"ForLoopInitRewriter"}; + static void run(OptimiserStepContext&, Block& _ast) + { + ForLoopInitRewriter{}(_ast); + } + using ASTModifier::operator(); void operator()(Block& _block) override; + +private: + ForLoopInitRewriter() = default; }; } diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index f764b0b1e..437758481 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -38,6 +38,11 @@ using namespace std; using namespace dev; using namespace yul; +void FullInliner::run(OptimiserStepContext& _context, Block& _ast) +{ + FullInliner{_ast, _context.dispenser}.run(); +} + FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): m_ast(_ast), m_nameDispenser(_dispenser) { diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index 43ecddbfc..4f1ea9d3c 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -69,9 +70,8 @@ class NameCollector; class FullInliner: public ASTModifier { public: - explicit FullInliner(Block& _ast, NameDispenser& _dispenser); - - void run(); + static constexpr char const* name{"FullInliner"}; + static void run(OptimiserStepContext&, Block& _ast); /// Inlining heuristic. /// @param _callSite the name of the function in which the function call is located. @@ -91,6 +91,9 @@ public: void tentativelyUpdateCodeSize(YulString _function, YulString _callSite); private: + FullInliner(Block& _ast, NameDispenser& _dispenser); + void run(); + void updateCodeSize(FunctionDefinition const& _fun); void handleBlock(YulString _currentFunctionName, Block& _block); bool recursive(FunctionDefinition const& _fun) const; diff --git a/libyul/optimiser/FunctionGrouper.h b/libyul/optimiser/FunctionGrouper.h index 4b6abf761..2e36c1be6 100644 --- a/libyul/optimiser/FunctionGrouper.h +++ b/libyul/optimiser/FunctionGrouper.h @@ -26,6 +26,8 @@ namespace yul { +struct OptimiserStepContext; + /** * Moves all instructions in a block into a new block at the start of the block, followed by * all function definitions. @@ -37,9 +39,14 @@ namespace yul class FunctionGrouper { public: + static constexpr char const* name{"FunctionGrouper"}; + static void run(OptimiserStepContext&, Block& _ast) { FunctionGrouper{}(_ast); } + void operator()(Block& _block); private: + FunctionGrouper() = default; + bool alreadyGrouped(Block const& _block); }; diff --git a/libyul/optimiser/FunctionHoister.h b/libyul/optimiser/FunctionHoister.h index 310920696..4807d7625 100644 --- a/libyul/optimiser/FunctionHoister.h +++ b/libyul/optimiser/FunctionHoister.h @@ -26,6 +26,7 @@ namespace yul { +struct OptimiserStepContext; /** * Moves all functions to the top-level scope. @@ -37,10 +38,15 @@ namespace yul class FunctionHoister: public ASTModifier { public: + static constexpr char const* name{"FunctionHoister"}; + static void run(OptimiserStepContext&, Block& _ast) { FunctionHoister{}(_ast); } + using ASTModifier::operator(); virtual void operator()(Block& _block); private: + FunctionHoister() = default; + bool m_isTopLevel = true; std::vector m_functions; }; diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp index b33963e94..184214254 100644 --- a/libyul/optimiser/LoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -23,44 +23,64 @@ #include #include +#include +#include #include using namespace std; using namespace dev; using namespace yul; -void LoadResolver::run(Dialect const& _dialect, Block& _ast) +void LoadResolver::run(OptimiserStepContext& _context, Block& _ast) { - bool containsMSize = SideEffectsCollector(_dialect, _ast).containsMSize(); - LoadResolver{_dialect, !containsMSize}(_ast); + bool containsMSize = MSizeFinder::containsMSize(_context.dialect, _ast); + LoadResolver{ + _context.dialect, + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)), + !containsMSize + }(_ast); } void LoadResolver::visit(Expression& _e) { + DataFlowAnalyzer::visit(_e); + + if (!dynamic_cast(&m_dialect)) + return; + if (_e.type() == typeid(FunctionCall)) { FunctionCall const& funCall = boost::get(_e); if (auto const* builtin = dynamic_cast(m_dialect).builtin(funCall.functionName.name)) - if (!builtin->parameters.empty() && funCall.arguments.at(0).type() == typeid(Identifier)) - { - YulString key = boost::get(funCall.arguments.at(0)).name; - if ( - builtin->instruction == dev::eth::Instruction::SLOAD && - m_storage.values.count(key) - ) - { - _e = Identifier{locationOf(_e), m_storage.values[key]}; - return; - } - else if ( - m_optimizeMLoad && - builtin->instruction == dev::eth::Instruction::MLOAD && - m_memory.values.count(key) - ) - { - _e = Identifier{locationOf(_e), m_memory.values[key]}; - return; - } - } + if (builtin->instruction) + tryResolve(_e, *builtin->instruction, funCall.arguments); + } + else if (_e.type() == typeid(FunctionalInstruction)) + { + FunctionalInstruction const& instruction = boost::get(_e); + tryResolve(_e, instruction.instruction, instruction.arguments); } } + +void LoadResolver::tryResolve( + Expression& _e, + dev::eth::Instruction _instruction, + vector const& _arguments +) +{ + if (_arguments.empty() || _arguments.at(0).type() != typeid(Identifier)) + return; + + YulString key = boost::get(_arguments.at(0)).name; + if ( + _instruction == dev::eth::Instruction::SLOAD && + m_storage.values.count(key) + ) + _e = Identifier{locationOf(_e), m_storage.values[key]}; + else if ( + m_optimizeMLoad && + _instruction == dev::eth::Instruction::MLOAD && + m_memory.values.count(key) + ) + _e = Identifier{locationOf(_e), m_memory.values[key]}; +} diff --git a/libyul/optimiser/LoadResolver.h b/libyul/optimiser/LoadResolver.h index 3db124594..52ac935dd 100644 --- a/libyul/optimiser/LoadResolver.h +++ b/libyul/optimiser/LoadResolver.h @@ -22,11 +22,14 @@ #pragma once #include +#include +#include namespace yul { struct EVMDialect; +struct BuiltinFunctionForEVM; /** * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value @@ -39,11 +42,17 @@ struct EVMDialect; class LoadResolver: public DataFlowAnalyzer { public: - static void run(Dialect const& _dialect, Block& _ast); + static constexpr char const* name{"LoadResolver"}; + /// Run the load resolver on the given complete AST. + static void run(OptimiserStepContext&, Block& _ast); private: - LoadResolver(Dialect const& _dialect, bool _optimizeMLoad): - DataFlowAnalyzer(_dialect), + LoadResolver( + Dialect const& _dialect, + std::map _functionSideEffects, + bool _optimizeMLoad + ): + DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)), m_optimizeMLoad(_optimizeMLoad) {} @@ -51,6 +60,12 @@ protected: using ASTModifier::visit; void visit(Expression& _e) override; + void tryResolve( + Expression& _e, + dev::eth::Instruction _instruction, + std::vector const& _arguments + ); + bool m_optimizeMLoad = false; }; diff --git a/libyul/optimiser/MainFunction.h b/libyul/optimiser/MainFunction.h index 96acc0ac8..03e94a51f 100644 --- a/libyul/optimiser/MainFunction.h +++ b/libyul/optimiser/MainFunction.h @@ -26,13 +26,21 @@ namespace yul { +struct OptimiserStepContext; + /** * Prerequisites: Function Grouper */ class MainFunction { public: + static constexpr char const* name{"MainFunction"}; + static void run(OptimiserStepContext&, Block& _ast) { MainFunction{}(_ast); } + void operator()(Block& _block); + +private: + MainFunction() = default; }; } diff --git a/libyul/optimiser/OptimiserStep.h b/libyul/optimiser/OptimiserStep.h new file mode 100644 index 000000000..485a7b422 --- /dev/null +++ b/libyul/optimiser/OptimiserStep.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 +#include + +namespace yul +{ + +struct Dialect; +struct Block; +class YulString; +class NameDispenser; + +struct OptimiserStepContext +{ + Dialect const& dialect; + NameDispenser& dispenser; + std::set const& reservedIdentifiers; +}; + + +/** + * Construction to create dynamically callable objects out of the + * statically callable optimiser steps. + */ +struct OptimiserStep +{ + explicit OptimiserStep(std::string _name): name(std::move(_name)) {} + virtual ~OptimiserStep() = default; + + virtual void run(OptimiserStepContext&, Block&) const = 0; + std::string name; +}; + +template +struct OptimiserStepInstance: public OptimiserStep +{ + OptimiserStepInstance(): OptimiserStep{Step::name} {} + void run(OptimiserStepContext& _context, Block& _ast) const override + { + Step::run(_context, _ast); + } +}; + + +} diff --git a/libyul/optimiser/README.md b/libyul/optimiser/README.md index dfd09184c..58cc006b4 100644 --- a/libyul/optimiser/README.md +++ b/libyul/optimiser/README.md @@ -119,8 +119,9 @@ so that other components can more easily work with it. The final representation will be similar to a static-single-assignment (SSA) form, with the difference that it does not make use of explicit "phi" functions which combines the values from different branches of control flow because such a feature does not exist -in the Yul language. Instead, assignments to existing variables are -used. +in the Yul language. Instead, when control flow merges, if a variable is re-assigned +in one of the branches, a new SSA variable is declared to hold its current value, +so that the following expressions still only need to reference SSA variables. An example transformation is the following: @@ -139,21 +140,25 @@ as follows: { let _1 := 0 - let a_1 := calldataload(_1) + let a_9 := calldataload(_1) + let a := a_9 let _2 := 0x20 - let b_1 := calldataload(_2) - let b := b_1 + let b_10 := calldataload(_2) + let b := b_10 let _3 := 0 - let _4 := gt(a_1, _3) - if _4 { + let _4 := gt(a_9, _3) + if _4 + { let _5 := 0x20 - let b_2 := mul(b_1, _5) - b := b_2 + let b_11 := mul(b_10, _5) + b := b_11 } - let a_2 := add(a_1, 1) - let _6 := 0x20 - let _7 := add(b, _6) - sstore(a_2, _7) + let b_12 := b + let _6 := 1 + let a_13 := add(a_9, _6) + let _7 := 0x20 + let _8 := add(b_12, _7) + sstore(a_13, _8) } Note that the only variable that is re-assigned in this snippet is ``b``. @@ -240,6 +245,10 @@ reference to ``a`` by ``a_i``. The current value mapping is cleared for a variable ``a`` at the end of each block in which it was assigned to and at the end of the for loop init block if it is assigned inside the for loop body or post block. +If a variable's value is cleared according to the rule above and the variable is declared outside +the block, a new SSA variable will be created at the location where control flow joins, +this includes the beginning of loop post/body block and the location right after +If/Switch/ForLoop/Block statement. After this stage, the Redundant Assign Eliminator is recommended to remove the unnecessary intermediate assignments. diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 8baa4e7dc..16383d307 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -32,6 +32,15 @@ using namespace std; using namespace dev; using namespace yul; +void RedundantAssignEliminator::run(OptimiserStepContext& _context, Block& _ast) +{ + RedundantAssignEliminator rae{_context.dialect}; + rae(_ast); + + AssignmentRemover remover{rae.m_pendingRemovals}; + remover(_ast); +} + void RedundantAssignEliminator::operator()(Identifier const& _identifier) { changeUndecidedTo(_identifier.name, State::Used); @@ -204,14 +213,6 @@ void RedundantAssignEliminator::operator()(Block const& _block) swap(m_declaredVariables, outerDeclaredVariables); } -void RedundantAssignEliminator::run(Dialect const& _dialect, Block& _ast) -{ - RedundantAssignEliminator rae{_dialect}; - rae(_ast); - - AssignmentRemover remover{rae.m_pendingRemovals}; - remover(_ast); -} template void joinMap(std::map& _a, std::map&& _b, F _conflictSolver) diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index ecea0fefa..65ddc19ae 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -105,6 +106,9 @@ struct Dialect; class RedundantAssignEliminator: public ASTWalker { public: + static constexpr char const* name{"RedundantAssignEliminator"}; + static void run(OptimiserStepContext&, Block& _ast); + explicit RedundantAssignEliminator(Dialect const& _dialect): m_dialect(&_dialect) {} RedundantAssignEliminator() = delete; RedundantAssignEliminator(RedundantAssignEliminator const&) = delete; @@ -123,8 +127,6 @@ public: void operator()(Continue const&) override; void operator()(Block const& _block) override; - static void run(Dialect const& _dialect, Block& _ast); - private: class State { diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index e11ed98f3..c9088a431 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -71,9 +71,9 @@ void Rematerialiser::visit(Expression& _e) if (_e.type() == typeid(Identifier)) { Identifier& identifier = boost::get(_e); - if (m_value.count(identifier.name)) + YulString name = identifier.name; + if (m_value.count(name)) { - YulString name = identifier.name; assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); size_t refs = m_referenceCounts[name]; @@ -93,3 +93,20 @@ void Rematerialiser::visit(Expression& _e) } DataFlowAnalyzer::visit(_e); } + +void LiteralRematerialiser::visit(Expression& _e) +{ + if (_e.type() == typeid(Identifier)) + { + Identifier& identifier = boost::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)) + _e = *value; + } + } + DataFlowAnalyzer::visit(_e); +} diff --git a/libyul/optimiser/Rematerialiser.h b/libyul/optimiser/Rematerialiser.h index 6871cdaa9..acf48d669 100644 --- a/libyul/optimiser/Rematerialiser.h +++ b/libyul/optimiser/Rematerialiser.h @@ -21,6 +21,7 @@ #pragma once #include +#include namespace yul { @@ -38,6 +39,12 @@ namespace yul class Rematerialiser: public DataFlowAnalyzer { public: + static constexpr char const* name{"Rematerialiser"}; + static void run( + OptimiserStepContext& _context, + Block& _ast + ) { run(_context.dialect, _ast); } + static void run( Dialect const& _dialect, Block& _ast, @@ -68,4 +75,32 @@ protected: std::set m_varsToAlwaysRematerialize; }; +/** + * If a variable is referenced that is known to have a literal + * value at that point, replace it by a literal. + * + * This is mostly used so that other components do not have to rely + * on the data flow analyzer. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class LiteralRematerialiser: public DataFlowAnalyzer +{ +public: + static constexpr char const* name{"LiteralRematerialiser"}; + static void run( + OptimiserStepContext& _context, + Block& _ast + ) { LiteralRematerialiser{_context.dialect}(_ast); } + + using ASTModifier::visit; + void visit(Expression& _e) override; + +private: + LiteralRematerialiser(Dialect const& _dialect): + DataFlowAnalyzer(_dialect) + {} +}; + + } diff --git a/libyul/optimiser/SSAReverser.cpp b/libyul/optimiser/SSAReverser.cpp index a9a49fdb6..d5f989d18 100644 --- a/libyul/optimiser/SSAReverser.cpp +++ b/libyul/optimiser/SSAReverser.cpp @@ -23,7 +23,7 @@ using namespace std; using namespace dev; using namespace yul; -void SSAReverser::run(Block& _block) +void SSAReverser::run(OptimiserStepContext&, Block& _block) { AssignmentCounter assignmentCounter; assignmentCounter(_block); @@ -56,18 +56,24 @@ void SSAReverser::operator()(Block& _block) identifier && identifier->name == varDecl->variables.front().name ) - return make_vector( - Assignment{ - std::move(assignment->location), - assignment->variableNames, - std::move(varDecl->value) - }, - VariableDeclaration{ - std::move(varDecl->location), - std::move(varDecl->variables), - std::make_unique(std::move(assignment->variableNames.front())) - } - ); + { + // in the special case a == a_1, just remove the assignment + if (assignment->variableNames.front().name == identifier->name) + return make_vector(std::move(_stmt1)); + else + return make_vector( + Assignment{ + std::move(assignment->location), + assignment->variableNames, + std::move(varDecl->value) + }, + VariableDeclaration{ + std::move(varDecl->location), + std::move(varDecl->variables), + std::make_unique(std::move(assignment->variableNames.front())) + } + ); + } } // Replaces // let a_1 := E diff --git a/libyul/optimiser/SSAReverser.h b/libyul/optimiser/SSAReverser.h index 67abeb568..a0425a9ea 100644 --- a/libyul/optimiser/SSAReverser.h +++ b/libyul/optimiser/SSAReverser.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace yul { @@ -40,6 +41,13 @@ class AssignmentCounter; * a := E * let a_1 := a * + * In the special case + * let a := E + * a := a + * + * the redundant assignment "a := a" is removed. + * + * * Secondly, the SSA transform will rewrite * * let a := E @@ -62,12 +70,15 @@ class AssignmentCounter; class SSAReverser: public ASTModifier { public: + static constexpr char const* name{"SSAReverser"}; + static void run(OptimiserStepContext& _context, Block& _ast); + using ASTModifier::operator(); void operator()(Block& _block) override; - static void run(Block& _block); private: - SSAReverser(AssignmentCounter const& _assignmentCounter): m_assignmentCounter(_assignmentCounter) {} + explicit SSAReverser(AssignmentCounter const& _assignmentCounter): m_assignmentCounter(_assignmentCounter) {} + AssignmentCounter const& m_assignmentCounter; }; diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp index 68726c486..9d9175754 100644 --- a/libyul/optimiser/SSATransform.cpp +++ b/libyul/optimiser/SSATransform.cpp @@ -32,45 +32,30 @@ using namespace dev; using namespace langutil; using namespace yul; -void SSATransform::operator()(Identifier& _identifier) +namespace { - if (m_currentVariableValues.count(_identifier.name)) - _identifier.name = m_currentVariableValues[_identifier.name]; -} -void SSATransform::operator()(ForLoop& _for) +/** + * First step of SSA transform: Introduces new SSA variables for each assignment or + * declaration of a variable to be replaced. + */ +class IntroduceSSA: public ASTModifier { - // This will clear the current value in case of a reassignment inside the - // init part, although the new variable would still be in scope inside the whole loop. - // This small inefficiency is fine if we move the pre part of all for loops out - // of the for loop. - (*this)(_for.pre); +public: + explicit IntroduceSSA(NameDispenser& _nameDispenser, set const& _variablesToReplace): + m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace) + { } - Assignments assignments; - assignments(_for.body); - assignments(_for.post); - for (auto const& var: assignments.names()) - m_currentVariableValues.erase(var); + void operator()(Block& _block) override; - visit(*_for.condition); - (*this)(_for.body); - (*this)(_for.post); -} +private: + NameDispenser& m_nameDispenser; + set const& m_variablesToReplace; +}; -void SSATransform::operator()(Block& _block) +void IntroduceSSA::operator()(Block& _block) { - set variablesToClearAtEnd; - - // Creates a new variable and stores it in the current variable value map. - auto newVariable = [&](YulString _varName) -> YulString - { - YulString newName = m_nameDispenser.newName(_varName); - m_currentVariableValues[_varName] = newName; - variablesToClearAtEnd.emplace(_varName); - return newName; - }; - iterateReplacing( _block.statements, [&](Statement& _s) -> boost::optional> @@ -96,8 +81,8 @@ void SSATransform::operator()(Block& _block) TypedNameList newVariables; for (auto const& var: varDecl.variables) { - YulString newName = newVariable(var.name); YulString oldName = var.name; + YulString newName = m_nameDispenser.newName(oldName); newVariables.emplace_back(TypedName{loc, newName, {}}); statements.emplace_back(VariableDeclaration{ loc, @@ -123,8 +108,8 @@ void SSATransform::operator()(Block& _block) TypedNameList newVariables; for (auto const& var: assignment.variableNames) { - YulString newName = newVariable(var.name); YulString oldName = var.name; + YulString newName = m_nameDispenser.newName(oldName); newVariables.emplace_back(TypedName{loc, newName, {}}); statements.emplace_back(Assignment{ loc, @@ -140,14 +125,261 @@ void SSATransform::operator()(Block& _block) return {}; } ); - for (auto const& var: variablesToClearAtEnd) - m_currentVariableValues.erase(var); } -void SSATransform::run(Block& _ast, NameDispenser& _nameDispenser) +/** + * Second step of SSA transform: Introduces new SSA variables at each control-flow join + * and at the beginning of functions. + */ +class IntroduceControlFlowSSA: public ASTModifier +{ +public: + explicit IntroduceControlFlowSSA( + NameDispenser& _nameDispenser, + set const& _variablesToReplace + ): + m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace) + { } + + void operator()(FunctionDefinition& _function) override; + void operator()(ForLoop& _forLoop) override; + void operator()(Switch& _switch) override; + void operator()(Block& _block) override; + +private: + NameDispenser& m_nameDispenser; + set const& m_variablesToReplace; + /// Variables (that are to be replaced) currently in scope. + set m_variablesInScope; + /// Set of variables that do not have a specific value. + set m_variablesToReassign; +}; + +void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function) +{ + set varsInScope; + std::swap(varsInScope, m_variablesInScope); + set toReassign; + std::swap(toReassign, m_variablesToReassign); + + for (auto const& param: _function.parameters) + if (m_variablesToReplace.count(param.name)) + { + m_variablesInScope.insert(param.name); + m_variablesToReassign.insert(param.name); + } + + ASTModifier::operator()(_function); + + m_variablesInScope = std::move(varsInScope); + m_variablesToReassign = std::move(toReassign); +} + +void IntroduceControlFlowSSA::operator()(ForLoop& _for) +{ + (*this)(_for.pre); + + Assignments assignments; + assignments(_for.body); + assignments(_for.post); + + + for (auto const& var: assignments.names()) + if (m_variablesInScope.count(var)) + m_variablesToReassign.insert(var); + + (*this)(_for.body); + (*this)(_for.post); +} + +void IntroduceControlFlowSSA::operator()(Switch& _switch) +{ + yulAssert(m_variablesToReassign.empty(), ""); + + set toReassign; + for (auto& c: _switch.cases) + { + (*this)(c.body); + toReassign += m_variablesToReassign; + } + + m_variablesToReassign += toReassign; +} + +void IntroduceControlFlowSSA::operator()(Block& _block) +{ + set variablesDeclaredHere; + set assignedVariables; + + iterateReplacing( + _block.statements, + [&](Statement& _s) -> boost::optional> + { + vector toPrepend; + for (YulString toReassign: m_variablesToReassign) + { + YulString newName = m_nameDispenser.newName(toReassign); + toPrepend.emplace_back(VariableDeclaration{ + locationOf(_s), + {TypedName{locationOf(_s), newName, {}}}, + make_unique(Identifier{locationOf(_s), toReassign}) + }); + assignedVariables.insert(toReassign); + } + m_variablesToReassign.clear(); + + if (_s.type() == typeid(VariableDeclaration)) + { + VariableDeclaration& varDecl = boost::get(_s); + for (auto const& var: varDecl.variables) + if (m_variablesToReplace.count(var.name)) + { + variablesDeclaredHere.insert(var.name); + m_variablesInScope.insert(var.name); + } + } + else if (_s.type() == typeid(Assignment)) + { + Assignment& assignment = boost::get(_s); + for (auto const& var: assignment.variableNames) + if (m_variablesToReplace.count(var.name)) + assignedVariables.insert(var.name); + } + else + visit(_s); + + if (toPrepend.empty()) + return {}; + else + { + toPrepend.emplace_back(std::move(_s)); + return toPrepend; + } + } + ); + m_variablesToReassign += assignedVariables; + m_variablesInScope -= variablesDeclaredHere; + m_variablesToReassign -= variablesDeclaredHere; +} + +/** + * Third step of SSA transform: Replace the references to variables-to-be-replaced + * by their current values. + */ +class PropagateValues: public ASTModifier +{ +public: + explicit PropagateValues(set const& _variablesToReplace): + m_variablesToReplace(_variablesToReplace) + { } + + void operator()(Identifier& _identifier) override; + void operator()(VariableDeclaration& _varDecl) override; + void operator()(Assignment& _assignment) override; + void operator()(ForLoop& _for) override; + void operator()(Block& _block) override; + +private: + /// This is a set of all variables that are assigned to anywhere in the code. + /// Variables that are only declared but never re-assigned are not touched. + set const& m_variablesToReplace; + map m_currentVariableValues; + set m_clearAtEndOfBlock; +}; + +void PropagateValues::operator()(Identifier& _identifier) +{ + if (m_currentVariableValues.count(_identifier.name)) + _identifier.name = m_currentVariableValues[_identifier.name]; +} + +void PropagateValues::operator()(VariableDeclaration& _varDecl) +{ + ASTModifier::operator()(_varDecl); + + if (_varDecl.variables.size() != 1) + return; + + YulString variable = _varDecl.variables.front().name; + 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; + m_clearAtEndOfBlock.insert(variable); + } + else if (_varDecl.value && _varDecl.value->type() == typeid(Identifier)) + { + // `let a_1 := a` - assignment to SSA variable after a branch. + YulString value = boost::get(*_varDecl.value).name; + if (m_variablesToReplace.count(value)) + { + // This is safe because `a_1` is not a "variable to replace" and thus + // will not be re-assigned. + m_currentVariableValues[value] = variable; + m_clearAtEndOfBlock.insert(value); + } + } +} + + +void PropagateValues::operator()(Assignment& _assignment) +{ + visit(*_assignment.value); + + if (_assignment.variableNames.size() != 1) + return; + YulString name = _assignment.variableNames.front().name; + if (!m_variablesToReplace.count(name)) + return; + + yulAssert(_assignment.value && _assignment.value->type() == typeid(Identifier), ""); + m_currentVariableValues[name] = boost::get(*_assignment.value).name; + m_clearAtEndOfBlock.insert(name); +} + +void PropagateValues::operator()(ForLoop& _for) +{ + // This will clear the current value in case of a reassignment inside the + // init part, although the new variable would still be in scope inside the whole loop. + // This small inefficiency is fine if we move the pre part of all for loops out + // of the for loop. + (*this)(_for.pre); + + Assignments assignments; + assignments(_for.body); + assignments(_for.post); + + for (auto const& var: assignments.names()) + m_currentVariableValues.erase(var); + + visit(*_for.condition); + (*this)(_for.body); + (*this)(_for.post); +} + +void PropagateValues::operator()(Block& _block) +{ + set clearAtParentBlock = std::move(m_clearAtEndOfBlock); + m_clearAtEndOfBlock.clear(); + + ASTModifier::operator()(_block); + + for (auto const& var: m_clearAtEndOfBlock) + m_currentVariableValues.erase(var); + + m_clearAtEndOfBlock = std::move(clearAtParentBlock); +} + +} + +void SSATransform::run(OptimiserStepContext& _context, Block& _ast) { Assignments assignments; assignments(_ast); - SSATransform{_nameDispenser, assignments.names()}(_ast); + IntroduceSSA{_context.dispenser, assignments.names()}(_ast); + IntroduceControlFlowSSA{_context.dispenser, assignments.names()}(_ast); + PropagateValues{assignments.names()}(_ast); } + diff --git a/libyul/optimiser/SSATransform.h b/libyul/optimiser/SSATransform.h index 1a367afc7..938a23151 100644 --- a/libyul/optimiser/SSATransform.h +++ b/libyul/optimiser/SSATransform.h @@ -22,6 +22,9 @@ #include #include +#include + +#include #include @@ -61,8 +64,10 @@ class NameDispenser; * Furthermore, always note the current variable/value assigned to a and replace each * reference to a by this variable. * The current value mapping is cleared for a variable a at the end of each block - * in which it was assigned and just after the for loop init block if it is assigned - * inside the for loop. + * in which it was assigned. We compensate that by appending a declaration + * of the form of "let a_1 := a" right after the location where control flow joins so + * variable references can use the SSA variable. The only exception to this rule are + * for loop conditions, as we cannot insert a variable declaration there. * * After this stage, redundantAssignmentRemover is recommended to remove the unnecessary * intermediate assignments. @@ -70,27 +75,23 @@ class NameDispenser; * This stage provides best results if CSE is run right before it, because * then it does not generate excessive amounts of variables. * + * The transform is implemented in three stages. All stages are only concerned + * with variables that are assigned somewhere in the code (excluding declarations). + * The first stage inserts new SSA variables for each declaration and assignment of + * such variables. + * The second stage inserts new SSA variables at control flow joins. + * The last stage replaces references to variables that are assigned to somewhere in the + * code by their current SSA variable. + * * TODO Which transforms are required to keep this idempotent? + * + * Prerequisite: Disambiguator. */ class SSATransform: public ASTModifier { public: - void operator()(Identifier&) override; - void operator()(ForLoop&) override; - void operator()(Block& _block) override; - - static void run(Block& _ast, NameDispenser& _nameDispenser); - -private: - explicit SSATransform(NameDispenser& _nameDispenser, std::set const& _variablesToReplace): - m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace) - { } - - NameDispenser& m_nameDispenser; - /// This is a set of all variables that are assigned to anywhere in the code. - /// Variables that are only declared but never re-assigned are not touched. - std::set const& m_variablesToReplace; - std::map m_currentVariableValues; + static constexpr char const* name{"SSATransform"}; + static void run(OptimiserStepContext& _context, Block& _ast); }; } diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 154a63bb5..001193533 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -28,13 +28,19 @@ #include #include +#include using namespace std; using namespace dev; using namespace yul; -SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Expression const& _expression): - SideEffectsCollector(_dialect) + +SideEffectsCollector::SideEffectsCollector( + Dialect const& _dialect, + Expression const& _expression, + map const* _functionSideEffects +): + SideEffectsCollector(_dialect, _functionSideEffects) { visit(_expression); } @@ -45,8 +51,12 @@ SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement co visit(_statement); } -SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Block const& _ast): - SideEffectsCollector(_dialect) +SideEffectsCollector::SideEffectsCollector( + Dialect const& _dialect, + Block const& _ast, + map const* _functionSideEffects +): + SideEffectsCollector(_dialect, _functionSideEffects) { operator()(_ast); } @@ -55,47 +65,71 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr) { ASTWalker::operator()(_instr); - if (!eth::SemanticInformation::movable(_instr.instruction)) - m_movable = false; - if (!eth::SemanticInformation::sideEffectFree(_instr.instruction)) - m_sideEffectFree = false; - if (!eth::SemanticInformation::sideEffectFreeIfNoMSize(_instr.instruction)) - m_sideEffectFreeIfNoMSize = false; - if (_instr.instruction == eth::Instruction::MSIZE) - m_containsMSize = true; - if (eth::SemanticInformation::invalidatesStorage(_instr.instruction)) - m_invalidatesStorage = true; - if (eth::SemanticInformation::invalidatesMemory(_instr.instruction)) - m_invalidatesMemory = true; + m_sideEffects += EVMDialect::sideEffectsOfInstruction(_instr.instruction); } void SideEffectsCollector::operator()(FunctionCall const& _functionCall) { ASTWalker::operator()(_functionCall); - if (BuiltinFunction const* f = m_dialect.builtin(_functionCall.functionName.name)) - { - if (!f->movable) - m_movable = false; - if (!f->sideEffectFree) - m_sideEffectFree = false; - if (!f->sideEffectFreeIfNoMSize) - m_sideEffectFreeIfNoMSize = false; - if (f->isMSize) - m_containsMSize = true; - if (f->invalidatesStorage) - m_invalidatesStorage = true; - if (f->invalidatesMemory) - m_invalidatesMemory = true; - } + YulString functionName = _functionCall.functionName.name; + if (BuiltinFunction const* f = m_dialect.builtin(functionName)) + m_sideEffects += f->sideEffects; + else if (m_functionSideEffects && m_functionSideEffects->count(functionName)) + m_sideEffects += m_functionSideEffects->at(functionName); else + m_sideEffects += SideEffects::worst(); +} + +bool MSizeFinder::containsMSize(Dialect const& _dialect, Block const& _ast) +{ + MSizeFinder finder(_dialect); + finder(_ast); + return finder.m_msizeFound; +} + +void MSizeFinder::operator()(FunctionalInstruction const& _instr) +{ + ASTWalker::operator()(_instr); + + if (_instr.instruction == eth::Instruction::MSIZE) + m_msizeFound = true; +} + +void MSizeFinder::operator()(FunctionCall const& _functionCall) +{ + ASTWalker::operator()(_functionCall); + + if (BuiltinFunction const* f = m_dialect.builtin(_functionCall.functionName.name)) + if (f->isMSize) + m_msizeFound = true; +} + + +map SideEffectsPropagator::sideEffects( + Dialect const& _dialect, + map> const& _directCallGraph +) +{ + map ret; + for (auto const& call: _directCallGraph) { - m_movable = false; - m_sideEffectFree = false; - m_sideEffectFreeIfNoMSize = false; - m_invalidatesStorage = true; - m_invalidatesMemory = true; + YulString funName = call.first; + SideEffects sideEffects; + BreadthFirstSearch{call.second, {funName}}.run( + [&](YulString _function, auto&& _addChild) { + if (sideEffects == SideEffects::worst()) + return; + if (BuiltinFunction const* f = _dialect.builtin(_function)) + sideEffects += f->sideEffects; + else + for (YulString callee: _directCallGraph.at(_function)) + _addChild(callee); + } + ); + ret[funName] = sideEffects; } + return ret; } MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression): diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 14fa128c9..8953ab321 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -21,6 +21,8 @@ #pragma once #include +#include +#include #include @@ -35,46 +37,80 @@ struct Dialect; class SideEffectsCollector: public ASTWalker { public: - explicit SideEffectsCollector(Dialect const& _dialect): m_dialect(_dialect) {} - SideEffectsCollector(Dialect const& _dialect, Expression const& _expression); + explicit SideEffectsCollector( + Dialect const& _dialect, + std::map const* _functionSideEffects = nullptr + ): m_dialect(_dialect), m_functionSideEffects(_functionSideEffects) {} + SideEffectsCollector( + Dialect const& _dialect, + Expression const& _expression, + std::map const* _functionSideEffects = nullptr + ); SideEffectsCollector(Dialect const& _dialect, Statement const& _statement); - SideEffectsCollector(Dialect const& _dialect, Block const& _ast); + SideEffectsCollector( + Dialect const& _dialect, + Block const& _ast, + std::map const* _functionSideEffects = nullptr + ); using ASTWalker::operator(); void operator()(FunctionalInstruction const& _functionalInstruction) override; void operator()(FunctionCall const& _functionCall) override; - bool movable() const { return m_movable; } + bool movable() const { return m_sideEffects.movable; } bool sideEffectFree(bool _allowMSizeModification = false) const { if (_allowMSizeModification) return sideEffectFreeIfNoMSize(); else - return m_sideEffectFree; + return m_sideEffects.sideEffectFree; } - bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } - bool containsMSize() const { return m_containsMSize; } - bool invalidatesStorage() const { return m_invalidatesStorage; } - bool invalidatesMemory() const { return m_invalidatesMemory; } + bool sideEffectFreeIfNoMSize() const { return m_sideEffects.sideEffectFreeIfNoMSize; } + bool invalidatesStorage() const { return m_sideEffects.invalidatesStorage; } + bool invalidatesMemory() const { return m_sideEffects.invalidatesMemory; } private: Dialect const& m_dialect; - /// Is the current expression movable or not. - bool m_movable = true; - /// Is the current expression side-effect free, i.e. can be removed - /// without changing the semantics. - bool m_sideEffectFree = true; - /// Is the current expression side-effect free up to msize, i.e. can be removed - /// without changing the semantics except for the value returned by the msize instruction. - bool m_sideEffectFreeIfNoMSize = true; - /// Does the current code contain the MSize operation? - /// Note that this is a purely syntactic property meaning that even if this is false, - /// the code can still contain calls to functions that contain the msize instruction. - bool m_containsMSize = false; - /// If false, storage is guaranteed to be unchanged by the code under all - /// circumstances. - bool m_invalidatesStorage = false; - bool m_invalidatesMemory = false; + std::map const* m_functionSideEffects = nullptr; + SideEffects m_sideEffects; +}; + +/** + * This class can be used to determine the side-effects of user-defined functions. + * + * It is given a dialect and a mapping that represents the direct calls from user-defined + * functions to other user-defined functions and built-in functions. + */ +class SideEffectsPropagator +{ +public: + static std::map sideEffects( + Dialect const& _dialect, + std::map> const& _directCallGraph + ); +}; + +/** + * Class that can be used to find out if certain code contains the MSize instruction. + * + * Note that this is a purely syntactic property meaning that even if this is false, + * the code can still contain calls to functions that contain the msize instruction. + * + * The only safe way to determine this is by passing the full AST. + */ +class MSizeFinder: public ASTWalker +{ +public: + static bool containsMSize(Dialect const& _dialect, Block const& _ast); + + using ASTWalker::operator(); + void operator()(FunctionalInstruction const& _instr); + void operator()(FunctionCall const& _funCall); + +private: + MSizeFinder(Dialect const& _dialect): m_dialect(_dialect) {} + Dialect const& m_dialect; + bool m_msizeFound = false; }; /** @@ -85,7 +121,10 @@ private: class MovableChecker: public SideEffectsCollector { public: - explicit MovableChecker(Dialect const& _dialect): SideEffectsCollector(_dialect) {} + explicit MovableChecker( + Dialect const& _dialect, + std::map const* _functionSideEffects = nullptr + ): SideEffectsCollector(_dialect, _functionSideEffects) {} MovableChecker(Dialect const& _dialect, Expression const& _expression); void operator()(Identifier const& _identifier) override; diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 48e883701..3f5d8541b 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -97,15 +97,19 @@ SimplificationRules::SimplificationRules() Pattern B(PatternKind::Constant); Pattern C(PatternKind::Constant); // Anything. + Pattern W; Pattern X; Pattern Y; + Pattern Z; A.setMatchGroup(1, m_matchGroups); B.setMatchGroup(2, m_matchGroups); C.setMatchGroup(3, m_matchGroups); - X.setMatchGroup(4, m_matchGroups); - Y.setMatchGroup(5, m_matchGroups); + W.setMatchGroup(4, m_matchGroups); + X.setMatchGroup(5, m_matchGroups); + Y.setMatchGroup(6, m_matchGroups); + Z.setMatchGroup(7, m_matchGroups); - addRules(simplificationRuleList(A, B, C, X, Y)); + addRules(simplificationRuleList(A, B, C, W, X, Y, Z)); assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 3c417c2dd..72f63b76d 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -165,7 +165,7 @@ bool StackCompressor::run( _object.code->statements.size() > 0 && _object.code->statements.at(0).type() == typeid(Block), "Need to run the function grouper before the stack compressor." ); - bool allowMSizeOptimzation = !SideEffectsCollector(_dialect, *_object.code).containsMSize(); + bool allowMSizeOptimzation = !MSizeFinder::containsMSize(_dialect, *_object.code); for (size_t iterations = 0; iterations < _maxIterations; iterations++) { map stackSurplus = CompilabilityChecker::run(_dialect, _object, _optimizeStackAllocation); diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 98e70a3ee..d7bd36fe9 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -59,31 +59,14 @@ OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _const } -void StructuralSimplifier::operator()(Block& _block) +void StructuralSimplifier::run(OptimiserStepContext&, Block& _ast) { - pushScope(false); - simplify(_block.statements); - popScope(); + StructuralSimplifier{}(_ast); } -boost::optional StructuralSimplifier::hasLiteralValue(Expression const& _expression) const +void StructuralSimplifier::operator()(Block& _block) { - Expression const* expr = &_expression; - - if (expr->type() == typeid(Identifier)) - { - Identifier const& ident = boost::get(*expr); - if (m_value.count(ident.name)) - expr = m_value.at(ident.name); - } - - if (expr && expr->type() == typeid(Literal)) - { - Literal const& literal = boost::get(*expr); - return valueOfLiteral(literal); - } - - return boost::optional(); + simplify(_block.statements); } void StructuralSimplifier::simplify(std::vector& _statements) @@ -124,34 +107,24 @@ void StructuralSimplifier::simplify(std::vector& _statements) bool StructuralSimplifier::expressionAlwaysTrue(Expression const& _expression) { - return boost::apply_visitor(GenericFallbackReturnsVisitor( - [&](Identifier const& _identifier) -> bool { - if (auto expr = m_value[_identifier.name]) - return expressionAlwaysTrue(*expr); - return false; - }, - [](Literal const& _literal) -> bool { - return - (_literal.kind == LiteralKind::Boolean && _literal.value == "true"_yulstring) || - (_literal.kind == LiteralKind::Number && valueOfNumberLiteral(_literal) != u256(0)) - ; - } - ), _expression); + if (boost::optional value = hasLiteralValue(_expression)) + return *value != 0; + else + return false; } bool StructuralSimplifier::expressionAlwaysFalse(Expression const& _expression) { - return boost::apply_visitor(GenericFallbackReturnsVisitor( - [&](Identifier const& _identifier) -> bool { - if (auto expr = m_value[_identifier.name]) - return expressionAlwaysFalse(*expr); - return false; - }, - [](Literal const& _literal) -> bool { - return - (_literal.kind == LiteralKind::Boolean && _literal.value == "false"_yulstring) || - (_literal.kind == LiteralKind::Number && valueOfNumberLiteral(_literal) == u256(0)) - ; - } - ), _expression); + if (boost::optional value = hasLiteralValue(_expression)) + return *value == 0; + else + return false; +} + +boost::optional StructuralSimplifier::hasLiteralValue(Expression const& _expression) const +{ + if (_expression.type() == typeid(Literal)) + return valueOfLiteral(boost::get(_expression)); + else + return boost::optional(); } diff --git a/libyul/optimiser/StructuralSimplifier.h b/libyul/optimiser/StructuralSimplifier.h index a003abd6e..28917bff9 100644 --- a/libyul/optimiser/StructuralSimplifier.h +++ b/libyul/optimiser/StructuralSimplifier.h @@ -18,6 +18,7 @@ #include #include +#include #include namespace yul @@ -30,18 +31,23 @@ namespace yul * - replace switch with const expr with matching case body * - replace for with false condition by its initialization part * - * Prerequisite: Disambiguator, ForLoopInitRewriter. + * The LiteralRematerialiser should be run before this. + * + * Prerequisite: Disambiguator. * * Important: Can only be used on EVM code. */ -class StructuralSimplifier: public DataFlowAnalyzer +class StructuralSimplifier: public ASTModifier { public: - explicit StructuralSimplifier(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + static constexpr char const* name{"StructuralSimplifier"}; + static void run(OptimiserStepContext&, Block& _ast); - using DataFlowAnalyzer::operator(); + using ASTModifier::operator(); void operator()(Block& _block) override; private: + StructuralSimplifier() = default; + void simplify(std::vector& _statements); bool expressionAlwaysTrue(Expression const& _expression); bool expressionAlwaysFalse(Expression const& _expression); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 6c0fb0fbc..a23f1f24e 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -32,17 +33,21 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -78,24 +83,28 @@ void OptimiserSuite::run( )(*_object.code)); Block& ast = *_object.code; - VarDeclInitializer{}(ast); - FunctionHoister{}(ast); - BlockFlattener{}(ast); - ForLoopInitRewriter{}(ast); - DeadCodeEliminator{_dialect}(ast); - FunctionGrouper{}(ast); - EquivalentFunctionCombiner::run(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); - BlockFlattener{}(ast); - ControlFlowSimplifier{_dialect}(ast); - StructuralSimplifier{_dialect}(ast); - ControlFlowSimplifier{_dialect}(ast); - BlockFlattener{}(ast); + OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast); + + suite.runSequence({ + VarDeclInitializer::name, + FunctionHoister::name, + BlockFlattener::name, + ForLoopInitRewriter::name, + DeadCodeEliminator::name, + FunctionGrouper::name, + EquivalentFunctionCombiner::name, + UnusedPruner::name, + BlockFlattener::name, + ControlFlowSimplifier::name, + LiteralRematerialiser::name, + StructuralSimplifier::name, + ControlFlowSimplifier::name, + ForLoopConditionIntoBody::name, + BlockFlattener::name + }, ast); // None of the above can make stack problems worse. - NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; - size_t codeSize = 0; for (size_t rounds = 0; rounds < 12; ++rounds) { @@ -108,105 +117,138 @@ void OptimiserSuite::run( { // Turn into SSA and simplify - ExpressionSplitter{_dialect, dispenser}(ast); - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(_dialect, ast); - RedundantAssignEliminator::run(_dialect, ast); - - ExpressionSimplifier::run(_dialect, ast); - CommonSubexpressionEliminator{_dialect}(ast); + suite.runSequence({ + ExpressionSplitter::name, + SSATransform::name, + RedundantAssignEliminator::name, + RedundantAssignEliminator::name, + ExpressionSimplifier::name, + CommonSubexpressionEliminator::name, + LoadResolver::name + }, ast); } { // still in SSA, perform structural simplification - ControlFlowSimplifier{_dialect}(ast); - StructuralSimplifier{_dialect}(ast); - ControlFlowSimplifier{_dialect}(ast); - BlockFlattener{}(ast); - DeadCodeEliminator{_dialect}(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + suite.runSequence({ + LiteralRematerialiser::name, + ForLoopConditionOutOfBody::name, + ControlFlowSimplifier::name, + StructuralSimplifier::name, + ControlFlowSimplifier::name, + BlockFlattener::name, + DeadCodeEliminator::name, + ForLoopConditionIntoBody::name, + UnusedPruner::name + }, ast); } + { // simplify again - CommonSubexpressionEliminator{_dialect}(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + suite.runSequence({ + LoadResolver::name, + CommonSubexpressionEliminator::name, + UnusedPruner::name, + }, ast); } { // reverse SSA - SSAReverser::run(ast); - CommonSubexpressionEliminator{_dialect}(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + suite.runSequence({ + SSAReverser::name, + CommonSubexpressionEliminator::name, + UnusedPruner::name, - ExpressionJoiner::run(ast); - ExpressionJoiner::run(ast); + ExpressionJoiner::name, + ExpressionJoiner::name, + }, ast); } // should have good "compilability" property here. { // run functional expression inliner - ExpressionInliner(_dialect, ast).run(); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + suite.runSequence({ + ExpressionInliner::name, + UnusedPruner::name, + }, ast); } { // Turn into SSA again and simplify - ExpressionSplitter{_dialect, dispenser}(ast); - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(_dialect, ast); - RedundantAssignEliminator::run(_dialect, ast); - CommonSubexpressionEliminator{_dialect}(ast); + suite.runSequence({ + ExpressionSplitter::name, + SSATransform::name, + RedundantAssignEliminator::name, + RedundantAssignEliminator::name, + CommonSubexpressionEliminator::name, + LoadResolver::name, + }, ast); } { // run full inliner - FunctionGrouper{}(ast); - EquivalentFunctionCombiner::run(ast); - FullInliner{ast, dispenser}.run(); - BlockFlattener{}(ast); + suite.runSequence({ + FunctionGrouper::name, + EquivalentFunctionCombiner::name, + FullInliner::name, + BlockFlattener::name + }, ast); } { // SSA plus simplify - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(_dialect, ast); - RedundantAssignEliminator::run(_dialect, ast); - ExpressionSimplifier::run(_dialect, ast); - StructuralSimplifier{_dialect}(ast); - BlockFlattener{}(ast); - DeadCodeEliminator{_dialect}(ast); - ControlFlowSimplifier{_dialect}(ast); - CommonSubexpressionEliminator{_dialect}(ast); - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(_dialect, ast); - RedundantAssignEliminator::run(_dialect, ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); - CommonSubexpressionEliminator{_dialect}(ast); + suite.runSequence({ + SSATransform::name, + RedundantAssignEliminator::name, + RedundantAssignEliminator::name, + LoadResolver::name, + ExpressionSimplifier::name, + LiteralRematerialiser::name, + ForLoopConditionOutOfBody::name, + StructuralSimplifier::name, + BlockFlattener::name, + DeadCodeEliminator::name, + ControlFlowSimplifier::name, + CommonSubexpressionEliminator::name, + SSATransform::name, + RedundantAssignEliminator::name, + RedundantAssignEliminator::name, + ForLoopConditionIntoBody::name, + UnusedPruner::name, + CommonSubexpressionEliminator::name, + }, ast); } } // Make source short and pretty. - ExpressionJoiner::run(ast); - Rematerialiser::run(_dialect, ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); - ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); - ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + suite.runSequence({ + ExpressionJoiner::name, + Rematerialiser::name, + UnusedPruner::name, + ExpressionJoiner::name, + UnusedPruner::name, + ExpressionJoiner::name, + UnusedPruner::name, - SSAReverser::run(ast); - CommonSubexpressionEliminator{_dialect}(ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + SSAReverser::name, + CommonSubexpressionEliminator::name, + LiteralRematerialiser::name, + ForLoopConditionOutOfBody::name, + CommonSubexpressionEliminator::name, + UnusedPruner::name, - ExpressionJoiner::run(ast); - Rematerialiser::run(_dialect, ast); - UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + ExpressionJoiner::name, + Rematerialiser::name, + UnusedPruner::name, + }, ast); // This is a tuning parameter, but actually just prevents infinite loops. size_t stackCompressorMaxIterations = 16; - FunctionGrouper{}(ast); + suite.runSequence({ + FunctionGrouper::name + }, ast); // We ignore the return value because we will get a much better error // message once we perform code generation. StackCompressor::run( @@ -215,11 +257,16 @@ void OptimiserSuite::run( _optimizeStackAllocation, stackCompressorMaxIterations ); - BlockFlattener{}(ast); - DeadCodeEliminator{_dialect}(ast); - ControlFlowSimplifier{_dialect}(ast); + suite.runSequence({ + BlockFlattener::name, + DeadCodeEliminator::name, + ControlFlowSimplifier::name, + LiteralRematerialiser::name, + ForLoopConditionOutOfBody::name, + CommonSubexpressionEliminator::name, - FunctionGrouper{}(ast); + FunctionGrouper::name, + }, ast); if (EVMDialect const* dialect = dynamic_cast(&_dialect)) { @@ -233,7 +280,73 @@ void OptimiserSuite::run( if (ast.statements.size() > 1 && boost::get(ast.statements.front()).statements.empty()) ast.statements.erase(ast.statements.begin()); } - VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); + suite.runSequence({ + VarNameCleaner::name + }, ast); *_object.analysisInfo = AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object); } + +namespace +{ + + +template +map> optimiserStepCollection() +{ + map> ret; + for (unique_ptr& s: make_vector>( + (make_unique>())... + )) + { + yulAssert(!ret.count(s->name), ""); + ret[s->name] = std::move(s); + } + return ret; +} + +} + +map> const& OptimiserSuite::allSteps() +{ + static map> instance; + if (instance.empty()) + instance = optimiserStepCollection< + BlockFlattener, + CommonSubexpressionEliminator, + ControlFlowSimplifier, + DeadCodeEliminator, + EquivalentFunctionCombiner, + ExpressionInliner, + ExpressionJoiner, + ExpressionSimplifier, + ExpressionSplitter, + ForLoopConditionIntoBody, + ForLoopConditionOutOfBody, + ForLoopInitRewriter, + FullInliner, + FunctionGrouper, + FunctionHoister, + LiteralRematerialiser, + LoadResolver, + RedundantAssignEliminator, + Rematerialiser, + SSAReverser, + SSATransform, + StructuralSimplifier, + UnusedPruner, + VarDeclInitializer, + VarNameCleaner + >(); + return instance; +} + +void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) +{ + for (string const& step: _steps) + { + if (m_debug == Debug::PrintStep) + cout << "Running " << step << endl; + allSteps().at(step)->run(m_context, _ast); + } +} diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 137066bd9..f47574799 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -22,9 +22,13 @@ #include #include +#include +#include #include #include +#include +#include namespace yul { @@ -41,6 +45,11 @@ struct Object; class OptimiserSuite { public: + enum class Debug + { + None, + PrintStep + }; static void run( Dialect const& _dialect, GasMeter const* _meter, @@ -48,6 +57,26 @@ public: bool _optimizeStackAllocation, std::set const& _externallyUsedIdentifiers = {} ); + + void runSequence(std::vector const& _steps, Block& _ast); + + static std::map> const& allSteps(); + +private: + OptimiserSuite( + Dialect const& _dialect, + std::set const& _externallyUsedIdentifiers, + Debug _debug, + Block& _ast + ): + m_dispenser{_dialect, _ast, _externallyUsedIdentifiers}, + m_context{_dialect, m_dispenser, _externallyUsedIdentifiers}, + m_debug(_debug) + {} + + NameDispenser m_dispenser; + OptimiserStepContext m_context; + Debug m_debug; }; } diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 1ce3af0a0..f77c1f95c 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -20,12 +20,14 @@ #include +#include #include #include #include #include #include #include +#include #include @@ -37,10 +39,12 @@ UnusedPruner::UnusedPruner( Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, + map const* _functionSideEffects, set const& _externallyUsedFunctions ): m_dialect(_dialect), - m_allowMSizeOptimization(_allowMSizeOptimization) + m_allowMSizeOptimization(_allowMSizeOptimization), + m_functionSideEffects(_functionSideEffects) { m_references = ReferencesCounter::countReferences(_ast); for (auto const& f: _externallyUsedFunctions) @@ -88,7 +92,10 @@ void UnusedPruner::operator()(Block& _block) { if (!varDecl.value) statement = Block{std::move(varDecl.location), {}}; - else if (SideEffectsCollector(m_dialect, *varDecl.value).sideEffectFree(m_allowMSizeOptimization)) + else if ( + SideEffectsCollector(m_dialect, *varDecl.value, m_functionSideEffects). + sideEffectFree(m_allowMSizeOptimization) + ) { subtractReferences(ReferencesCounter::countReferences(*varDecl.value)); statement = Block{std::move(varDecl.location), {}}; @@ -104,7 +111,10 @@ void UnusedPruner::operator()(Block& _block) else if (statement.type() == typeid(ExpressionStatement)) { ExpressionStatement& exprStmt = boost::get(statement); - if (SideEffectsCollector(m_dialect, exprStmt.expression).sideEffectFree(m_allowMSizeOptimization)) + if ( + SideEffectsCollector(m_dialect, exprStmt.expression, m_functionSideEffects). + sideEffectFree(m_allowMSizeOptimization) + ) { subtractReferences(ReferencesCounter::countReferences(exprStmt.expression)); statement = Block{std::move(exprStmt.location), {}}; @@ -120,26 +130,31 @@ void UnusedPruner::runUntilStabilised( Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, + map const* _functionSideEffects, set const& _externallyUsedFunctions ) { while (true) { - UnusedPruner pruner(_dialect, _ast, _allowMSizeOptimization, _externallyUsedFunctions); + UnusedPruner pruner( + _dialect, _ast, _allowMSizeOptimization, _functionSideEffects, + _externallyUsedFunctions); pruner(_ast); if (!pruner.shouldRunAgain()) return; } } -void UnusedPruner::runUntilStabilised( +void UnusedPruner::runUntilStabilisedOnFullAST( Dialect const& _dialect, Block& _ast, set const& _externallyUsedFunctions ) { - bool allowMSizeOptimization = !SideEffectsCollector(_dialect, _ast).containsMSize(); - runUntilStabilised(_dialect, _ast, allowMSizeOptimization, _externallyUsedFunctions); + map functionSideEffects = + SideEffectsPropagator::sideEffects(_dialect, CallGraphGenerator::callGraph(_ast)); + bool allowMSizeOptimization = !MSizeFinder::containsMSize(_dialect, _ast); + runUntilStabilised(_dialect, _ast, allowMSizeOptimization, &functionSideEffects, _externallyUsedFunctions); } void UnusedPruner::runUntilStabilised( diff --git a/libyul/optimiser/UnusedPruner.h b/libyul/optimiser/UnusedPruner.h index 766b01e18..d0454243d 100644 --- a/libyul/optimiser/UnusedPruner.h +++ b/libyul/optimiser/UnusedPruner.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include @@ -29,6 +30,7 @@ namespace yul { struct Dialect; +struct SideEffects; /** * Optimisation stage that removes unused variables and functions and also @@ -46,18 +48,11 @@ struct Dialect; class UnusedPruner: public ASTModifier { public: - UnusedPruner( - Dialect const& _dialect, - Block& _ast, - bool _allowMSizeOptimization, - std::set const& _externallyUsedFunctions = {} - ); - UnusedPruner( - Dialect const& _dialect, - FunctionDefinition& _function, - bool _allowMSizeOptimization, - std::set const& _externallyUsedFunctions = {} - ); + static constexpr char const* name{"UnusedPruner"}; + static void run(OptimiserStepContext& _context, Block& _ast) { + UnusedPruner::runUntilStabilisedOnFullAST(_context.dialect, _ast, _context.reservedIdentifiers); + } + using ASTModifier::operator(); void operator()(Block& _block) override; @@ -70,10 +65,24 @@ public: Dialect const& _dialect, Block& _ast, bool _allowMSizeOptimization, + std::map const* _functionSideEffects = nullptr, std::set const& _externallyUsedFunctions = {} ); - static void runUntilStabilised( + static void run( + Dialect const& _dialect, + Block& _ast, + std::set const& _externallyUsedFunctions = {} + ) + { + runUntilStabilisedOnFullAST(_dialect, _ast, _externallyUsedFunctions); + } + + /// Run the pruner until the code does not change anymore. + /// The provided block has to be a full AST. + /// The pruner itself determines if msize is used and which user-defined functions + /// are side-effect free. + static void runUntilStabilisedOnFullAST( Dialect const& _dialect, Block& _ast, std::set const& _externallyUsedFunctions = {} @@ -92,11 +101,26 @@ public: ); private: + UnusedPruner( + Dialect const& _dialect, + Block& _ast, + bool _allowMSizeOptimization, + std::map const* _functionSideEffects = nullptr, + std::set const& _externallyUsedFunctions = {} + ); + UnusedPruner( + Dialect const& _dialect, + FunctionDefinition& _function, + bool _allowMSizeOptimization, + std::set const& _externallyUsedFunctions = {} + ); + bool used(YulString _name) const; void subtractReferences(std::map const& _subtrahend); Dialect const& m_dialect; bool m_allowMSizeOptimization = false; + std::map const* m_functionSideEffects = nullptr; bool m_shouldRunAgain = false; std::map m_references; }; diff --git a/libyul/optimiser/VarDeclInitializer.h b/libyul/optimiser/VarDeclInitializer.h index 41d0917ce..ea3ca2988 100644 --- a/libyul/optimiser/VarDeclInitializer.h +++ b/libyul/optimiser/VarDeclInitializer.h @@ -19,6 +19,7 @@ #include #include +#include namespace yul { @@ -32,6 +33,9 @@ namespace yul class VarDeclInitializer: public ASTModifier { public: + static constexpr char const* name{"VarDeclInitializer"}; + static void run(OptimiserStepContext&, Block& _ast) { VarDeclInitializer{}(_ast); } + void operator()(Block& _block) override; }; diff --git a/libyul/optimiser/VarNameCleaner.h b/libyul/optimiser/VarNameCleaner.h index 7f2e90989..246152deb 100644 --- a/libyul/optimiser/VarNameCleaner.h +++ b/libyul/optimiser/VarNameCleaner.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -43,11 +44,11 @@ struct Dialect; class VarNameCleaner: public ASTModifier { public: - VarNameCleaner( - Block const& _ast, - Dialect const& _dialect, - std::set _blacklist = {} - ); + static constexpr char const* name{"VarNameCleaner"}; + static void run(OptimiserStepContext& _context, Block& _ast) + { + VarNameCleaner{_ast, _context.dialect, _context.reservedIdentifiers}(_ast); + } using ASTModifier::operator(); void operator()(VariableDeclaration& _varDecl) override; @@ -55,6 +56,12 @@ public: void operator()(FunctionDefinition& _funDef) override; private: + VarNameCleaner( + Block const& _ast, + Dialect const& _dialect, + std::set _blacklist = {} + ); + /// Tries to rename a list of variables. void renameVariables(std::vector& _variables); diff --git a/scripts/bytecodecompare/storebytecode.bat b/scripts/bytecodecompare/storebytecode.bat index ef20a3207..996b0e72a 100644 --- a/scripts/bytecodecompare/storebytecode.bat +++ b/scripts/bytecodecompare/storebytecode.bat @@ -39,5 +39,5 @@ set REPORT=%DIRECTORY%/windows.txt cp ../report.txt %REPORT% git add %REPORT% git commit -a -m "Added report." -git pull --rebase +git pull --rebase 2>&1 git push origin 2>&1 diff --git a/scripts/codespell_whitelist.txt b/scripts/codespell_whitelist.txt index 7247cd005..bbfe3e05c 100644 --- a/scripts/codespell_whitelist.txt +++ b/scripts/codespell_whitelist.txt @@ -9,3 +9,4 @@ ba fo compilability errorstring +hist diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh new file mode 100755 index 000000000..b971fdd24 --- /dev/null +++ b/scripts/deps-ppa/static_z3.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +############################################################################## +## This is used to package .deb packages and upload them to the launchpad +## ppa servers for building. +## +## The gnupg key for "builds@ethereum.org" has to be present in order to sign +## the package. +## +## It will clone the Z3 git from github on the specified version tag, +## create a source archive and push it to the ubuntu ppa servers. +## +## This requires the following entries in /etc/dput.cf: +## +## [cpp-build-deps] +## fqdn = ppa.launchpad.net +## method = ftp +## incoming = ~ethereum/cpp-build-deps +## login = anonymous + +## +############################################################################## + +set -ev + +keyid=70D110489D66E2F6 +email=builds@ethereum.org +packagename=libz3-static-dev +version=4.8.5 + +DISTRIBUTIONS="bionic disco" + +for distribution in $DISTRIBUTIONS +do +cd /tmp/ +rm -rf $distribution +mkdir $distribution +cd $distribution + +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 +cd z3 +debversion="$version" + +CMAKE_OPTIONS="-DBUILD_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 +# create it once. +if [ ! -e /tmp/${packagename}_${debversion}.orig.tar.gz ] +then + tar --exclude .git -czf /tmp/${packagename}_${debversion}.orig.tar.gz . +fi +cp /tmp/${packagename}_${debversion}.orig.tar.gz ../ + +# Create debian package information + +mkdir debian +echo 9 > debian/compat +# TODO: the Z3 packages have different build dependencies +cat < debian/control +Source: libz3-static-dev +Section: science +Priority: extra +Maintainer: Daniel Kirchner +Build-Depends: debhelper (>= 9.0.0), + cmake, + g++, + git, + libgmp-dev, + dh-python, + python +Standards-Version: 3.9.6 +Homepage: https://github.com/Z3Prover/z3 +Vcs-Git: git://github.com/Z3Prover/z3.git +Vcs-Browser: https://github.com/Z3Prover/z3 + +Package: libz3-static-dev +Section: libdevel +Architecture: any-i386 any-amd64 +Multi-Arch: same +Depends: \${shlibs:Depends}, \${misc:Depends} +Description: theorem prover from Microsoft Research - development files (static library) + Z3 is a state-of-the art theorem prover from Microsoft Research. It can be + used to check the satisfiability of logical formulas over one or more + theories. Z3 offers a compelling match for software analysis and verification + tools, since several common software constructs map directly into supported + theories. + . + This package can be used to invoke Z3 via its C++ API. +EOF +cat < debian/rules +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. +# +# Modified to make a template file for a multi-binary package with separated +# build-arch and build-indep targets by Bill Allombert 2001 + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + +# This has to be exported to make some magic below work. +export DH_OPTIONS + + +%: + dh \$@ --buildsystem=cmake + +override_dh_auto_test: + +override_dh_shlibdeps: + dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info + +override_dh_auto_configure: + dh_auto_configure -- ${CMAKE_OPTIONS} + +override_dh_auto_install: + dh_auto_install --destdir debian/tmp +EOF +cat < debian/libz3-static-dev.install +usr/include/* +usr/lib/*/libz3.a +usr/lib/*/cmake/z3/* +EOF +cat < debian/copyright +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: z3 +Source: https://github.com/Z3Prover/z3 + +Files: * +Copyright: Microsoft Corporation +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Files: debian/* +Copyright: 2019 Ethereum +License: GPL-3.0+ +This program 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. + . + This package 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 this program. If not, see . + . + On Debian systems, the complete text of the GNU General + 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 + + * Initial release. + + -- Daniel Mon, 03 Jun 2019 14:50:20 +0000 +EOF +mkdir debian/source +echo "3.0 (quilt)" > debian/source/format +chmod +x debian/rules + +versionsuffix=0ubuntu1~${distribution} +EMAIL="$email" dch -v 1:${debversion}-${versionsuffix} "build of ${version}" + +# build source package +# If packages is rejected because original source is already present, add +# -sd to remove it from the .changes file +# -d disables the build dependencies check +debuild -S -d -sa -us -uc + +# prepare .changes file for Launchpad +sed -i -e s/UNRELEASED/${distribution}/ -e s/urgency=medium/urgency=low/ ../*.changes + +# check if ubuntu already has the source tarball +( +cd .. +orig=${packagename}_${debversion}.orig.tar.gz +orig_size=$(ls -l $orig | cut -d ' ' -f 5) +orig_sha1=$(sha1sum $orig | cut -d ' ' -f 1) +orig_sha256=$(sha256sum $orig | cut -d ' ' -f 1) +orig_md5=$(md5sum $orig | cut -d ' ' -f 1) + +if wget --quiet -O $orig-tmp "$ppafilesurl/$orig" +then + echo "[WARN] Original tarball found in Ubuntu archive, using it instead" + mv $orig-tmp $orig + new_size=$(ls -l *.orig.tar.gz | cut -d ' ' -f 5) + new_sha1=$(sha1sum $orig | cut -d ' ' -f 1) + new_sha256=$(sha256sum $orig | cut -d ' ' -f 1) + new_md5=$(md5sum $orig | cut -d ' ' -f 1) + sed -i -e s,$orig_sha1,$new_sha1,g -e s,$orig_sha256,$new_sha256,g -e s,$orig_size,$new_size,g -e s,$orig_md5,$new_md5,g *.dsc + sed -i -e s,$orig_sha1,$new_sha1,g -e s,$orig_sha256,$new_sha256,g -e s,$orig_size,$new_size,g -e s,$orig_md5,$new_md5,g *.changes +fi +) + +# sign the package +debsign --re-sign -k ${keyid} ../${packagename}_${debversion}-${versionsuffix}_source.changes + +# upload +dput ${pparepo} ../${packagename}_${debversion}-${versionsuffix}_source.changes + +done diff --git a/scripts/install_cmake.sh b/scripts/install_cmake.sh index e334b2c90..134b86f09 100755 --- a/scripts/install_cmake.sh +++ b/scripts/install_cmake.sh @@ -6,16 +6,18 @@ set -e -VERSION=3.7.1 -PREFIX=~/.local +VERSION_MAJOR=3 +VERSION_MINOR=15 +VERSION_MICRO=2 +VERSION=$VERSION_MAJOR.$VERSION_MINOR.$VERSION_MICRO +PREFIX="/usr/local" OS=$(uname -s) case $OS in -Linux) SHA256=7b4b7a1d9f314f45722899c0521c261e4bfab4a6b532609e37fef391da6bade2;; -Darwin) SHA256=1851d1448964893fdc5a8c05863326119f397a3790e0c84c40b83499c7960267;; +Linux) SHA256=f8cbec2abc433938bd9378b129d1d288bb33b8b5a277afe19644683af6e32a59;; +Darwin) SHA256=7ec056d641b8cbea98b220efdcc99da1991758a370063dcac3a0cd388d6b30b6;; esac - BIN=$PREFIX/bin PATH=$PREFIX/bin:$PATH @@ -24,7 +26,7 @@ if test -f $BIN/cmake && ($BIN/cmake --version | grep -q "$VERSION"); then echo "CMake $VERSION already installed in $BIN" else FILE=cmake-$VERSION-$OS-x86_64.tar.gz - URL=https://cmake.org/files/v3.7/$FILE + URL=https://cmake.org/files/v$VERSION_MAJOR.$VERSION_MINOR/$FILE ERROR=0 TMPFILE=$(mktemp --tmpdir cmake-$VERSION-$OS-x86_64.XXXXXXXX.tar.gz) echo "Downloading CMake ($URL)..." diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index c8e90b0d2..0617dd028 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -90,9 +90,12 @@ case $(uname -s) in 10.14) echo "Installing solidity dependencies on macOS 10.14 Mojave." ;; + 10.15) + echo "Installing solidity dependencies on macOS 10.15 Catalina." + ;; *) echo "Unsupported macOS version." - echo "We only support Mavericks, Yosemite, El Capitan, Sierra, High Sierra and Mojave." + echo "We only support Mavericks, Yosemite, El Capitan, Sierra, High Sierra, Mojave, and Catalina." exit 1 ;; esac diff --git a/scripts/install_static_z3.sh b/scripts/install_static_z3.sh new file mode 100644 index 000000000..7748fb1f3 --- /dev/null +++ b/scripts/install_static_z3.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +git clone --depth 1 --branch z3-4.8.1 https://github.com/Z3Prover/z3.git +cd z3 +mkdir build +cd build +LDFLAGS="-static" cmake -DBUILD_LIBZ3_SHARED=OFF .. +make -j 4 +make install \ No newline at end of file diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 74daaa0b5..78ea3118b 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -83,8 +83,15 @@ else else pparepo=ethereum-dev fi - SMTDEPENDENCY="libcvc4-dev, + if [ $distribution = disco ] + then + SMTDEPENDENCY="libz3-static-dev, + libcvc4-dev, " + else + SMTDEPENDENCY="libz3-static-dev, + " + fi CMAKE_OPTIONS="" fi ppafilesurl=https://launchpad.net/~ethereum/+archive/ubuntu/${pparepo}/+files diff --git a/scripts/tests.sh b/scripts/tests.sh index 80c19752f..32fc4134c 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -29,6 +29,7 @@ set -e REPO_ROOT="$(dirname "$0")/.." +SOLIDITY_BUILD_DIR="${SOLIDITY_BUILD_DIR:-build}" source "${REPO_ROOT}/scripts/common.sh" @@ -120,7 +121,7 @@ do fi set +e - "$REPO_ROOT"/build/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $force_abiv2_flag + "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $force_abiv2_flag if test "0" -ne "$?"; then exit 1 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index fdb2fd0bf..d732b9077 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -26,7 +26,7 @@ parts: source: . source-type: git plugin: cmake - build-packages: [build-essential, libboost-all-dev, libcvc4-dev] + build-packages: [build-essential, libboost-all-dev] stage-packages: [libicu60] override-build: | if git describe --exact-match --tags 2> /dev/null @@ -34,7 +34,7 @@ parts: echo -n > ../src/prerelease.txt fi snapcraftctl build - after: [z3] + after: [z3, cvc4] z3: source: https://github.com/Z3Prover/z3.git source-tag: z3-4.8.4 @@ -47,3 +47,16 @@ parts: cd build make -j -l $(grep -c "^processor" /proc/cpuinfo) make install DESTDIR=$SNAPCRAFT_PART_INSTALL + cvc4: + source: https://github.com/CVC4/CVC4.git + source-tag: "1.7" + plugin: nil + build-packages: [python, cmake, openjdk-11-jre, libgmp-dev, wget] + override-build: | + ./contrib/get-antlr-3.4 + ./configure.sh --prefix=$SNAPCRAFT_STAGE/usr + cd build + make -j -l $(grep -c "^processor" /proc/cpuinfo) + make install + mkdir -p $SNAPCRAFT_PART_INSTALL/usr/lib/ + cp $SNAPCRAFT_STAGE/usr/lib/libcvc4.so.6 $SNAPCRAFT_PART_INSTALL/usr/lib/ diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 34d4e8894..79d85d82f 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -641,7 +641,7 @@ Allowed options)", ( g_strEVMVersion.c_str(), po::value()->value_name("version"), - "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg (default)." + "Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg (default), istanbul or berlin." ) (g_argOptimize.c_str(), "Enable bytecode optimizer.") ( diff --git a/test/.solhint.json b/test/.solhint.json index 1379d570d..0a8b2adfc 100644 --- a/test/.solhint.json +++ b/test/.solhint.json @@ -2,7 +2,7 @@ "extends": "solhint:default", "plugins": [], "rules": { - "compiler-fixed": false, - "no-inline-assembly": false + "compiler-fixed": "off", + "no-inline-assembly": "off" } } \ No newline at end of file diff --git a/test/Common.cpp b/test/Common.cpp index a6dd23e0b..48966d9a3 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -76,7 +76,7 @@ std::string EVMOneEnvOrDefaultPath() }; for (auto const& basePath: searchPath) { - fs::path p = basePath / "libevmone.so"; + fs::path p = basePath / evmoneFilename; if (fs::exists(p)) return p.string(); } @@ -92,7 +92,7 @@ CommonOptions::CommonOptions(std::string _caption): options.add_options() ("evm-version", po::value(&evmVersionString), "which evm version to use") ("testpath", po::value(&this->testPath)->default_value(dev::test::testPath()), "path to test files") - ("evmonepath", po::value(&evmonePath)->default_value(EVMOneEnvOrDefaultPath()), "path to libevmone.so") + ("evmonepath", po::value(&evmonePath)->default_value(EVMOneEnvOrDefaultPath()), "path to evmone library") ("no-smt", po::bool_switch(&disableSMT), "disable SMT checker"); } diff --git a/test/Common.h b/test/Common.h index 6daf17675..ebbe2d2ad 100644 --- a/test/Common.h +++ b/test/Common.h @@ -30,6 +30,18 @@ namespace dev 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"; +#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"; +#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"; +#endif + + struct ConfigException : public Exception {}; struct CommonOptions: boost::noncopyable diff --git a/test/EVMHost.cpp b/test/EVMHost.cpp index f18abc10d..19d8f6f77 100644 --- a/test/EVMHost.cpp +++ b/test/EVMHost.cpp @@ -61,7 +61,7 @@ EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::vm* _vm): { if (!m_vm) { - cerr << "Unable to find library libevmone.so" << endl; + cerr << "Unable to find evmone library" << endl; assertThrow(false, Exception, ""); } @@ -75,6 +75,10 @@ EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::vm* _vm): m_evmVersion = EVMC_BYZANTIUM; else if (_evmVersion == langutil::EVMVersion::constantinople()) m_evmVersion = EVMC_CONSTANTINOPLE; + else if (_evmVersion == langutil::EVMVersion::istanbul()) + assertThrow(false, Exception, "Istanbul is not supported yet."); + else if (_evmVersion == langutil::EVMVersion::berlin()) + assertThrow(false, Exception, "Berlin is not supported yet."); else //if (_evmVersion == langutil::EVMVersion::petersburg()) m_evmVersion = EVMC_PETERSBURG; } diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h index b20f10f9c..5019c0fbc 100644 --- a/test/ExecutionFramework.h +++ b/test/ExecutionFramework.h @@ -92,6 +92,12 @@ public: return callFallbackWithValue(0); } + bytes const& callLowLevel(bytes const& _data, u256 const& _value) + { + sendMessage(_data, false, _value); + return m_output; + } + bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments) { FixedHash<4> hash(dev::keccak256(_sig)); diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index b12c0a0ca..b75be49e7 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -56,6 +57,7 @@ Testsuite const g_interactiveTestsuites[] = { {"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}, + {"Function Side Effects","libyul", "functionSideEffects", false, false, &yul::test::FunctionSideEffects::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, diff --git a/test/TestCase.cpp b/test/TestCase.cpp index e3671e4db..88d7f7862 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -62,11 +62,15 @@ bool TestCase::validateSettings(langutil::EVMVersion) return true; } -pair TestCase::parseSourceAndSettingsWithLineNumbers(istream& _stream) +pair, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream) { - string source; + map sources; + string currentSourceName; + string currentSource; string line; size_t lineNumber = 1; + static string const sourceDelimiterStart("==== Source:"); + static string const sourceDelimiterEnd("===="); static string const comment("// "); static string const settingsDelimiter("// ===="); static string const delimiter("// ----"); @@ -80,7 +84,22 @@ pair TestCase::parseSourceAndSettingsWithLineNumbers(istream& _s else if (boost::algorithm::starts_with(line, settingsDelimiter)) sourcePart = false; else if (sourcePart) - source += line + "\n"; + { + if (boost::algorithm::starts_with(line, sourceDelimiterStart) && boost::algorithm::ends_with(line, sourceDelimiterEnd)) + { + if (!(currentSourceName.empty() && currentSource.empty())) + sources[currentSourceName] = std::move(currentSource); + currentSource = {}; + currentSourceName = boost::trim_copy(line.substr( + sourceDelimiterStart.size(), + line.size() - sourceDelimiterEnd.size() - sourceDelimiterStart.size() + )); + if (sources.count(currentSourceName)) + throw runtime_error("Multiple definitions of test source \"" + currentSourceName + "\"."); + } + else + currentSource += line + "\n"; + } else if (boost::algorithm::starts_with(line, comment)) { size_t colon = line.find(':'); @@ -95,12 +114,26 @@ pair TestCase::parseSourceAndSettingsWithLineNumbers(istream& _s else throw runtime_error(string("Expected \"//\" or \"// ---\" to terminate settings and source.")); } - return make_pair(source, lineNumber); + sources[currentSourceName] = currentSource; + return {sources, lineNumber}; +} + +map TestCase::parseSourcesAndSettings(istream& _stream) +{ + return get<0>(parseSourcesAndSettingsWithLineNumbers(_stream)); +} + +pair TestCase::parseSourceAndSettingsWithLineNumbers(istream& _stream) +{ + auto [sourceMap, lineOffset] = parseSourcesAndSettingsWithLineNumbers(_stream); + if (sourceMap.size() != 1) + BOOST_THROW_EXCEPTION(runtime_error("Expected single source definition, but got multiple sources.")); + return {std::move(sourceMap.begin()->second), lineOffset}; } string TestCase::parseSourceAndSettings(istream& _stream) { - return get<0>(parseSourceAndSettingsWithLineNumbers(_stream)); + return parseSourceAndSettingsWithLineNumbers(_stream).first; } string TestCase::parseSimpleExpectations(std::istream& _file) diff --git a/test/TestCase.h b/test/TestCase.h index a121f50f2..0c149c22c 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -80,6 +80,8 @@ public: virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/); protected: + std::pair, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file); + std::map parseSourcesAndSettings(std::istream& _file); std::pair parseSourceAndSettingsWithLineNumbers(std::istream& _file); std::string parseSourceAndSettings(std::istream& _file); static void expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c); diff --git a/test/boostTest.cpp b/test/boostTest.cpp index 87cc07525..fd33f667e 100644 --- a/test/boostTest.cpp +++ b/test/boostTest.cpp @@ -143,9 +143,9 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] ) bool disableSemantics = !dev::test::EVMHost::getVM(dev::test::Options::get().evmonePath.string()); if (disableSemantics) { - cout << "Unable to find libevmone.so. Please provide the path using -- --evmonepath ." << endl; + cout << "Unable to find " << dev::test::evmoneFilename << ". Please provide the path using -- --evmonepath ." << endl; cout << "You can download it at" << endl; - cout << "https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-linux-x86_64.tar.gz" << endl; + cout << dev::test::evmoneDownloadLink << endl; cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; } // Include the interactive tests in the automatic tests as well diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index ba0b1cf8b..a9ab91bc7 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,8 +31,9 @@ set -e ## GLOBAL VARIABLES REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) +SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build} source "${REPO_ROOT}/scripts/common.sh" -SOLC="$REPO_ROOT/build/solc/solc" +SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/solc" INTERACTIVE=true if ! tty -s || [ "$CI" ] then @@ -439,8 +440,8 @@ SOLTMPDIR=$(mktemp -d) "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/test/ "$REPO_ROOT"/scripts/isolate_tests.py "$REPO_ROOT"/docs/ docs - echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/build/test/tools/solfuzzer --quiet --input-files - echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/build/test/tools/solfuzzer --without-optimizer --quiet --input-files + echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer --quiet --input-files + echo *.sol | xargs -P 4 -n 50 "$REPO_ROOT"/${SOLIDITY_BUILD_DIR}/test/tools/solfuzzer --without-optimizer --quiet --input-files ) rm -rf "$SOLTMPDIR" diff --git a/test/cmdlineTests/standard_eWasm_requested/output.json b/test/cmdlineTests/standard_eWasm_requested/output.json index a0956a399..17469c367 100644 --- a/test/cmdlineTests/standard_eWasm_requested/output.json +++ b/test/cmdlineTests/standard_eWasm_requested/output.json @@ -7,24 +7,16 @@ (local $_1 i64) (local $pos i64) (local $_2 i64) - (local $_3 i64) (local $hi i64) - (local $_4 i64) + (local $y i64) (local $hi_1 i64) - (local $hi_2 i64) - (local $hi_3 i64) - (local $hi_4 i64) (set_local $_1 (i64.const 0)) (set_local $pos (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (i64.const 64))) - (set_local $_2 (i64.const 32)) - (set_local $_3 (i64.shr_u (get_local $_1) (i64.const 16))) - (set_local $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (get_local $_1) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (get_local $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (get_local $_3))) (get_local $_2))) - (set_local $_4 (i64.shr_u (get_local $_1) (get_local $_2))) - (i64.store (get_local $pos) (i64.or (get_local $hi) (call $endian_swap_32 (get_local $_4)))) (set_local $hi_1 (i64.shl (call $endian_swap_16 (get_local $_1)) (i64.const 16))) - (set_local $hi_2 (i64.shl (i64.or (get_local $hi_1) (call $endian_swap_16 (get_local $_3))) (get_local $_2))) - (i64.store (i64.add (get_local $pos) (i64.const 8)) (i64.or (get_local $hi_2) (call $endian_swap_32 (get_local $_4)))) (set_local $hi_3 (i64.shl (call $endian_swap_32 (get_local $_1)) (get_local $_2))) - (i64.store (i64.add (get_local $pos) (i64.const 16)) (i64.or (get_local $hi_3) (call $endian_swap_32 (get_local $_4)))) (set_local $hi_4 (i64.shl (call $endian_swap_32 (i64.const 64)) (get_local $_2))) - (i64.store (i64.add (get_local $pos) (i64.const 24)) (i64.or (get_local $hi_4) (call $endian_swap_32 (i64.shr_u (i64.const 64) (get_local $_2))))) (call $eth.revert (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1))) + (set_local $_2 (i64.const 65280)) + (set_local $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (get_local $_1) (i64.const 8)) (get_local $_2)) (i64.and (i64.shr_u (get_local $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (get_local $_1) (i64.const 16)))) (i64.const 32))) + (set_local $y (i64.or (get_local $hi) (call $endian_swap_32 (i64.shr_u (get_local $_1) (i64.const 32))))) + (i64.store (get_local $pos) (get_local $y)) (i64.store (i64.add (get_local $pos) (i64.const 8)) (get_local $y)) (i64.store (i64.add (get_local $pos) (i64.const 16)) (get_local $y)) (set_local $hi_1 (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (i64.const 64) (i64.const 8)) (get_local $_2)) (i64.and (i64.shr_u (i64.const 64) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (i64.const 64) (i64.const 16)))) (i64.const 32))) + (i64.store (i64.add (get_local $pos) (i64.const 24)) (i64.or (get_local $hi_1) (call $endian_swap_32 (i64.shr_u (i64.const 64) (i64.const 32))))) (call $eth.revert (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1))) ) (func $u256_to_i32 @@ -34,9 +26,9 @@ (param $x4 i64) (result i64) (local $v i64) - (if (i64.ne (i64.const 0) (i64.or (i64.or (get_local $x1) (get_local $x2)) (get_local $x3))) (then + (if (i64.ne (get_local $v) (i64.or (i64.or (get_local $x1) (get_local $x2)) (get_local $x3))) (then (unreachable))) - (if (i64.ne (i64.const 0) (i64.shr_u (get_local $x4) (i64.const 32))) (then + (if (i64.ne (get_local $v) (i64.shr_u (get_local $x4) (i64.const 32))) (then (unreachable))) (set_local $v (get_local $x4)) (get_local $v) @@ -74,44 +66,38 @@ (local $_1 i64) (local $pos i64) (local $hi i64) - (local $_2 i64) + (local $y i64) (local $hi_1 i64) - (local $_3 i64) (local $hi_2 i64) - (local $hi_3 i64) - (local $hi_4 i64) + (local $_2 i64) + (local $_3 i64) (local $_4 i64) (local $_5 i64) (local $_6 i64) (local $_7 i64) (local $_8 i64) (local $_9 i64) - (local $_10 i64) - (local $_11 i64) (set_local $_1 (i64.const 0)) (set_local $pos (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (i64.const 64))) - (set_local $hi (i64.shl (call $endian_swap_16 (get_local $_1)) (i64.const 16))) - (set_local $_2 (i64.shr_u (get_local $_1) (i64.const 16))) - (set_local $hi_1 (i64.shl (i64.or (get_local $hi) (call $endian_swap_16 (get_local $_2))) (i64.const 32))) - (set_local $_3 (i64.shr_u (get_local $_1) (i64.const 32))) - (i64.store (get_local $pos) (i64.or (get_local $hi_1) (call $endian_swap_32 (get_local $_3)))) (set_local $hi_2 (i64.shl (call $endian_swap_16 (get_local $_1)) (i64.const 16))) - (set_local $hi_3 (i64.shl (i64.or (get_local $hi_2) (call $endian_swap_16 (get_local $_2))) (i64.const 32))) - (i64.store (i64.add (get_local $pos) (i64.const 8)) (i64.or (get_local $hi_3) (call $endian_swap_32 (get_local $_3)))) (set_local $hi_4 (i64.shl (call $endian_swap_32 (get_local $_1)) (i64.const 32))) - (i64.store (i64.add (get_local $pos) (i64.const 16)) (i64.or (get_local $hi_4) (call $endian_swap_32 (get_local $_3)))) (i64.store (i64.add (get_local $pos) (i64.const 24)) (call $endian_swap (i64.const 64))) (block - (set_local $_4 (datasize \"C_2_deployed\")) - (set_local $_5 (get_global $global_)) - (set_local $_6 (get_global $global__1)) - (set_local $_7 (get_global $global__2)) + (set_local $hi (i64.shl (i64.or (i64.shl (i64.or (i64.and (i64.shl (get_local $_1) (i64.const 8)) (i64.const 65280)) (i64.and (i64.shr_u (get_local $_1) (i64.const 8)) (i64.const 255))) (i64.const 16)) (call $endian_swap_16 (i64.shr_u (get_local $_1) (i64.const 16)))) (i64.const 32))) + (set_local $y (i64.or (get_local $hi) (call $endian_swap_32 (i64.shr_u (get_local $_1) (i64.const 32))))) + (i64.store (get_local $pos) (get_local $y)) (i64.store (i64.add (get_local $pos) (i64.const 8)) (get_local $y)) (i64.store (i64.add (get_local $pos) (i64.const 16)) (get_local $y)) (set_local $hi_1 (i64.shl (call $endian_swap_16 (i64.const 64)) (i64.const 16))) + (set_local $hi_2 (i64.shl (i64.or (get_local $hi_1) (call $endian_swap_16 (i64.shr_u (i64.const 64) (i64.const 16)))) (i64.const 32))) + (i64.store (i64.add (get_local $pos) (i64.const 24)) (i64.or (get_local $hi_2) (call $endian_swap_32 (i64.shr_u (i64.const 64) (i64.const 32))))) (block + (set_local $_2 (datasize \"C_2_deployed\")) + (set_local $_3 (get_global $global_)) + (set_local $_4 (get_global $global__1)) + (set_local $_5 (get_global $global__2)) ) (block - (set_local $_8 (dataoffset \"C_2_deployed\")) - (set_local $_9 (get_global $global_)) - (set_local $_10 (get_global $global__1)) - (set_local $_11 (get_global $global__2)) + (set_local $_6 (dataoffset \"C_2_deployed\")) + (set_local $_7 (get_global $global_)) + (set_local $_8 (get_global $global__1)) + (set_local $_9 (get_global $global__2)) ) - (call $eth.codeCopy (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_8) (get_local $_9) (get_local $_10) (get_local $_11)) (call $u256_to_i32 (get_local $_4) (get_local $_5) (get_local $_6) (get_local $_7))) (call $eth.finish (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_4) (get_local $_5) (get_local $_6) (get_local $_7))) + (call $eth.codeCopy (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_6) (get_local $_7) (get_local $_8) (get_local $_9)) (call $u256_to_i32 (get_local $_2) (get_local $_3) (get_local $_4) (get_local $_5))) (call $eth.finish (call $u256_to_i32 (get_local $_1) (get_local $_1) (get_local $_1) (get_local $_1)) (call $u256_to_i32 (get_local $_2) (get_local $_3) (get_local $_4) (get_local $_5))) ) (func $u256_to_i32 @@ -121,9 +107,9 @@ (param $x4 i64) (result i64) (local $v i64) - (if (i64.ne (i64.const 0) (i64.or (i64.or (get_local $x1) (get_local $x2)) (get_local $x3))) (then + (if (i64.ne (get_local $v) (i64.or (i64.or (get_local $x1) (get_local $x2)) (get_local $x3))) (then (unreachable))) - (if (i64.ne (i64.const 0) (i64.shr_u (get_local $x4) (i64.const 32))) (then + (if (i64.ne (get_local $v) (i64.shr_u (get_local $x4) (i64.const 32))) (then (unreachable))) (set_local $v (get_local $x4)) (get_local $v) @@ -147,16 +133,6 @@ (get_local $y) ) -(func $endian_swap - (param $x i64) - (result i64) - (local $y i64) - (local $hi i64) - (set_local $hi (i64.shl (call $endian_swap_32 (get_local $x)) (i64.const 32))) - (set_local $y (i64.or (get_local $hi) (call $endian_swap_32 (i64.shr_u (get_local $x) (i64.const 32))))) - (get_local $y) -) - ) "}}}},"errors":[{"component":"general","formattedMessage":"Warning: The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests. ","message":"The Yul optimiser is still experimental. Do not use it in production unless correctness of generated code is verified with extensive tests.","severity":"warning","type":"Warning"}],"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/yul_stack_opt/output b/test/cmdlineTests/yul_stack_opt/output index 0f987c7e6..7168d526b 100644 --- a/test/cmdlineTests/yul_stack_opt/output +++ b/test/cmdlineTests/yul_stack_opt/output @@ -11,20 +11,20 @@ object "object" { } function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 { - let a := 1 - sstore(a, a) - sstore(2, a) - sstore(3, a) - sstore(4, a) - sstore(5, a) - sstore(6, a) - sstore(7, a) - sstore(8, a) - sstore(9, a) - sstore(10, a) - sstore(11, a) - sstore(12, a) - sstore(13, a) + let _1 := 1 + sstore(_1, _1) + sstore(2, _1) + sstore(3, _1) + sstore(4, _1) + sstore(5, _1) + sstore(6, _1) + sstore(7, _1) + sstore(8, _1) + sstore(9, _1) + sstore(10, _1) + sstore(11, _1) + sstore(12, _1) + sstore(13, _1) } } } @@ -103,79 +103,77 @@ tag_2: 0x00 /* "yul_stack_opt/input.sol":98:99 */ 0x01 - /* "yul_stack_opt/input.sol":139:140 */ dup1 - /* "yul_stack_opt/input.sol":136:137 */ dup2 /* "yul_stack_opt/input.sol":129:141 */ sstore - /* "yul_stack_opt/input.sol":162:163 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":151:160 */ 0x02 /* "yul_stack_opt/input.sol":144:164 */ sstore - /* "yul_stack_opt/input.sol":185:186 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":174:183 */ 0x03 /* "yul_stack_opt/input.sol":167:187 */ sstore - /* "yul_stack_opt/input.sol":208:209 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":197:206 */ 0x04 /* "yul_stack_opt/input.sol":190:210 */ sstore - /* "yul_stack_opt/input.sol":231:232 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":220:229 */ 0x05 /* "yul_stack_opt/input.sol":213:233 */ sstore - /* "yul_stack_opt/input.sol":254:255 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":243:252 */ 0x06 /* "yul_stack_opt/input.sol":236:256 */ sstore - /* "yul_stack_opt/input.sol":277:278 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":266:275 */ 0x07 /* "yul_stack_opt/input.sol":259:279 */ sstore - /* "yul_stack_opt/input.sol":300:301 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":289:298 */ 0x08 /* "yul_stack_opt/input.sol":282:302 */ sstore - /* "yul_stack_opt/input.sol":323:324 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":312:321 */ 0x09 /* "yul_stack_opt/input.sol":305:325 */ sstore - /* "yul_stack_opt/input.sol":346:347 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":335:344 */ 0x0a /* "yul_stack_opt/input.sol":328:348 */ sstore - /* "yul_stack_opt/input.sol":370:371 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":358:368 */ 0x0b /* "yul_stack_opt/input.sol":351:372 */ sstore - /* "yul_stack_opt/input.sol":394:395 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":382:392 */ 0x0c /* "yul_stack_opt/input.sol":375:396 */ sstore - /* "yul_stack_opt/input.sol":418:419 */ + /* "yul_stack_opt/input.sol":98:99 */ dup1 /* "yul_stack_opt/input.sol":406:416 */ 0x0d diff --git a/test/compilationTests/corion/provider.sol b/test/compilationTests/corion/provider.sol index db909a90e..0789c7b5d 100644 --- a/test/compilationTests/corion/provider.sol +++ b/test/compilationTests/corion/provider.sol @@ -149,7 +149,7 @@ contract provider is module, safeMath, announcementTypes { } function getUserDetails(address payable addr, uint256 schellingRound) public view returns (address ProviderAddress, uint256 ProviderHeight, uint256 ConnectedOn, uint256 value) { /* - Collecting the datas of the client. + Collecting the data of the client. @addr Address of the client. @schellingRound Number of the schelling round. If it is not defined then the current one. @@ -272,7 +272,7 @@ contract provider is module, safeMath, announcementTypes { } function setProviderDetails(address payable addr, string calldata website, string calldata country, string calldata info, uint8 rate, address payable admin) isReady external { /* - Modifying the datas of the provider. + Modifying the data of the provider. This can only be invited by the provider’s admin. The emission rate is only valid for the next schelling round for this one it is not. The admin can only be changed by the address of the provider. @@ -323,7 +323,7 @@ contract provider is module, safeMath, announcementTypes { } function getProviderDetails(address payable addr, uint256 height) public view returns (uint8 rate, bool isForRent, uint256 clientsCount, bool priv, bool getInterest, bool valid) { /* - Asking for the datas of the provider. + Asking for the data of the provider. In case the height is unknown then the system will use the last known height. @addr Address of the provider diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp index 27db45a5c..3e2b38889 100644 --- a/test/liblll/Compiler.cpp +++ b/test/liblll/Compiler.cpp @@ -299,6 +299,8 @@ BOOST_AUTO_TEST_CASE(valid_opcodes_functional) "(NUMBER)", "(DIFFICULTY)", "(GASLIMIT)", + "(CHAINID)", + "(SELFBALANCE)", "(POP 0)", "(MLOAD 0)", "(MSTORE 0 0)", diff --git a/test/libsolidity/ABIJson/events.sol b/test/libsolidity/ABIJson/events.sol index 287a0298f..1988e094b 100644 --- a/test/libsolidity/ABIJson/events.sol +++ b/test/libsolidity/ABIJson/events.sol @@ -9,29 +9,6 @@ contract test { // :test // [ // { -// "constant": false, -// "inputs": -// [ -// { -// "internalType": "uint256", -// "name": "a", -// "type": "uint256" -// } -// ], -// "name": "f", -// "outputs": -// [ -// { -// "internalType": "uint256", -// "name": "d", -// "type": "uint256" -// } -// ], -// "payable": false, -// "stateMutability": "nonpayable", -// "type": "function" -// }, -// { // "anonymous": false, // "inputs": // [ @@ -76,5 +53,28 @@ contract test { // "inputs": [], // "name": "e3", // "type": "event" +// }, +// { +// "constant": false, +// "inputs": +// [ +// { +// "internalType": "uint256", +// "name": "a", +// "type": "uint256" +// } +// ], +// "name": "f", +// "outputs": +// [ +// { +// "internalType": "uint256", +// "name": "d", +// "type": "uint256" +// } +// ], +// "payable": false, +// "stateMutability": "nonpayable", +// "type": "function" // } // ] diff --git a/test/libsolidity/ABIJson/inherited.sol b/test/libsolidity/ABIJson/inherited.sol index 5d61f4aef..00abd50ef 100644 --- a/test/libsolidity/ABIJson/inherited.sol +++ b/test/libsolidity/ABIJson/inherited.sol @@ -10,6 +10,20 @@ contract Derived is Base { // :Base // [ // { +// "anonymous": false, +// "inputs": +// [ +// { +// "indexed": true, +// "internalType": "bytes32", +// "name": "evtArgBase", +// "type": "bytes32" +// } +// ], +// "name": "baseEvent", +// "type": "event" +// }, +// { // "constant": false, // "inputs": // [ @@ -31,7 +45,12 @@ contract Derived is Base { // "payable": false, // "stateMutability": "nonpayable", // "type": "function" -// }, +// } +// ] +// +// +// :Derived +// [ // { // "anonymous": false, // "inputs": @@ -45,12 +64,21 @@ contract Derived is Base { // ], // "name": "baseEvent", // "type": "event" -// } -// ] -// -// -// :Derived -// [ +// }, +// { +// "anonymous": false, +// "inputs": +// [ +// { +// "indexed": true, +// "internalType": "uint256", +// "name": "evtArgDerived", +// "type": "uint256" +// } +// ], +// "name": "derivedEvent", +// "type": "event" +// }, // { // "constant": false, // "inputs": @@ -96,33 +124,5 @@ contract Derived is Base { // "payable": false, // "stateMutability": "nonpayable", // "type": "function" -// }, -// { -// "anonymous": false, -// "inputs": -// [ -// { -// "indexed": true, -// "internalType": "uint256", -// "name": "evtArgDerived", -// "type": "uint256" -// } -// ], -// "name": "derivedEvent", -// "type": "event" -// }, -// { -// "anonymous": false, -// "inputs": -// [ -// { -// "indexed": true, -// "internalType": "bytes32", -// "name": "evtArgBase", -// "type": "bytes32" -// } -// ], -// "name": "baseEvent", -// "type": "event" // } // ] diff --git a/test/libsolidity/ABIJson/pure_function.sol b/test/libsolidity/ABIJson/pure_function.sol index f1279ab3d..072b8f1f9 100644 --- a/test/libsolidity/ABIJson/pure_function.sol +++ b/test/libsolidity/ABIJson/pure_function.sol @@ -6,6 +6,29 @@ contract test { // :test // [ // { +// "constant": true, +// "inputs": +// [ +// { +// "internalType": "uint32", +// "name": "a", +// "type": "uint32" +// } +// ], +// "name": "boo", +// "outputs": +// [ +// { +// "internalType": "uint256", +// "name": "b", +// "type": "uint256" +// } +// ], +// "payable": false, +// "stateMutability": "pure", +// "type": "function" +// }, +// { // "constant": false, // "inputs": // [ @@ -32,28 +55,5 @@ contract test { // "payable": false, // "stateMutability": "nonpayable", // "type": "function" -// }, -// { -// "constant": true, -// "inputs": -// [ -// { -// "internalType": "uint32", -// "name": "a", -// "type": "uint32" -// } -// ], -// "name": "boo", -// "outputs": -// [ -// { -// "internalType": "uint256", -// "name": "b", -// "type": "uint256" -// } -// ], -// "payable": false, -// "stateMutability": "pure", -// "type": "function" // } // ] diff --git a/test/libsolidity/ABIJson/return_param_in_abi.sol b/test/libsolidity/ABIJson/return_param_in_abi.sol index 835a88a7b..2618e9ed2 100644 --- a/test/libsolidity/ABIJson/return_param_in_abi.sol +++ b/test/libsolidity/ABIJson/return_param_in_abi.sol @@ -11,6 +11,19 @@ contract test { // :test // [ // { +// "inputs": +// [ +// { +// "internalType": "enum test.ActionChoices", +// "name": "param", +// "type": "uint8" +// } +// ], +// "payable": false, +// "stateMutability": "nonpayable", +// "type": "constructor" +// }, +// { // "constant": false, // "inputs": [], // "name": "ret", @@ -25,18 +38,5 @@ contract test { // "payable": false, // "stateMutability": "nonpayable", // "type": "function" -// }, -// { -// "inputs": -// [ -// { -// "internalType": "enum test.ActionChoices", -// "name": "param", -// "type": "uint8" -// } -// ], -// "payable": false, -// "stateMutability": "nonpayable", -// "type": "constructor" // } // ] diff --git a/test/libsolidity/ABIJson/view_function.sol b/test/libsolidity/ABIJson/view_function.sol index 6112574d5..0c55b6e6e 100644 --- a/test/libsolidity/ABIJson/view_function.sol +++ b/test/libsolidity/ABIJson/view_function.sol @@ -6,6 +6,29 @@ contract test { // :test // [ // { +// "constant": true, +// "inputs": +// [ +// { +// "internalType": "uint32", +// "name": "a", +// "type": "uint32" +// } +// ], +// "name": "boo", +// "outputs": +// [ +// { +// "internalType": "uint256", +// "name": "b", +// "type": "uint256" +// } +// ], +// "payable": false, +// "stateMutability": "view", +// "type": "function" +// }, +// { // "constant": false, // "inputs": // [ @@ -32,28 +55,5 @@ contract test { // "payable": false, // "stateMutability": "nonpayable", // "type": "function" -// }, -// { -// "constant": true, -// "inputs": -// [ -// { -// "internalType": "uint32", -// "name": "a", -// "type": "uint32" -// } -// ], -// "name": "boo", -// "outputs": -// [ -// { -// "internalType": "uint256", -// "name": "b", -// "type": "uint256" -// } -// ], -// "payable": false, -// "stateMutability": "view", -// "type": "function" // } // ] diff --git a/test/libsolidity/ASTJSON/contract_dep_order.json b/test/libsolidity/ASTJSON/contract_dep_order.json new file mode 100644 index 000000000..de42ca600 --- /dev/null +++ b/test/libsolidity/ASTJSON/contract_dep_order.json @@ -0,0 +1,233 @@ +{ + "absolutePath" : "a", + "exportedSymbols" : + { + "A" : + [ + 1 + ], + "B" : + [ + 4 + ], + "C" : + [ + 7 + ], + "D" : + [ + 10 + ], + "E" : + [ + 13 + ] + }, + "id" : 14, + "nodeType" : "SourceUnit", + "nodes" : + [ + { + "baseContracts" : [], + "contractDependencies" : [], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "id" : 1, + "linearizedBaseContracts" : + [ + 1 + ], + "name" : "A", + "nodeType" : "ContractDefinition", + "nodes" : [], + "scope" : 14, + "src" : "0:14:1" + }, + { + "baseContracts" : + [ + { + "arguments" : null, + "baseName" : + { + "contractScope" : null, + "id" : 2, + "name" : "A", + "nodeType" : "UserDefinedTypeName", + "referencedDeclaration" : 1, + "src" : "29:1:1", + "typeDescriptions" : + { + "typeIdentifier" : "t_contract$_A_$1", + "typeString" : "contract A" + } + }, + "id" : 3, + "nodeType" : "InheritanceSpecifier", + "src" : "29:1:1" + } + ], + "contractDependencies" : + [ + 1 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "id" : 4, + "linearizedBaseContracts" : + [ + 4, + 1 + ], + "name" : "B", + "nodeType" : "ContractDefinition", + "nodes" : [], + "scope" : 14, + "src" : "15:19:1" + }, + { + "baseContracts" : + [ + { + "arguments" : null, + "baseName" : + { + "contractScope" : null, + "id" : 5, + "name" : "B", + "nodeType" : "UserDefinedTypeName", + "referencedDeclaration" : 4, + "src" : "49:1:1", + "typeDescriptions" : + { + "typeIdentifier" : "t_contract$_B_$4", + "typeString" : "contract B" + } + }, + "id" : 6, + "nodeType" : "InheritanceSpecifier", + "src" : "49:1:1" + } + ], + "contractDependencies" : + [ + 1, + 4 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "id" : 7, + "linearizedBaseContracts" : + [ + 7, + 4, + 1 + ], + "name" : "C", + "nodeType" : "ContractDefinition", + "nodes" : [], + "scope" : 14, + "src" : "35:19:1" + }, + { + "baseContracts" : + [ + { + "arguments" : null, + "baseName" : + { + "contractScope" : null, + "id" : 8, + "name" : "C", + "nodeType" : "UserDefinedTypeName", + "referencedDeclaration" : 7, + "src" : "69:1:1", + "typeDescriptions" : + { + "typeIdentifier" : "t_contract$_C_$7", + "typeString" : "contract C" + } + }, + "id" : 9, + "nodeType" : "InheritanceSpecifier", + "src" : "69:1:1" + } + ], + "contractDependencies" : + [ + 1, + 4, + 7 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "id" : 10, + "linearizedBaseContracts" : + [ + 10, + 7, + 4, + 1 + ], + "name" : "D", + "nodeType" : "ContractDefinition", + "nodes" : [], + "scope" : 14, + "src" : "55:19:1" + }, + { + "baseContracts" : + [ + { + "arguments" : null, + "baseName" : + { + "contractScope" : null, + "id" : 11, + "name" : "D", + "nodeType" : "UserDefinedTypeName", + "referencedDeclaration" : 10, + "src" : "89:1:1", + "typeDescriptions" : + { + "typeIdentifier" : "t_contract$_D_$10", + "typeString" : "contract D" + } + }, + "id" : 12, + "nodeType" : "InheritanceSpecifier", + "src" : "89:1:1" + } + ], + "contractDependencies" : + [ + 1, + 4, + 7, + 10 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "id" : 13, + "linearizedBaseContracts" : + [ + 13, + 10, + 7, + 4, + 1 + ], + "name" : "E", + "nodeType" : "ContractDefinition", + "nodes" : [], + "scope" : 14, + "src" : "75:19:1" + } + ], + "src" : "0:95:1" +} diff --git a/test/libsolidity/ASTJSON/contract_dep_order.sol b/test/libsolidity/ASTJSON/contract_dep_order.sol new file mode 100644 index 000000000..7558e01dc --- /dev/null +++ b/test/libsolidity/ASTJSON/contract_dep_order.sol @@ -0,0 +1,7 @@ +contract A { } +contract B is A { } +contract C is B { } +contract D is C { } +contract E is D { } + +// ---- diff --git a/test/libsolidity/ASTJSON/contract_dep_order_legacy.json b/test/libsolidity/ASTJSON/contract_dep_order_legacy.json new file mode 100644 index 000000000..5990b52ca --- /dev/null +++ b/test/libsolidity/ASTJSON/contract_dep_order_legacy.json @@ -0,0 +1,288 @@ +{ + "attributes" : + { + "absolutePath" : "a", + "exportedSymbols" : + { + "A" : + [ + 1 + ], + "B" : + [ + 4 + ], + "C" : + [ + 7 + ], + "D" : + [ + 10 + ], + "E" : + [ + 13 + ] + } + }, + "children" : + [ + { + "attributes" : + { + "baseContracts" : + [ + null + ], + "contractDependencies" : + [ + null + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 1 + ], + "name" : "A", + "nodes" : + [ + null + ], + "scope" : 14 + }, + "id" : 1, + "name" : "ContractDefinition", + "src" : "0:14:1" + }, + { + "attributes" : + { + "contractDependencies" : + [ + 1 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 4, + 1 + ], + "name" : "B", + "nodes" : + [ + null + ], + "scope" : 14 + }, + "children" : + [ + { + "attributes" : + { + "arguments" : null + }, + "children" : + [ + { + "attributes" : + { + "contractScope" : null, + "name" : "A", + "referencedDeclaration" : 1, + "type" : "contract A" + }, + "id" : 2, + "name" : "UserDefinedTypeName", + "src" : "29:1:1" + } + ], + "id" : 3, + "name" : "InheritanceSpecifier", + "src" : "29:1:1" + } + ], + "id" : 4, + "name" : "ContractDefinition", + "src" : "15:19:1" + }, + { + "attributes" : + { + "contractDependencies" : + [ + 1, + 4 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 7, + 4, + 1 + ], + "name" : "C", + "nodes" : + [ + null + ], + "scope" : 14 + }, + "children" : + [ + { + "attributes" : + { + "arguments" : null + }, + "children" : + [ + { + "attributes" : + { + "contractScope" : null, + "name" : "B", + "referencedDeclaration" : 4, + "type" : "contract B" + }, + "id" : 5, + "name" : "UserDefinedTypeName", + "src" : "49:1:1" + } + ], + "id" : 6, + "name" : "InheritanceSpecifier", + "src" : "49:1:1" + } + ], + "id" : 7, + "name" : "ContractDefinition", + "src" : "35:19:1" + }, + { + "attributes" : + { + "contractDependencies" : + [ + 1, + 4, + 7 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 10, + 7, + 4, + 1 + ], + "name" : "D", + "nodes" : + [ + null + ], + "scope" : 14 + }, + "children" : + [ + { + "attributes" : + { + "arguments" : null + }, + "children" : + [ + { + "attributes" : + { + "contractScope" : null, + "name" : "C", + "referencedDeclaration" : 7, + "type" : "contract C" + }, + "id" : 8, + "name" : "UserDefinedTypeName", + "src" : "69:1:1" + } + ], + "id" : 9, + "name" : "InheritanceSpecifier", + "src" : "69:1:1" + } + ], + "id" : 10, + "name" : "ContractDefinition", + "src" : "55:19:1" + }, + { + "attributes" : + { + "contractDependencies" : + [ + 1, + 4, + 7, + 10 + ], + "contractKind" : "contract", + "documentation" : null, + "fullyImplemented" : true, + "linearizedBaseContracts" : + [ + 13, + 10, + 7, + 4, + 1 + ], + "name" : "E", + "nodes" : + [ + null + ], + "scope" : 14 + }, + "children" : + [ + { + "attributes" : + { + "arguments" : null + }, + "children" : + [ + { + "attributes" : + { + "contractScope" : null, + "name" : "D", + "referencedDeclaration" : 10, + "type" : "contract D" + }, + "id" : 11, + "name" : "UserDefinedTypeName", + "src" : "89:1:1" + } + ], + "id" : 12, + "name" : "InheritanceSpecifier", + "src" : "89:1:1" + } + ], + "id" : 13, + "name" : "ContractDefinition", + "src" : "75:19:1" + } + ], + "id" : 14, + "name" : "SourceUnit", + "src" : "0:95:1" +} diff --git a/test/libsolidity/ASTJSON/inheritance_specifier.sol b/test/libsolidity/ASTJSON/inheritance_specifier.sol index 02dbf0c51..0809cbe79 100644 --- a/test/libsolidity/ASTJSON/inheritance_specifier.sol +++ b/test/libsolidity/ASTJSON/inheritance_specifier.sol @@ -1 +1,3 @@ contract C1 {} contract C2 is C1 {} + +// ---- diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp index 95a538aae..a326b4e5d 100644 --- a/test/libsolidity/Imports.cpp +++ b/test/libsolidity/Imports.cpp @@ -41,183 +41,6 @@ namespace test BOOST_AUTO_TEST_SUITE(SolidityImports) -BOOST_AUTO_TEST_CASE(smoke_test) -{ - CompilerStack c; - c.setSources({{"a", "contract C {} pragma solidity >=0.0;"}}); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(regular_import) -{ - CompilerStack c; - c.setSources({ - {"a", "contract C {} pragma solidity >=0.0;"}, - {"b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee) -{ - CompilerStack c; - c.setSources({ - {"a", "contract C { D d; } pragma solidity >=0.0;"}, - {"b", "import \"a\"; contract D is C {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(import_is_transitive) -{ - CompilerStack c; - c.setSources({ - {"a", "contract C { } pragma solidity >=0.0;"}, - {"b", "import \"a\"; pragma solidity >=0.0;"}, - {"c", "import \"b\"; contract D is C {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(circular_import) -{ - CompilerStack c; - c.setSources({ - {"a", "import \"b\"; contract C { D d; } pragma solidity >=0.0;"}, - {"b", "import \"a\"; contract D { C c; } pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(relative_import) -{ - CompilerStack c; - c.setSources({ - {"a", "import \"./dir/b\"; contract A is B {} pragma solidity >=0.0;"}, - {"dir/b", "contract B {} pragma solidity >=0.0;"}, - {"dir/c", "import \"../a\"; contract C is A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(relative_import_multiplex) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"dir/a/b/c", "import \"../../.././a\"; contract B is A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(simple_alias) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"dir/a/b/c", "import \"../../.././a\" as x; contract B is x.A { function() external { x.A r = x.A(20); } } pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(library_name_clash) -{ - CompilerStack c; - c.setSources({ - {"a", "library A {} pragma solidity >=0.0;"}, - {"b", "library A {} pragma solidity >=0.0;"}, - {"c", "import {A} from \"./a\"; import {A} from \"./b\";"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(library_name_clash_with_contract) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "library A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(complex_import) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} contract B {} contract C { struct S { uint a; } } pragma solidity >=0.0;"}, - {"b", "import \"a\" as x; import {B as b, C as c, C} from \"a\"; " - "contract D is b { function f(c.S memory var1, x.C.S memory var2, C.S memory var3) internal {} } pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - -BOOST_AUTO_TEST_CASE(name_clash_in_import_1) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "import \"a\"; contract A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(name_clash_in_import_2) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "import \"a\" as A; contract A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(name_clash_in_import_3) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "import {A as b} from \"a\"; contract b {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(name_clash_in_import_4) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "import {A} from \"a\"; contract A {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(name_clash_in_import_5) -{ - CompilerStack c; - c.setSources({ - {"a", "contract A {} pragma solidity >=0.0;"}, - {"b", "import {A} from \"a\"; contract B {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); -} - BOOST_AUTO_TEST_CASE(remappings) { CompilerStack c; @@ -246,17 +69,6 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings) BOOST_CHECK(c.compile()); } -BOOST_AUTO_TEST_CASE(filename_with_period) -{ - CompilerStack c; - c.setSources({ - {"a/a.sol", "import \".b.sol\"; contract A is B {} pragma solidity >=0.0;"}, - {"a/.b.sol", "contract B {} pragma solidity >=0.0;"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - BOOST_AUTO_TEST_CASE(context_dependent_remappings_ensure_default_and_module_preserved) { CompilerStack c; @@ -303,245 +115,6 @@ BOOST_AUTO_TEST_CASE(context_dependent_remappings_order_independent_2) BOOST_CHECK(c.compile()); } -BOOST_AUTO_TEST_CASE(shadowing_via_import) -{ - CompilerStack c; - c.setSources({ - {"a", "library A {} pragma solidity >=0.0;"}, - {"b", "library A {} pragma solidity >=0.0;"}, - {"c", "import {A} from \"./a\"; import {A} from \"./b\";"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); -} - -BOOST_AUTO_TEST_CASE(shadowing_builtins_with_imports) -{ - CompilerStack c; - c.setSources({ - {"B.sol", "contract X {} pragma solidity >=0.0;"}, - {"b", R"( - pragma solidity >=0.0; - import * as msg from "B.sol"; - contract C { - })"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); - size_t errorCount = 0; - for (auto const& e: c.errors()) - { - string const* msg = e->comment(); - BOOST_REQUIRE(msg); - if (msg->find("pre-release") != string::npos) - continue; - BOOST_CHECK( - msg->find("shadows a builtin symbol") != string::npos - ); - errorCount++; - } - BOOST_CHECK_EQUAL(errorCount, 1); -} - -BOOST_AUTO_TEST_CASE(shadowing_builtins_with_multiple_imports) -{ - CompilerStack c; - c.setSources({ - {"B.sol", "contract msg {} contract block{} pragma solidity >=0.0;"}, - {"b", R"( - pragma solidity >=0.0; - import {msg, block} from "B.sol"; - contract C { - })"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); - auto numErrors = c.errors().size(); - // Sometimes we get the prerelease warning, sometimes not. - BOOST_CHECK(4 <= numErrors && numErrors <= 5); - for (auto const& e: c.errors()) - { - string const* msg = e->comment(); - BOOST_REQUIRE(msg); - BOOST_CHECK( - msg->find("pre-release") != string::npos || - msg->find("shadows a builtin symbol") != string::npos - ); - } -} - -BOOST_AUTO_TEST_CASE(shadowing_builtins_with_alias) -{ - CompilerStack c; - c.setSources({ - {"B.sol", "contract C {} pragma solidity >=0.0;"}, - {"b", R"( - pragma solidity >=0.0; - import {C as msg} from "B.sol";)"} - }); - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); - auto numErrors = c.errors().size(); - // Sometimes we get the prerelease warning, sometimes not. - BOOST_CHECK(1 <= numErrors && numErrors <= 2); - for (auto const& e: c.errors()) - { - string const* msg = e->comment(); - BOOST_REQUIRE(msg); - BOOST_CHECK( - msg->find("pre-release") != string::npos || - msg->find("shadows a builtin symbol") != string::npos - ); - } -} - -BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_1) -{ - CompilerStack c; - c.setSources({ - {"A.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - contract A - { - struct S { uint a; } - S public s; - function f(S memory _s) returns (S memory,S memory) { } - } - )"}, - {"B.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - import "./A.sol"; - contract B is A { } - )"}, - {"C.sol", R"( - pragma solidity >=0.0; - - import "./B.sol"; - contract C is B { } - )"} - }); - - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); - - int typeErrors = 0; - - // Sometimes we get the prerelease warning, sometimes not. - for (auto const& e: c.errors()) - { - if (e->type() != langutil::Error::Type::TypeError) - continue; - - typeErrors++; - - string const* msg = e->comment(); - BOOST_REQUIRE(msg); - BOOST_CHECK_EQUAL(*msg, std::string("Contract \"C\" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature.")); - } - BOOST_CHECK_EQUAL(typeErrors, 1); -} - -BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_mismatch_2) -{ - CompilerStack c; - c.setSources({ - {"A.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - contract A - { - struct S { uint a; } - S public s; - function f(S memory _s) returns (S memory,S memory) { } - } - )"}, - {"B.sol", R"( - pragma solidity >=0.0; - - import "./A.sol"; - contract B is A { } - )"}, - {"C.sol", R"( - pragma solidity >=0.0; - - import "./B.sol"; - contract C is B { } - )"} - }); - - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(!c.compile()); - - int typeErrors = 0; - - // Sometimes we get the prerelease warning, sometimes not. - for (auto const& e: c.errors()) - { - if (e->type() != langutil::Error::Type::TypeError) - continue; - - typeErrors++; - - string const* msg = e->comment(); - BOOST_REQUIRE(msg); - BOOST_CHECK_EQUAL(*msg, std::string("Contract \"B\" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature.")); - } - BOOST_CHECK_EQUAL(typeErrors, 1); -} - -BOOST_AUTO_TEST_CASE(inheritance_abi_encoder_match) -{ - CompilerStack c; - c.setSources({ - {"A.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - contract A - { - struct S { uint a; } - S public s; - function f(S memory _s) public returns (S memory,S memory) { } - } - )"}, - {"B.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - import "./A.sol"; - contract B is A { } - )"}, - {"C.sol", R"( - pragma solidity >=0.0; - pragma experimental ABIEncoderV2; - - import "./B.sol"; - contract C is B { } - )"} - }); - - c.setEVMVersion(dev::test::Options::get().evmVersion()); - BOOST_CHECK(c.compile()); - - int typeErrors = 0; - - // Sometimes we get the prerelease warning, sometimes not. - for (auto const& e: c.errors()) - { - if (e->type() != langutil::Error::Type::TypeError) - continue; - - typeErrors++; - } - - BOOST_CHECK_EQUAL(typeErrors, 0); -} - BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 39cc6fbf2..76985f54d 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -836,6 +836,20 @@ BOOST_AUTO_TEST_CASE(shift_constantinople_warning) CHECK_PARSE_WARNING("{ pop(sar(10, 32)) }", TypeError, "The \"sar\" instruction is only available for Constantinople-compatible VMs"); } +BOOST_AUTO_TEST_CASE(chainid_instanbul_warning) +{ + if (dev::test::Options::get().evmVersion().hasChainID()) + return; + CHECK_PARSE_WARNING("{ pop(chainid()) }", TypeError, "The \"chainid\" instruction is only available for Istanbul-compatible VMs"); +} + +BOOST_AUTO_TEST_CASE(selfbalance_instanbul_warning) +{ + if (dev::test::Options::get().evmVersion().hasSelfBalance()) + return; + CHECK_PARSE_WARNING("{ pop(selfbalance()) }", TypeError, "The \"selfbalance\" instruction is only available for Istanbul-compatible VMs"); +} + BOOST_AUTO_TEST_CASE(jump_warning) { CHECK_PARSE_WARNING("{ 1 jump }", Warning, "Jump instructions"); diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 5e5fbb9c9..c7414d824 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -116,13 +116,15 @@ BOOST_AUTO_TEST_CASE(division) if (a == 0) { return 0; } + // TODO remove when SMTChecker sees that this code is the `else` of the `return`. + require(a != 0); uint256 c = a * b; require(c / a == b); return c; } } )"; - CHECK_WARNING(text, "Division by zero"); + CHECK_SUCCESS_OR_WARNING(text, "might happen"); text = R"( contract C { function div(uint256 a, uint256 b) internal pure returns (uint256) { @@ -210,7 +212,7 @@ BOOST_AUTO_TEST_CASE(compound_assignment_division) uint[] array; function f(uint x, uint p) public { require(x == 2); - require(array[p] == 10); + array[p] = 10; array[p] /= array[p] / x; assert(array[p] == x); assert(array[p] == 0); @@ -223,7 +225,7 @@ BOOST_AUTO_TEST_CASE(compound_assignment_division) mapping (uint => uint) map; function f(uint x, uint p) public { require(x == 2); - require(map[p] == 10); + map[p] = 10; map[p] /= map[p] / x; assert(map[p] == x); assert(map[p] == 0); diff --git a/test/libsolidity/SMTCheckerJSONTest.cpp b/test/libsolidity/SMTCheckerJSONTest.cpp index ae49ea279..a2ce4e26b 100644 --- a/test/libsolidity/SMTCheckerJSONTest.cpp +++ b/test/libsolidity/SMTCheckerJSONTest.cpp @@ -108,6 +108,9 @@ TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePr BOOST_THROW_EXCEPTION(runtime_error("Error must have a SourceLocation with start and end.")); int start = location["start"].asInt(); int end = location["end"].asInt(); + std::string sourceName; + if (location.isMember("source") && location["source"].isString()) + sourceName = location["source"].asString(); if (start >= static_cast(versionPragma.size())) start -= versionPragma.size(); if (end >= static_cast(versionPragma.size())) @@ -115,6 +118,7 @@ TestCase::TestResult SMTCheckerTest::run(ostream& _stream, string const& _linePr m_errorList.emplace_back(SyntaxTestError{ error["type"].asString(), error["message"].asString(), + sourceName, start, end }); @@ -141,13 +145,20 @@ vector SMTCheckerTest::hashesFromJson(Json::Value const& _jsonObj, strin Json::Value SMTCheckerTest::buildJson(string const& _extra) { string language = "\"language\": \"Solidity\""; - string sourceName = "\"A\""; - string sourceContent = "\"" + _extra + m_source + "\""; - string sourceObj = "{ \"content\": " + sourceContent + "}"; - string sources = " \"sources\": { " + sourceName + ": " + sourceObj + "}"; + string sources = " \"sources\": { "; + bool first = true; + for (auto [sourceName, sourceContent]: m_sources) + { + string sourceObj = "{ \"content\": \"" + _extra + sourceContent + "\"}"; + if (!first) + sources += ", "; + sources += "\"" + sourceName + "\": " + sourceObj; + first = false; + } + sources += "}"; string input = "{" + language + ", " + sources + "}"; Json::Value source; if (!jsonParse(input, source)) - BOOST_THROW_EXCEPTION(runtime_error("Could not build JSON from string.")); + BOOST_THROW_EXCEPTION(runtime_error("Could not build JSON from string: " + input)); return source; } diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index dd95c4dee..cdb4e3625 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -98,11 +98,13 @@ TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePref } else { - bytes output = callContractFunctionWithValueNoEncoding( - test.call().signature, - test.call().value, - test.call().arguments.rawBytes() - ); + bytes output = test.call().useCallWithoutSignature ? + callLowLevel(test.call().arguments.rawBytes(), test.call().value) : + callContractFunctionWithValueNoEncoding( + test.call().signature, + test.call().value, + test.call().arguments.rawBytes() + ); if ((m_transactionSuccessful == test.call().expectations.failure) || (output != test.call().expectations.rawBytes())) success = false; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 903740182..af940e0ef 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2514,23 +2514,6 @@ BOOST_AUTO_TEST_CASE(super_alone) ) } -BOOST_AUTO_TEST_CASE(fallback_function) -{ - char const* sourceCode = R"( - contract A { - uint data; - function() external { data = 1; } - function getData() public returns (uint r) { return data; } - } - )"; - ALSO_VIA_YUL( - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); - ABI_CHECK(callContractFunction(""), encodeArgs()); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); - ) -} - BOOST_AUTO_TEST_CASE(inherited_fallback_function) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityScanner.cpp b/test/libsolidity/SolidityScanner.cpp index debd30477..7e7b58469 100644 --- a/test/libsolidity/SolidityScanner.cpp +++ b/test/libsolidity/SolidityScanner.cpp @@ -568,7 +568,7 @@ BOOST_AUTO_TEST_CASE(multiline_comment_at_eos) BOOST_AUTO_TEST_CASE(regular_line_break_in_single_line_comment) { - for (auto const& nl: {"\r", "\n"}) + for (auto const& nl: {"\r", "\n", "\r\n"}) { Scanner scanner(CharStream("// abc " + string(nl) + " def ", "")); BOOST_CHECK_EQUAL(scanner.currentCommentLiteral(), ""); @@ -595,7 +595,7 @@ BOOST_AUTO_TEST_CASE(irregular_line_breaks_in_single_line_comment) BOOST_AUTO_TEST_CASE(regular_line_breaks_in_single_line_doc_comment) { - for (auto const& nl: {"\r", "\n"}) + for (auto const& nl: {"\r", "\n", "\r\n"}) { Scanner scanner(CharStream("/// abc " + string(nl) + " def ", "")); BOOST_CHECK_EQUAL(scanner.currentCommentLiteral(), "abc "); @@ -605,6 +605,22 @@ BOOST_AUTO_TEST_CASE(regular_line_breaks_in_single_line_doc_comment) } } +BOOST_AUTO_TEST_CASE(regular_line_breaks_in_multiline_doc_comment) +{ + // Test CR, LF, CRLF as line valid terminators for code comments. + // Any accepted non-LF is being canonicalized to LF. + for (auto const& nl : {"\r"s, "\n"s, "\r\n"s}) + { + Scanner scanner{CharStream{"/// Hello" + nl + "/// World" + nl + "ident", ""}}; + auto const& lit = scanner.currentCommentLiteral(); + BOOST_CHECK_EQUAL(lit, "Hello\n World"); + BOOST_CHECK_EQUAL(scanner.currentCommentLiteral(), "Hello\n World"); + BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Identifier); + BOOST_CHECK_EQUAL(scanner.currentLiteral(), "ident"); + BOOST_CHECK_EQUAL(scanner.next(), Token::EOS); + } +} + BOOST_AUTO_TEST_CASE(irregular_line_breaks_in_single_line_doc_comment) { for (auto const& nl: {"\v", "\f", "\xE2\x80\xA8", "\xE2\x80\xA9"}) @@ -622,9 +638,9 @@ BOOST_AUTO_TEST_CASE(irregular_line_breaks_in_single_line_doc_comment) BOOST_AUTO_TEST_CASE(regular_line_breaks_in_strings) { - for (auto const& nl: {"\n", "\r"}) + for (auto const& nl: {"\r"s, "\n"s, "\r\n"s}) { - Scanner scanner(CharStream("\"abc " + string(nl) + " def\"", "")); + Scanner scanner(CharStream("\"abc " + nl + " def\"", "")); BOOST_CHECK_EQUAL(scanner.currentToken(), Token::Illegal); BOOST_CHECK_EQUAL(scanner.next(), Token::Identifier); BOOST_CHECK_EQUAL(scanner.currentLiteral(), "def"); diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 45ca1e81d..41a4dd18b 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -59,7 +59,8 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion BOOST_THROW_EXCEPTION(runtime_error("Cannot open test contract: \"" + _filename + "\".")); file.exceptions(ios::badbit); - m_source = parseSourceAndSettings(file); + m_sources = parseSourcesAndSettings(file); + if (m_settings.count("optimize-yul")) { m_optimiseYul = true; @@ -74,7 +75,10 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix { string const versionPragma = "pragma solidity >=0.0;\n"; compiler().reset(); - compiler().setSources({{"", versionPragma + m_source}}); + auto sourcesWithPragma = m_sources; + for (auto& source: sourcesWithPragma) + source.second = versionPragma + source.second; + compiler().setSources(sourcesWithPragma); compiler().setEVMVersion(m_evmVersion); compiler().setParserErrorRecovery(m_parserErrorRecovery); compiler().setOptimiserSettings( @@ -83,11 +87,27 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix OptimiserSettings::minimal() ); if (compiler().parse()) - compiler().analyze(); + if (compiler().analyze()) + try + { + if (!compiler().compile()) + BOOST_THROW_EXCEPTION(runtime_error("Compilation failed even though analysis was successful.")); + } + catch (UnimplementedFeatureError const& _e) + { + m_errorList.emplace_back(SyntaxTestError{ + "UnimplementedFeatureError", + errorMessage(_e), + "", + -1, + -1 + }); + } for (auto const& currentError: filterErrors(compiler().errors(), true)) { int locationStart = -1, locationEnd = -1; + string sourceName; if (auto location = boost::get_error_info(*currentError)) { // ignore the version pragma inserted by the testing tool when calculating locations. @@ -95,10 +115,13 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix locationStart = location->start - versionPragma.size(); if (location->end >= static_cast(versionPragma.size())) locationEnd = location->end - versionPragma.size(); + if (location->source) + sourceName = location->source->name(); } m_errorList.emplace_back(SyntaxTestError{ currentError->typeName(), errorMessage(*currentError), + sourceName, locationStart, locationEnd }); @@ -123,51 +146,65 @@ bool SyntaxTest::printExpectationAndError(ostream& _stream, string const& _lineP void SyntaxTest::printSource(ostream& _stream, string const& _linePrefix, bool _formatted) const { + + if (m_sources.empty()) + return; + + bool outputSourceNames = true; + if (m_sources.size() == 1 && m_sources.begin()->first.empty()) + outputSourceNames = false; + if (_formatted) { - if (m_source.empty()) - return; - - vector sourceFormatting(m_source.length(), formatting::RESET); - for (auto const& error: m_errorList) - if (error.locationStart >= 0 && error.locationEnd >= 0) - { - assert(static_cast(error.locationStart) <= m_source.length()); - assert(static_cast(error.locationEnd) <= m_source.length()); - bool isWarning = error.type == "Warning"; - for (int i = error.locationStart; i < error.locationEnd; i++) - if (isWarning) - { - if (sourceFormatting[i] == formatting::RESET) - sourceFormatting[i] = formatting::ORANGE_BACKGROUND_256; - } - else - sourceFormatting[i] = formatting::RED_BACKGROUND; - } - - _stream << _linePrefix << sourceFormatting.front() << m_source.front(); - for (size_t i = 1; i < m_source.length(); i++) + for (auto const& [name, source]: m_sources) { - if (sourceFormatting[i] != sourceFormatting[i - 1]) - _stream << sourceFormatting[i]; - if (m_source[i] != '\n') - _stream << m_source[i]; - else + if (outputSourceNames) + _stream << _linePrefix << formatting::CYAN << "==== Source: " << name << " ====" << formatting::RESET << endl; + vector sourceFormatting(source.length(), formatting::RESET); + for (auto const& error: m_errorList) + if (error.sourceName == name && error.locationStart >= 0 && error.locationEnd >= 0) + { + assert(static_cast(error.locationStart) <= source.length()); + assert(static_cast(error.locationEnd) <= source.length()); + bool isWarning = error.type == "Warning"; + for (int i = error.locationStart; i < error.locationEnd; i++) + if (isWarning) + { + if (sourceFormatting[i] == formatting::RESET) + sourceFormatting[i] = formatting::ORANGE_BACKGROUND_256; + } + else + sourceFormatting[i] = formatting::RED_BACKGROUND; + } + + _stream << _linePrefix << sourceFormatting.front() << source.front(); + for (size_t i = 1; i < source.length(); i++) { - _stream << formatting::RESET << endl; - if (i + 1 < m_source.length()) - _stream << _linePrefix << sourceFormatting[i]; + if (sourceFormatting[i] != sourceFormatting[i - 1]) + _stream << sourceFormatting[i]; + if (source[i] != '\n') + _stream << source[i]; + else + { + _stream << formatting::RESET << endl; + if (i + 1 < source.length()) + _stream << _linePrefix << sourceFormatting[i]; + } } + _stream << formatting::RESET; } - _stream << formatting::RESET; + } else - { - stringstream stream(m_source); - string line; - while (getline(stream, line)) - _stream << _linePrefix << line << endl; - } + for (auto const& [name, source]: m_sources) + { + if (outputSourceNames) + _stream << _linePrefix << "==== Source: " + name << " ====" << endl; + stringstream stream(source); + string line; + while (getline(stream, line)) + _stream << _linePrefix << line << endl; + } } void SyntaxTest::printErrorList( @@ -187,9 +224,11 @@ void SyntaxTest::printErrorList( _stream << _linePrefix; _stream << error.type << ": "; } - if (error.locationStart >= 0 || error.locationEnd >= 0) + if (!error.sourceName.empty() || error.locationStart >= 0 || error.locationEnd >= 0) { _stream << "("; + if (!error.sourceName.empty()) + _stream << error.sourceName << ":"; if (error.locationStart >= 0) _stream << error.locationStart; _stream << "-"; @@ -234,10 +273,19 @@ vector SyntaxTest::parseExpectations(istream& _stream) int locationStart = -1; int locationEnd = -1; + std::string sourceName; if (it != line.end() && *it == '(') { ++it; + if (it != line.end() && !isdigit(*it)) + { + auto sourceNameStart = it; + while (it != line.end() && *it != ':') + ++it; + sourceName = std::string(sourceNameStart, it); + expect(it, line.end(), ':'); + } locationStart = parseUnsignedInteger(it, line.end()); expect(it, line.end(), '-'); locationEnd = parseUnsignedInteger(it, line.end()); @@ -251,6 +299,7 @@ vector SyntaxTest::parseExpectations(istream& _stream) expectations.emplace_back(SyntaxTestError{ move(errorType), move(errorMessage), + move(sourceName), locationStart, locationEnd }); diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index 62a85d864..53c06d3fe 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -38,12 +38,14 @@ struct SyntaxTestError { std::string type; std::string message; + std::string sourceName; int locationStart; int locationEnd; bool operator==(SyntaxTestError const& _rhs) const { return type == _rhs.type && message == _rhs.message && + sourceName == _rhs.sourceName && locationStart == _rhs.locationStart && locationEnd == _rhs.locationEnd; } @@ -85,7 +87,7 @@ protected: static std::vector parseExpectations(std::istream& _stream); - std::string m_source; + std::map m_sources; std::vector m_expectations; std::vector m_errorList; bool m_optimiseYul = false; diff --git a/test/libsolidity/gasTests/abiv2_optimised.sol b/test/libsolidity/gasTests/abiv2_optimised.sol index dcd52b1e2..83d07911f 100644 --- a/test/libsolidity/gasTests/abiv2_optimised.sol +++ b/test/libsolidity/gasTests/abiv2_optimised.sol @@ -17,9 +17,9 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 610600 +// codeDepositCost: 608400 // executionCost: 645 -// totalCost: 611245 +// totalCost: 609045 // external: // a(): 429 // b(uint256): 884 diff --git a/test/libsolidity/semanticTests/smoke_test_alignment.sol b/test/libsolidity/semanticTests/smoke/alignment.sol similarity index 99% rename from test/libsolidity/semanticTests/smoke_test_alignment.sol rename to test/libsolidity/semanticTests/smoke/alignment.sol index b5e23c022..621581468 100644 --- a/test/libsolidity/semanticTests/smoke_test_alignment.sol +++ b/test/libsolidity/semanticTests/smoke/alignment.sol @@ -26,3 +26,4 @@ contract D { // stateBytes() -> left(0x4200ef) // internalStateDecimal() -> 0x20 // update(bool,uint256,bytes32): false, -23, left(0x2300ef) -> false, -23, left(0x2300ef) + diff --git a/test/libsolidity/semanticTests/smoke/arrays.sol b/test/libsolidity/semanticTests/smoke/arrays.sol new file mode 100644 index 000000000..34ec984bb --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/arrays.sol @@ -0,0 +1,44 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct T { + uint a; + uint b; + string s; + } + bool[2][] flags; + function r() public returns (bool[3] memory) { + return [true, false, true]; + } + function s() public returns (uint[2] memory, uint) { + return ([uint(123), 456], 789); + } + function u() public returns (T[2] memory) { + return [T(23, 42, "any"), T(555, 666, "any")]; + } + function v() public returns (bool[2][] memory) { + return flags; + } + function w1() public returns (string[1] memory) { + return ["any"]; + } + function w2() public returns (string[2] memory) { + return ["any", "any"]; + } + function w3() public returns (string[3] memory) { + return ["any", "any", "any"]; + } + function x() public returns (string[2] memory, string[3] memory) { + return (["any", "any"], ["any", "any", "any"]); + } +} +// ---- +// r() -> true, false, true +// s() -> 123, 456, 789 +// u() -> 0x20, 0x40, 0xE0, 23, 42, 0x60, 3, "any", 555, 666, 0x60, 3, "any" +// v() -> 0x20, 0 +// w1() -> 0x20, 0x20, 3, "any" +// w2() -> 0x20, 0x40, 0x80, 3, "any", 3, "any" +// w3() -> 0x20, 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any" +// x() -> 0x40, 0x0100, 0x40, 0x80, 3, "any", 3, "any", 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any" + diff --git a/test/libsolidity/semanticTests/smoke/basic.sol b/test/libsolidity/semanticTests/smoke/basic.sol new file mode 100644 index 000000000..403b13c7b --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/basic.sol @@ -0,0 +1,40 @@ +pragma experimental ABIEncoderV2; + +contract C { + function d() public { + } + function e() public payable returns (uint) { + return msg.value; + } + function f(uint a) public pure returns (uint, uint) { + return (a, a); + } + function g() public pure returns (uint, uint) { + return (2, 3); + } + function h(uint x, uint y) public pure returns (uint) { + return x - y; + } + function i(bool b) public pure returns (bool) { + return !b; + } + function j(bytes32 b) public pure returns (bytes32, bytes32) { + return (b, b); + } + function k() public pure returns (uint) { + return msg.data.length; + } + function l(uint a) public pure returns (uint d) { + return a * 7; + } +} +// ---- +// d() -> +// e(), 1 ether -> 1 +// f(uint256): 3 -> 3, 3 +// g() -> 2, 3 +// h(uint256,uint256): 1, -2 -> 3 +// i(bool): true -> false +// j(bytes32): 0x10001 -> 0x10001, 0x10001 +// k(): hex"4200efef" -> 8 +// l(uint256): 99 -> 693 diff --git a/test/libsolidity/semanticTests/smoke/bytes_and_strings.sol b/test/libsolidity/semanticTests/smoke/bytes_and_strings.sol new file mode 100644 index 000000000..db6f35497 --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/bytes_and_strings.sol @@ -0,0 +1,22 @@ +contract C { + function e(bytes memory b) public pure returns (bytes memory) { + return b; + } + function f() public pure returns (string memory, string memory) { + return ("any", "any"); + } + function g() public pure returns (string memory, uint, string memory) { + return ("any", 42, "any"); + } + function h() public pure returns (string memory) { + return "any"; + } +} +// ---- +// e(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB) +// e(bytes): 32, 32, 0x20 -> 32, 32, 0x20 +// e(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000" +// f() -> 0x40, 0x80, 3, "any", 3, "any" +// g() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any" +// h() -> 0x20, 3, "any" + diff --git a/test/libsolidity/semanticTests/smoke/constructor.sol b/test/libsolidity/semanticTests/smoke/constructor.sol new file mode 100644 index 000000000..9aac2ed7b --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/constructor.sol @@ -0,0 +1,18 @@ +contract C { + uint public state = 0; + constructor(uint _state) public payable { + state = _state; + } + function balance() public payable returns (uint256) { + return address(this).balance; + } + function update(uint _state) public { + state = _state; + } +} +// ---- +// constructor(), 2 ether: 3 -> +// state() -> 3 +// balance() -> 2 +// update(uint256): 4 +// state() -> 4 diff --git a/test/libsolidity/semanticTests/smoke/failure.sol b/test/libsolidity/semanticTests/smoke/failure.sol new file mode 100644 index 000000000..ee1064062 --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/failure.sol @@ -0,0 +1,23 @@ +contract C { + function e() public pure { + revert("Transaction failed."); + } + function f(bool _value) public pure { + string memory message; + require(_value, message); + } + function g(bool _value) public pure { + require(_value, "Value is false."); + } + function h() public pure returns (uint) { + assert(false); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// _() -> FAILURE +// e() -> FAILURE, hex"08c379a0", 0x20, 19, "Transaction failed." +// f(bool): false -> FAILURE, hex"08c379a0", 0x20, 0 +// g(bool): false -> FAILURE, hex"08c379a0", 0x20, 15, "Value is false." +// h() -> FAILURE \ No newline at end of file diff --git a/test/libsolidity/semanticTests/smoke/fallback.sol b/test/libsolidity/semanticTests/smoke/fallback.sol new file mode 100644 index 000000000..71d17cf0c --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/fallback.sol @@ -0,0 +1,23 @@ +contract A { + uint public data; + uint public balance; + bytes public externalData; + function() external payable { + data += 1; + balance = msg.value; + externalData = msg.data; + } +} +// ---- +// data() -> 0 +// () +// data() -> 1 +// (): hex"42ef" +// data() -> 2 +// externalData() -> 0x20, 2, left(0x42ef) +// balance() -> 0 +// (), 1 ether +// balance() -> 1 +// (), 2 ether: hex"fefe" +// balance() -> 2 +// externalData() -> 0x20, 2, left(0xfefe) \ No newline at end of file diff --git a/test/libsolidity/semanticTests/smoke_test_multiline.sol b/test/libsolidity/semanticTests/smoke/multiline.sol similarity index 99% rename from test/libsolidity/semanticTests/smoke_test_multiline.sol rename to test/libsolidity/semanticTests/smoke/multiline.sol index 5d02e148e..1663a6de4 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline.sol +++ b/test/libsolidity/semanticTests/smoke/multiline.sol @@ -11,3 +11,4 @@ contract C { // g() // # g() does not exist # // -> FAILURE + diff --git a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol b/test/libsolidity/semanticTests/smoke/multiline_comments.sol similarity index 99% rename from test/libsolidity/semanticTests/smoke_test_multiline_comments.sol rename to test/libsolidity/semanticTests/smoke/multiline_comments.sol index 66bdfb915..3e1d59a6d 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol +++ b/test/libsolidity/semanticTests/smoke/multiline_comments.sol @@ -17,3 +17,4 @@ contract C { // 1 // -> 5 // # Should return sum of all parameters. # + diff --git a/test/libsolidity/semanticTests/smoke/structs.sol b/test/libsolidity/semanticTests/smoke/structs.sol new file mode 100644 index 000000000..979dbdcc0 --- /dev/null +++ b/test/libsolidity/semanticTests/smoke/structs.sol @@ -0,0 +1,22 @@ +pragma experimental ABIEncoderV2; + +contract C { + struct S { + uint a; + uint b; + } + struct T { + uint a; + uint b; + string s; + } + function s() public returns (S memory) { + return S(23, 42); + } + function t() public returns (T memory) { + return T(23, 42, "any"); + } +} +// ---- +// s() -> 23, 42 +// t() -> 0x20, 23, 42, 0x60, 3, "any" diff --git a/test/libsolidity/semanticTests/smoke_test.sol b/test/libsolidity/semanticTests/smoke_test.sol deleted file mode 100644 index 23ad109a0..000000000 --- a/test/libsolidity/semanticTests/smoke_test.sol +++ /dev/null @@ -1,121 +0,0 @@ -pragma experimental ABIEncoderV2; - -contract C { - struct S { - uint a; - uint b; - } - struct T { - uint a; - uint b; - string s; - } - uint public state = 0; - bool[2][] flags; - constructor(uint _state) public payable { - state = _state; - } - function balance() payable public returns (uint256) { - return address(this).balance; - } - function e(uint a) public { - state = a; - } - function f() payable public returns (uint) { - return 2; - } - function f(uint a) public returns (uint, uint) { - return (a, a); - } - function g() public returns (uint, uint) { - return (2, 3); - } - function h(uint x, uint y) public returns (uint) { - return x - y; - } - function j(bool b) public returns (bool) { - return !b; - } - function k(bytes32 b) public returns (bytes32, bytes32) { - return (b, b); - } - function l() public returns (uint256) { - return msg.data.length; - } - function m(bytes memory b) public returns (bytes memory) { - return b; - } - function n() public returns (string memory) { - return "any"; - } - function o() public returns (string memory, string memory) { - return ("any", "any"); - } - function p() public returns (string memory, uint, string memory) { - return ("any", 42, "any"); - } - function q(uint a) public returns (uint d) { - return a * 7; - } - function r() public returns (bool[3] memory) { - return [true, false, true]; - } - function s() public returns (uint[2] memory, uint) { - return ([uint(123), 456], 789); - } - function t1() public returns (S memory) { - return S(23, 42); - } - function t2() public returns (T memory) { - return T(23, 42, "any"); - } - function u() public returns (T[2] memory) { - return [T(23, 42, "any"), T(555, 666, "any")]; - } - function v() public returns (bool[2][] memory) { - return flags; - } - function w1() public returns (string[1] memory) { - return ["any"]; - } - function w2() public returns (string[2] memory) { - return ["any", "any"]; - } - function w3() public returns (string[3] memory) { - return ["any", "any", "any"]; - } - function x() public returns (string[2] memory, string[3] memory) { - return (["any", "any"], ["any", "any", "any"]); - } -} -// ---- -// constructor(), 2 ether: 3 -> -// state() -> 3 -// balance() -> 2 -// _() -> FAILURE -// e(uint256): 4 -// f() -> 2 -// f(uint256): 3 -> 3, 3 -// f(), 1 ether -> 2 -// g() -> 2, 3 -// g1() -> FAILURE -// h(uint256,uint256): 1, -2 -> 3 -// j(bool): true -> false -// k(bytes32): 0x10001 -> 0x10001, 0x10001 -// l(): hex"4200efef" -> 8 -// m(bytes): 32, 32, 0x20 -> 32, 32, 0x20 -// m(bytes): 32, 3, hex"AB33BB" -> 32, 3, left(0xAB33BB) -// m(bytes): 32, 3, hex"AB33FF" -> 32, 3, hex"ab33ff0000000000000000000000000000000000000000000000000000000000" -// n() -> 0x20, 3, "any" -// o() -> 0x40, 0x80, 3, "any", 3, "any" -// p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any" -// q(uint256): 99 -> 693 -// r() -> true, false, true -// s() -> 123, 456, 789 -// t1() -> 23, 42 -// t2() -> 0x20, 23, 42, 0x60, 3, "any" -// v() -> 32, 0 -// w1() -> 0x20, 0x20, 3, "any" -// w2() -> 0x20, 0x40, 0x80, 3, "any", 3, "any" -// w3() -> 0x20, 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any" -// x() -> 0x40, 0x0100, 0x40, 0x80, 3, "any", 3, "any", 0x60, 0xa0, 0xe0, 3, "any", 3, "any", 3, "any" diff --git a/test/libsolidity/semanticTests/storage/mappings_array2d_pop_delete.sol b/test/libsolidity/semanticTests/storage/mappings_array2d_pop_delete.sol new file mode 100644 index 000000000..d8cf3d7ca --- /dev/null +++ b/test/libsolidity/semanticTests/storage/mappings_array2d_pop_delete.sol @@ -0,0 +1,39 @@ +contract C { + mapping (uint => uint)[][] a; + + function n1(uint key, uint value) public { + a.length++; + mapping (uint => uint)[] storage b = a[a.length - 1]; + b.length++; + b[b.length - 1][key] = value; + } + + function n2() public { + a.length++; + mapping (uint => uint)[] storage b = a[a.length - 1]; + b.length++; + } + + function map(uint key) public view returns (uint) { + mapping (uint => uint)[] storage b = a[a.length - 1]; + return b[b.length - 1][key]; + } + + function p() public { + a.pop(); + } + + function d() public returns (uint) { + delete a; + return a.length; + } +} +// ---- +// n1(uint256,uint256): 42, 64 -> +// map(uint256): 42 -> 64 +// p() -> +// n2() -> +// map(uint256): 42 -> 64 +// d() -> 0 +// n2() -> +// map(uint256): 42 -> 64 diff --git a/test/libsolidity/semanticTests/storage/mappings_array_pop_delete.sol b/test/libsolidity/semanticTests/storage/mappings_array_pop_delete.sol new file mode 100644 index 000000000..6c2fce695 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/mappings_array_pop_delete.sol @@ -0,0 +1,34 @@ +contract C { + mapping (uint => uint)[] a; + + function n1(uint key, uint value) public { + a.length++; + a[a.length - 1][key] = value; + } + + function n2() public { + a.length++; + } + + function map(uint key) public view returns (uint) { + return a[a.length - 1][key]; + } + + function p() public { + a.pop(); + } + + function d() public returns (uint) { + delete a; + return a.length; + } +} +// ---- +// n1(uint256,uint256): 42, 64 -> +// map(uint256): 42 -> 64 +// p() -> +// n2() -> +// map(uint256): 42 -> 64 +// d() -> 0 +// n2() -> +// map(uint256): 42 -> 64 diff --git a/test/libsolidity/semanticTests/structs/array_of_recursive_struct.sol b/test/libsolidity/semanticTests/structs/array_of_recursive_struct.sol new file mode 100644 index 000000000..16ee9d59b --- /dev/null +++ b/test/libsolidity/semanticTests/structs/array_of_recursive_struct.sol @@ -0,0 +1,12 @@ +contract Test { + struct RecursiveStruct { + RecursiveStruct[] vals; + } + + function func() public pure { + RecursiveStruct[1] memory val = [ RecursiveStruct(new RecursiveStruct[](42)) ]; + assert(val[0].vals.length == 42); + } +} +// ----- +// func() -> diff --git a/test/libsolidity/semanticTests/types/assign_calldata_value_type.sol b/test/libsolidity/semanticTests/types/assign_calldata_value_type.sol new file mode 100644 index 000000000..8350628e4 --- /dev/null +++ b/test/libsolidity/semanticTests/types/assign_calldata_value_type.sol @@ -0,0 +1,9 @@ +contract C { + function f(uint256 x) public pure returns (uint256, uint256) { + uint256 b = x; + x = 42; + return (x, b); + } +} +// ---- +// f(uint256): 23 -> 42, 23 diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_array.sol b/test/libsolidity/smtCheckerTests/invariants/loop_array.sol new file mode 100644 index 000000000..0f6937d36 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_array.sol @@ -0,0 +1,19 @@ +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/invariants/loop_array_for.sol b/test/libsolidity/smtCheckerTests/invariants/loop_array_for.sol new file mode 100644 index 000000000..1bd8c7517 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_array_for.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract Simple { + uint[] a; + function f(uint n) public { + uint i; + for (i = 0; i < n; ++i) + a[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: (267-290): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_basic.sol b/test/libsolidity/smtCheckerTests/invariants/loop_basic.sol new file mode 100644 index 000000000..9bfdaf643 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_basic.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; + +contract Simple { + function f(uint x) public pure { + uint y; + require(x > 0); + while (y < x) + ++y; + assert(y == x); + } +} diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_basic_for.sol b/test/libsolidity/smtCheckerTests/invariants/loop_basic_for.sol new file mode 100644 index 000000000..55019cf0c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_basic_for.sol @@ -0,0 +1,10 @@ +pragma experimental SMTChecker; + +contract Simple { + function f(uint x) public pure { + uint y; + for (y = 0; y < x; ++y) {} + assert(y == x); + } +} +// ---- diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_nested.sol b/test/libsolidity/smtCheckerTests/invariants/loop_nested.sol new file mode 100644 index 000000000..b17140a0d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_nested.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract Simple { + function f() public pure { + uint x = 10; + uint y; + while (y < x) + { + ++y; + x = 0; + while (x < 10) + ++x; + assert(x == 10); + } + assert(y == x); + } +} diff --git a/test/libsolidity/smtCheckerTests/invariants/loop_nested_for.sol b/test/libsolidity/smtCheckerTests/invariants/loop_nested_for.sol new file mode 100644 index 000000000..ac7f0f7d1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/loop_nested_for.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract Simple { + function f() public pure { + uint x; + uint y; + for (x = 10; y < x; ++y) + { + for (x = 0; x < 10; ++x) {} + assert(x == 10); + } + assert(y == x); + } +} diff --git a/test/libsolidity/smtCheckerTests/invariants/state_machine_1.sol b/test/libsolidity/smtCheckerTests/invariants/state_machine_1.sol new file mode 100644 index 000000000..9cc43504c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/state_machine_1.sol @@ -0,0 +1,32 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + + function f() public { + if (x == 0) + x = 1; + } + + function g() public { + if (x == 1) + x = 2; + } + + function h() public { + if (x == 2) + x = 0; + } + + // This function shows that (x < 9) is not inductive and + // a stronger invariant is needed to be found. + // (x < 3) is the one found in the end. + function j() public { + if (x == 7) + x = 100; + } + + function i() public view { + assert(x < 9); + } +} diff --git a/test/libsolidity/smtCheckerTests/invariants/state_machine_1_fail.sol b/test/libsolidity/smtCheckerTests/invariants/state_machine_1_fail.sol new file mode 100644 index 000000000..7699337a1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/invariants/state_machine_1_fail.sol @@ -0,0 +1,32 @@ +pragma experimental SMTChecker; + +contract C { + uint x; + + function f() public { + if (x == 0) + x = 1; + } + + function g() public { + if (x == 1) + x = 2; + } + + function h() public { + if (x == 2) + x = 0; + } + + function j() public { + if (x < 2) + x = 100; + } + + // Fails due to j. + function i() public view { + assert(x < 2); + } +} +// ---- +// Warning: (311-324): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/do_while_1_false_positives.sol b/test/libsolidity/smtCheckerTests/loops/do_while_1_false_positives.sol index 4690af4e6..c5f5cf44d 100644 --- a/test/libsolidity/smtCheckerTests/loops/do_while_1_false_positives.sol +++ b/test/libsolidity/smtCheckerTests/loops/do_while_1_false_positives.sol @@ -15,4 +15,3 @@ contract C } // ---- // Warning: (150-155): Overflow (resulting value larger than 2**256 - 1) happens here -// Warning: (269-282): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_1_false_positive.sol b/test/libsolidity/smtCheckerTests/loops/for_1_false_positive.sol index d90667dec..11cd22d11 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_1_false_positive.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_1_false_positive.sol @@ -8,10 +8,12 @@ contract C // Overflows due to resetting x. x = x + 1; } - // The assertion is true but x is touched and reset. + // Assertion is safe but current solver version cannot solve it. + // Keep test for next solver release. assert(x > 0); } } // ---- +// Warning: (296-309): Error trying to invoke SMT solver. // Warning: (176-181): Overflow (resulting value larger than 2**256 - 1) happens here -// Warning: (244-257): Assertion violation happens here +// Warning: (296-309): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_6.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_6.sol index b0c3cae4c..9c54822e3 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_6.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_6.sol @@ -10,4 +10,3 @@ contract C { } } // ---- -// Warning: (213-226): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol index d545202f1..d7d88424a 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_memory.sol @@ -18,5 +18,4 @@ contract LoopFor2 { } } // ---- -// Warning: (265-285): Assertion violation happens here // Warning: (312-331): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol index 0c3f84996..a6793af13 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_array_assignment_storage_storage.sol @@ -18,6 +18,5 @@ contract LoopFor2 { } } // ---- -// Warning: (266-286): Assertion violation happens here // Warning: (290-309): Assertion violation happens here // Warning: (313-332): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/for_loop_trivial_condition_3.sol b/test/libsolidity/smtCheckerTests/loops/for_loop_trivial_condition_3.sol index eec59ded8..bb928e2c1 100644 --- a/test/libsolidity/smtCheckerTests/loops/for_loop_trivial_condition_3.sol +++ b/test/libsolidity/smtCheckerTests/loops/for_loop_trivial_condition_3.sol @@ -15,4 +15,3 @@ contract C { } // ---- // Warning: (115-121): Unused local variable. -// Warning: (356-370): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1.sol b/test/libsolidity/smtCheckerTests/loops/while_1.sol index 871ed9298..cb7fc5272 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_1.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_1.sol @@ -14,4 +14,3 @@ contract C } } // ---- -// Warning: (177-190): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1_break.sol b/test/libsolidity/smtCheckerTests/loops/while_1_break.sol new file mode 100644 index 000000000..b406a336d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_1_break.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, bool b) public pure { + require(x < 10); + while (x < 10) { + if (b) + ++x; + else { + x = 20; + break; + } + } + // Assertion is safe but break is unsupported for now + // so knowledge is erased. + assert(x >= 10); + } +} +// ---- +// Warning: (274-289): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1_break_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_1_break_fail.sol new file mode 100644 index 000000000..f187eac3e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_1_break_fail.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, bool b) public pure { + require(x < 10); + while (x < 10) { + if (b) + ++x; + else { + break; + } + } + // Fails because the loop might break. + assert(x >= 10); + } +} +// ---- +// Warning: (218-233): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1_continue.sol b/test/libsolidity/smtCheckerTests/loops/while_1_continue.sol new file mode 100644 index 000000000..72e2dfd78 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_1_continue.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, bool b) public pure { + require(x < 100); + while (x < 10) { + if (b) { + x = 15; + continue; + } + else + x = 20; + + } + // Should be safe, but fails due to continue being unsupported + // and erasing all knowledge. + assert(x >= 15); + } +} +// ---- +// Warning: (294-309): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1_continue_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_1_continue_fail.sol new file mode 100644 index 000000000..389942906 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_1_continue_fail.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, bool b) public pure { + require(x < 100); + while (x < 10) { + if (b) { + x = 15; + continue; + } + else + x = 20; + + } + // Fails due to the if. + assert(x >= 17); + } +} +// ---- +// Warning: (223-238): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_1_infinite.sol b/test/libsolidity/smtCheckerTests/loops/while_1_infinite.sol new file mode 100644 index 000000000..54b32b432 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_1_infinite.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x, bool b) public pure { + require(x < 100); + while (x < 10) { + if (b) + x = x + 1; + else + x = 0; + } + // CHC proves it safe because + // 1- if it doesn't go in the loop in the first place, x >= 10 + // 2- if it goes in the loop and b == true, x increases until >= 10 + // 3- if it goes in the loop and b == false, it's an infinite loop, therefore + // the assertion and the error are unreachable. + assert(x > 0); + } +} +// ---- diff --git a/test/libsolidity/smtCheckerTests/loops/while_2.sol b/test/libsolidity/smtCheckerTests/loops/while_2.sol new file mode 100644 index 000000000..00a71aca6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_2.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; +contract C { + function f(uint x) public pure { + x = 2; + while (x > 1) { + if (x > 10) + x = 2; + else + --x; + } + assert(x < 2); + } +} diff --git a/test/libsolidity/smtCheckerTests/loops/while_2_break.sol b/test/libsolidity/smtCheckerTests/loops/while_2_break.sol new file mode 100644 index 000000000..8fde4b06d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_2_break.sol @@ -0,0 +1,19 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint x = 0; + while (x == 0) { + ++x; + break; + ++x; + } + // Assertion is safe but break is unsupported for now + // so knowledge is erased. + assert(x == 1); + } +} +// ---- +// Warning: (128-131): Unreachable code. +// Warning: (224-238): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_2_break_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_2_break_fail.sol new file mode 100644 index 000000000..efd9bb18b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/loops/while_2_break_fail.sol @@ -0,0 +1,16 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint x) public pure { + while (x == 0) { + ++x; + break; + ++x; + } + assert(x == 2); + } +} +// ---- +// Warning: (120-123): Unreachable code. +// Warning: (131-145): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_2_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_2_fail.sol index 4c52287db..3c3e50468 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_2_fail.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_2_fail.sol @@ -12,4 +12,3 @@ contract C { } } // ---- -// Warning: (158-172): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_memory_storage.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_memory_storage.sol index 466cb9211..70bd231bb 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_memory_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_memory_storage.sol @@ -13,11 +13,13 @@ contract LoopFor2 { c[i] = b[i]; ++i; } + // Fails due to aliasing, since both b and c are + // memory references of same type. assert(b[0] == c[0]); assert(a[0] == 900); assert(b[0] == 900); } } // ---- -// Warning: (274-294): Assertion violation happens here -// Warning: (321-340): Assertion violation happens here +// Warning: (362-382): Assertion violation happens here +// Warning: (409-428): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_memory.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_memory.sol index 74a53a3ba..6cb6cd6d9 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_memory.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_memory.sol @@ -20,5 +20,4 @@ contract LoopFor2 { } } // ---- -// Warning: (265-285): Assertion violation happens here // Warning: (312-331): 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 index bbc9a2661..7670ddb6d 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_array_assignment_storage_storage.sol @@ -20,6 +20,5 @@ contract LoopFor2 { } } // ---- -// Warning: (266-286): Assertion violation happens here // Warning: (290-309): Assertion violation happens here // Warning: (313-332): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/loops/while_loop_simple_4.sol b/test/libsolidity/smtCheckerTests/loops/while_loop_simple_4.sol index 5a3aee9ef..792a90e23 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_loop_simple_4.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_loop_simple_4.sol @@ -8,4 +8,3 @@ contract C { } } // ---- -// Warning: (199-213): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol index 5ccaec149..7a420c28f 100644 --- a/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_code_after_placeholder.sol @@ -15,6 +15,10 @@ contract C assert(x > 0); x = x + 1; } + + function g(uint _x) public { + x = _x; + } } // ---- // Warning: (136-149): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol index 8a508cd68..241a367ea 100644 --- a/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_multi.sol @@ -20,6 +20,10 @@ contract C function f() m n public { x = x + 1; } + + function g(uint _x) public { + x = _x; + } } // ---- // Warning: (170-183): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol index 288506607..65b07f881 100644 --- a/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol +++ b/test/libsolidity/smtCheckerTests/modifiers/modifier_two_placeholders.sol @@ -17,6 +17,10 @@ contract C function f() m public { x = x + 1; } + + function g(uint _x) public { + x = _x; + } } // ---- // Warning: (156-170): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol index 816aff23a..c4acd3842 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_array_index.sol @@ -5,11 +5,11 @@ contract C uint[] array; function f(uint x, uint p) public { require(x < 100); - require(array[p] == 100); + array[p] = 100; array[p] += array[p] + x; assert(array[p] < 300); assert(array[p] < 110); } } // ---- -// Warning: (202-224): Assertion violation happens here +// Warning: (192-214): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol index f342ab97d..eab7df2f0 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_add_mapping.sol @@ -5,11 +5,11 @@ contract C mapping (uint => uint) map; function f(uint x, uint p) public { require(x < 100); - require(map[p] == 100); + map[p] = 100; map[p] += map[p] + x; assert(map[p] < 300); assert(map[p] < 110); } } // ---- -// Warning: (208-228): Assertion violation happens here +// Warning: (198-218): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol index ea837801d..110eb868e 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_mul_array_index.sol @@ -5,11 +5,11 @@ contract C uint[] array; function f(uint x, uint p) public { require(x < 10); - require(array[p] == 10); + array[p] = 10; array[p] *= array[p] + x; assert(array[p] <= 190); assert(array[p] < 50); } } // ---- -// Warning: (201-222): Assertion violation happens here +// Warning: (191-212): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol index fbcb5d88c..cd73bc687 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_mul_mapping.sol @@ -5,11 +5,11 @@ contract C mapping (uint => uint) map; function f(uint x, uint p) public { require(x < 10); - require(map[p] == 10); + map[p] = 10; map[p] *= map[p] + x; assert(map[p] <= 190); assert(map[p] < 50); } } // ---- -// Warning: (207-226): Assertion violation happens here +// Warning: (197-216): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol b/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol index b966337e9..8c8f84627 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_sub_array_index.sol @@ -5,11 +5,11 @@ contract C uint[] array; function f(uint x, uint p) public { require(x < 100); - require(array[p] == 200); + array[p] = 200; array[p] -= array[p] - x; assert(array[p] >= 0); assert(array[p] < 90); } } // ---- -// Warning: (201-222): Assertion violation happens here +// Warning: (191-212): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol b/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol index df20d3712..92e315832 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_sub_mapping.sol @@ -5,11 +5,11 @@ contract C mapping (uint => uint) map; function f(uint x, uint p) public { require(x < 100); - require(map[p] == 200); + map[p] = 200; map[p] -= map[p] - x; assert(map[p] >= 0); assert(map[p] < 90); } } // ---- -// Warning: (207-226): Assertion violation happens here +// Warning: (197-216): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array.sol b/test/libsolidity/smtCheckerTests/operators/delete_array.sol index 0a223d2a5..d34b0862c 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array.sol @@ -10,12 +10,9 @@ contract C delete a; else delete a[2]; - // Assertion fails as false positive because - // setZeroValue for arrays needs \forall i . a[i] = 0 - // which is still unimplemented. assert(a[2] == 0); + assert(a[1] == 0); } } // ---- // Warning: (118-119): Condition is always true. -// Warning: (297-314): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array_2d.sol b/test/libsolidity/smtCheckerTests/operators/delete_array_2d.sol index b8ce28f58..deff5bd10 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array_2d.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array_2d.sol @@ -6,10 +6,6 @@ contract C function f() public { require(a[2][3] == 4); delete a; - // Fails as false positive. - // setZeroValue needs forall for arrays. assert(a[2][3] == 0); } } -// ---- -// Warning: (194-214): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol index cafc09207..3e13c7f84 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_array_index_2d.sol @@ -9,11 +9,7 @@ contract C delete a; else delete a[2]; - // Fails as false positive since - // setZeroValue for arrays needs forall - // which is unimplemented. assert(a[2][3] == 0); + assert(a[1][1] == 0); } } -// ---- -// Warning: (266-286): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/operators/delete_function.sol b/test/libsolidity/smtCheckerTests/operators/delete_function.sol index 5c730b3d8..62579d7da 100644 --- a/test/libsolidity/smtCheckerTests/operators/delete_function.sol +++ b/test/libsolidity/smtCheckerTests/operators/delete_function.sol @@ -16,12 +16,9 @@ contract C g(); else h(); - // Assertion fails as false positive because - // setZeroValue for arrays needs \forall i . a[i] = 0 - // which is still unimplemented. assert(a[2] == 0); + assert(a[1] == 0); } } // ---- // Warning: (201-202): Condition is always true. -// Warning: (367-384): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol index 725e51ba2..8555b0613 100644 --- a/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_2_fail.sol @@ -4,10 +4,11 @@ contract C { uint[][] array; function f(uint x, uint y, uint z, uint t) public view { - require(array[x][y] == 200); + // TODO change to = 200 when 2d assignments are supported. + require(array[x][y] < 200); require(x == z && y == t); assert(array[z][t] > 300); } } // ---- -// Warning: (183-208): Assertion violation happens here +// Warning: (243-268): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol b/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol index 3a34ce5dd..d7eb8bccb 100644 --- a/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol +++ b/test/libsolidity/smtCheckerTests/types/array_dynamic_3_fail.sol @@ -4,10 +4,11 @@ contract C { uint[][][] array; function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { - require(array[x][y][z] == 200); + // TODO change to = 200 when 3d assignments are supported. + require(array[x][y][z] < 200); require(x == t && y == w && z == v); assert(array[t][w][v] > 300); } } // ---- -// Warning: (214-242): Assertion violation happens here +// Warning: (274-302): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol b/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol index 789308c57..b230886de 100644 --- a/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol +++ b/test/libsolidity/smtCheckerTests/types/array_static_2_fail.sol @@ -4,10 +4,10 @@ contract C { uint[10][20] array; function f(uint x, uint y, uint z, uint t) public view { - require(array[x][y] == 200); + require(array[x][y] < 200); require(x == z && y == t); assert(array[z][t] > 300); } } // ---- -// Warning: (187-212): Assertion violation happens here +// Warning: (186-211): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol b/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol index 240a11067..22a2f0506 100644 --- a/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol +++ b/test/libsolidity/smtCheckerTests/types/array_static_3_fail.sol @@ -4,10 +4,11 @@ contract C { uint[10][20][30] array; function f(uint x, uint y, uint z, uint t, uint w, uint v) public view { - require(array[x][y][z] == 200); + // TODO change to = 200 when 3d assignments are supported. + require(array[x][y][z] < 200); require(x == t && y == w && z == v); assert(array[t][w][v] > 300); } } // ---- -// Warning: (220-248): Assertion violation happens here +// Warning: (280-308): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/mapping_4.sol b/test/libsolidity/smtCheckerTests/types/mapping_4.sol index d02042113..7356a3b6b 100644 --- a/test/libsolidity/smtCheckerTests/types/mapping_4.sol +++ b/test/libsolidity/smtCheckerTests/types/mapping_4.sol @@ -9,4 +9,3 @@ contract C } } // ---- -// Warning: (125-144): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol b/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol new file mode 100644 index 000000000..f63e6aef3 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/type_expression_array_2d.sol @@ -0,0 +1,11 @@ +pragma experimental SMTChecker; +contract C { + +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 new file mode 100644 index 000000000..2588ee98d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/type_expression_array_3d.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; +contract C { + +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 new file mode 100644 index 000000000..02c2ac056 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_2d.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; +contract C { + +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 new file mode 100644 index 000000000..893f30581 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/types/type_expression_tuple_array_3d.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; +contract C { + +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/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index b848f6338..b125712fd 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -3,9 +3,8 @@ { "smtlib2responses": { - "0x047d0c67d7e03c5ac96ca227d1e19ba63257f4ab19cef30029413219ec8963af": "sat\n((|EVALEXPR_0| 0))\n", - "0x21d5b49d1416d788fe34b1d2a10a99ea92b007e17a977604afd7b2ff01a055cd": "unsat\n", - "0xada7569fb01a9b3e2823517ed40dcc99b11fb1e433e6e3ec8a8713f6f95753d3": "sat\n((|EVALEXPR_0| 1))\n" + "0x82fb8ee094f0f56b7a63a74177b54a1710d6fc531d426f288c18f36b76cf6a8b": "sat\n((|EVALEXPR_0| 1))\n", + "0xb524e7c577188e2e36f0e67fead51269fa0f8b8fb41bff2d973dcf584d38cd1e": "sat\n((|EVALEXPR_0| 0))\n" } } } diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.sol b/test/libsolidity/smtCheckerTestsJSON/multi.sol index e0d69b1d0..d81d76cb0 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.sol +++ b/test/libsolidity/smtCheckerTestsJSON/multi.sol @@ -1,3 +1,4 @@ +==== Source: A ==== pragma experimental SMTChecker; contract C diff --git a/test/libsolidity/smtCheckerTestsJSON/simple.json b/test/libsolidity/smtCheckerTestsJSON/simple.json index 2f7ebdffb..89eac1b80 100644 --- a/test/libsolidity/smtCheckerTestsJSON/simple.json +++ b/test/libsolidity/smtCheckerTestsJSON/simple.json @@ -3,7 +3,7 @@ { "smtlib2responses": { - "0x2e32517a1410b1a16decd448bb9bac7789d7cf1c6f98703ed6bacfcad6abebfb": "sat\n((|EVALEXPR_0| 0))\n" + "0x45c37a9829e623d7838d82b547d297cd446d6b5faff36c53a56862fcee50fb41": "sat\n((|EVALEXPR_0| 0))\n" } } } diff --git a/test/libsolidity/smtCheckerTestsJSON/simple.sol b/test/libsolidity/smtCheckerTestsJSON/simple.sol index 6bc7193db..f93c8fd79 100644 --- a/test/libsolidity/smtCheckerTestsJSON/simple.sol +++ b/test/libsolidity/smtCheckerTestsJSON/simple.sol @@ -1,3 +1,4 @@ +==== Source: A ==== pragma experimental SMTChecker; contract C diff --git a/test/libsolidity/syntaxTests/array/calldata_assign.sol b/test/libsolidity/syntaxTests/array/calldata_assign.sol new file mode 100644 index 000000000..5d55dd163 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/calldata_assign.sol @@ -0,0 +1,7 @@ +pragma experimental ABIEncoderV2; +contract Test { + function f(uint256[] calldata s) external { s[0] = 4; } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (98-102): Calldata arrays are read-only. diff --git a/test/libsolidity/syntaxTests/array/calldata_resize.sol b/test/libsolidity/syntaxTests/array/calldata_resize.sol new file mode 100644 index 000000000..f923993c2 --- /dev/null +++ b/test/libsolidity/syntaxTests/array/calldata_resize.sol @@ -0,0 +1,7 @@ +contract C { + function f (uint256[] calldata x) external pure { + x.length = 42; + } +} +// ---- +// TypeError: (75-83): Calldata arrays cannot be resized. diff --git a/test/libsolidity/syntaxTests/constants/mod_div_rational.sol b/test/libsolidity/syntaxTests/constants/mod_div_rational.sol index f8b6ce313..d983fa1ee 100644 --- a/test/libsolidity/syntaxTests/constants/mod_div_rational.sol +++ b/test/libsolidity/syntaxTests/constants/mod_div_rational.sol @@ -4,3 +4,5 @@ contract C { fixed a3 = 0 / 0.123; fixed a4 = 0 / -0.123; } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol index dbd3db08e..5add7bc72 100644 --- a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/named_err.sol @@ -2,4 +2,4 @@ contract C { function f() internal pure returns (mapping(uint=>uint) storage r) { } } // ---- -// TypeError: (53-82): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (53-82): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol index 476e799b2..5ea1cd36f 100644 --- a/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/mappingReturn/unnamed_err.sol @@ -2,4 +2,4 @@ contract C { function f() internal pure returns (mapping(uint=>uint) storage) {} } // ---- -// TypeError: (53-80): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (53-80): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol index a6df889dd..4d821ed2c 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/assembly_err.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// TypeError: (87-96): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (87-96): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol index c7e3eacd5..b59bed5c7 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol @@ -45,12 +45,12 @@ contract C { } } // ---- -// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. // Warning: (146-151): Unreachable code. // Warning: (169-174): Unreachable code. -// TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. // Warning: (316-321): Unreachable code. -// TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. // Warning: (933-938): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol index d2dec9c18..bf693cf89 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/for_err.sol @@ -12,5 +12,5 @@ contract C { } } // ---- -// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (182-193): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (182-193): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol index 12ec31e48..946a3f608 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/if_err.sol @@ -14,5 +14,5 @@ contract C { } } // ---- -// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (186-197): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (186-197): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol index ee1e08fde..e56b7aceb 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/modifier_err.sol @@ -18,5 +18,5 @@ contract C { } } // ---- -// TypeError: (249-258): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (367-376): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (249-258): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (367-376): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol index e3579628b..5fc907184 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/short_circuit_err.sol @@ -13,6 +13,6 @@ contract C { } } // ---- -// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (176-187): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (264-275): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (176-187): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (264-275): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol index d5ad73c5b..95f962369 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/ternary_err.sol @@ -9,5 +9,5 @@ contract C { } } // ---- -// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment. -// TypeError: (200-211): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (96-107): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. +// TypeError: (200-211): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol index aabb76dd7..aba70626b 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/while_err.sol @@ -8,4 +8,4 @@ contract C { } } // ---- -// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol index 9ffba6fa2..e73d9cde6 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/assembly.sol @@ -6,4 +6,4 @@ contract C { } } // ---- -// TypeError: (92-116): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (92-116): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol index 90d228fa3..f0d5bfe82 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_order_fail.sol @@ -5,4 +5,4 @@ contract C { function f() m1(b) m2(b = s) internal view returns (uint[] storage b) {} } // ---- -// TypeError: (129-130): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (129-130): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol index b9f08615f..82413011b 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_post_access.sol @@ -10,4 +10,4 @@ contract C { } } // ---- -// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol index 81618aec0..31353e201 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/modifier_pre_access.sol @@ -10,4 +10,4 @@ contract C { } } // ---- -// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (120-121): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol index 41ced51d6..50db459ba 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/smoke.sol @@ -7,4 +7,4 @@ contract C { } } // ---- -// TypeError: (94-95): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (94-95): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol index b4f2be5d7..38828e681 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/struct.sol @@ -8,4 +8,4 @@ contract C { } } // ---- -// TypeError: (109-110): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (109-110): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol b/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol index 95d4e02a5..4f2094baa 100644 --- a/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol +++ b/test/libsolidity/syntaxTests/dataLocations/libraries/library_function_with_data_location_fine.sol @@ -8,9 +8,9 @@ library L { function i(uint[] calldata, uint[] storage) external pure returns (S storage x) {return x; } } // ---- -// TypeError: (197-198): This variable is of storage pointer type and can be accessed without prior assignment. -// TypeError: (203-204): This variable is of storage pointer type and can be accessed without prior assignment. -// TypeError: (359-360): This variable is of storage pointer type and can be accessed without prior assignment. -// TypeError: (365-366): This variable is of storage pointer type and can be accessed without prior assignment. -// TypeError: (460-461): This variable is of storage pointer type and can be accessed without prior assignment. -// TypeError: (557-558): This variable is of storage pointer type and can be accessed without prior assignment. +// TypeError: (197-198): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError: (203-204): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError: (359-360): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError: (365-366): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError: (460-461): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. +// TypeError: (557-558): This variable is of storage pointer type and can be accessed without prior assignment, which would lead to undefined behaviour. diff --git a/test/libsolidity/syntaxTests/imports/circular_import.sol b/test/libsolidity/syntaxTests/imports/circular_import.sol new file mode 100644 index 000000000..45492d8cb --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/circular_import.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +import "b"; contract C { D d; } +==== Source: b ==== +import "a"; contract D { C c; } diff --git a/test/libsolidity/syntaxTests/imports/complex_import.sol b/test/libsolidity/syntaxTests/imports/complex_import.sol new file mode 100644 index 000000000..96fc4d308 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/complex_import.sol @@ -0,0 +1,5 @@ +==== Source: a ==== +contract A {} contract B {} contract C { struct S { uint a; } } +==== Source: b ==== +import "a" as x; import {B as b, C as c, C} from "a"; +contract D is b { function f(c.S memory var1, x.C.S memory var2, C.S memory var3) internal {} } \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/imports/filename_with_period.sol b/test/libsolidity/syntaxTests/imports/filename_with_period.sol new file mode 100644 index 000000000..73c47087f --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/filename_with_period.sol @@ -0,0 +1,6 @@ +==== Source: a/.b.sol ==== +contract B {} +==== Source: a/a.sol ==== +import ".b.sol"; contract A is B {} +// ---- +// ParserError: (a/a.sol:0-16): Source ".b.sol" not found: File not supplied initially. diff --git a/test/libsolidity/syntaxTests/imports/import_does_not_clutter_importee.sol b/test/libsolidity/syntaxTests/imports/import_does_not_clutter_importee.sol new file mode 100644 index 000000000..99f3f53e3 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/import_does_not_clutter_importee.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +contract C { D d; } +==== Source: b ==== +import "a"; contract D is C {} +// ---- +// DeclarationError: (a:13-14): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/imports/import_is_transitive.sol b/test/libsolidity/syntaxTests/imports/import_is_transitive.sol new file mode 100644 index 000000000..2b1f76002 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/import_is_transitive.sol @@ -0,0 +1,7 @@ +==== Source: a ==== +contract C { } +==== Source: b ==== +import "a"; +==== Source: c ==== +import "b"; +contract D is C {} \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_match.sol b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_match.sol new file mode 100644 index 000000000..cead709b4 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_match.sol @@ -0,0 +1,23 @@ +==== Source: A.sol ==== +pragma experimental ABIEncoderV2; + +contract A +{ + struct S { uint a; } + S public s; + function f(S memory _s) public returns (S memory,S memory) { } +} +==== Source: B.sol ==== +pragma experimental ABIEncoderV2; + +import "./A.sol"; +contract B is A { } +==== Source: C.sol ==== +pragma experimental ABIEncoderV2; + +import "./B.sol"; +contract C is B { } +// ---- +// Warning: (A.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (B.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (C.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. diff --git a/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_1.sol b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_1.sol new file mode 100644 index 000000000..9000ca0fc --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_1.sol @@ -0,0 +1,21 @@ +==== Source: A.sol ==== +pragma experimental ABIEncoderV2; + +contract A +{ + struct S { uint a; } + S public s; + function f(S memory _s) public returns (S memory,S memory) { } +} +==== Source: B.sol ==== +pragma experimental ABIEncoderV2; + +import "./A.sol"; +contract B is A { } +==== Source: C.sol ==== +import "./B.sol"; +contract C is B { } +// ---- +// Warning: (A.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// Warning: (B.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (C.sol:18-37): Contract "C" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use "pragma experimental ABIEncoderV2;" for the inheriting contract as well to enable the feature. diff --git a/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_2.sol b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_2.sol new file mode 100644 index 000000000..b900d1ba4 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/inheritance_abi_encoder_mismatch_2.sol @@ -0,0 +1,18 @@ +==== Source: A.sol ==== +pragma experimental ABIEncoderV2; + +contract A +{ + struct S { uint a; } + S public s; + function f(S memory _s) public returns (S memory,S memory) { } +} +==== Source: B.sol ==== +import "./A.sol"; +contract B is A { } +==== Source: C.sol ==== +import "./B.sol"; +contract C is B { } +// ---- +// Warning: (A.sol:0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (B.sol:18-37): Contract "B" does not use the new experimental ABI encoder but wants to inherit from a contract which uses types that require it. Use "pragma experimental ABIEncoderV2;" for the inheriting contract as well to enable the feature. diff --git a/test/libsolidity/syntaxTests/imports/library_name_clash.sol b/test/libsolidity/syntaxTests/imports/library_name_clash.sol new file mode 100644 index 000000000..19f3697d5 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/library_name_clash.sol @@ -0,0 +1,8 @@ +==== Source: a ==== +library A {} +==== Source: b ==== +library A {} +==== Source: c ==== +import {A} from "./a"; import {A} from "./b"; +// ---- +// DeclarationError: (c:31-32): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/library_name_clash_with_contract.sol b/test/libsolidity/syntaxTests/imports/library_name_clash_with_contract.sol new file mode 100644 index 000000000..8358b6485 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/library_name_clash_with_contract.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +library A {} diff --git a/test/libsolidity/syntaxTests/imports/name_clash_in_import_1.sol b/test/libsolidity/syntaxTests/imports/name_clash_in_import_1.sol new file mode 100644 index 000000000..a5b1acc60 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/name_clash_in_import_1.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +import "a"; contract A {} +// ---- +// DeclarationError: (b:12-25): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/name_clash_in_import_2.sol b/test/libsolidity/syntaxTests/imports/name_clash_in_import_2.sol new file mode 100644 index 000000000..96925f368 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/name_clash_in_import_2.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +import "a" as A; contract A {} +// ---- +// DeclarationError: (b:17-30): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/name_clash_in_import_3.sol b/test/libsolidity/syntaxTests/imports/name_clash_in_import_3.sol new file mode 100644 index 000000000..2ff01b5f8 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/name_clash_in_import_3.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +import {A as b} from "a"; contract b {} +// ---- +// DeclarationError: (b:26-39): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/name_clash_in_import_4.sol b/test/libsolidity/syntaxTests/imports/name_clash_in_import_4.sol new file mode 100644 index 000000000..abe59271e --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/name_clash_in_import_4.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +import {A} from "a"; contract A {} +// ---- +// DeclarationError: (b:21-34): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/name_clash_in_import_5.sol b/test/libsolidity/syntaxTests/imports/name_clash_in_import_5.sol new file mode 100644 index 000000000..596d47b76 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/name_clash_in_import_5.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +contract A {} +==== Source: b ==== +import {A} from "a"; contract B {} diff --git a/test/libsolidity/syntaxTests/imports/regular_import.sol b/test/libsolidity/syntaxTests/imports/regular_import.sol new file mode 100644 index 000000000..5811798e9 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/regular_import.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +contract C {} +==== Source: b ==== +import "a"; contract D is C {} \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/imports/relative_import.sol b/test/libsolidity/syntaxTests/imports/relative_import.sol new file mode 100644 index 000000000..db75c0569 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/relative_import.sol @@ -0,0 +1,6 @@ +==== Source: a ==== +import "./dir/b"; contract A is B {} +==== Source: dir/b ==== +contract B {} +==== Source: dir/c ==== +import "../a"; contract C is A {} \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/imports/relative_import_multiplex.sol b/test/libsolidity/syntaxTests/imports/relative_import_multiplex.sol new file mode 100644 index 000000000..c166def7a --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/relative_import_multiplex.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +contract A {} +==== Source: dir/a/b/c ==== +import "../../.././a"; contract B is A {} diff --git a/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_alias.sol b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_alias.sol new file mode 100644 index 000000000..65a606e0c --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_alias.sol @@ -0,0 +1,6 @@ +==== Source: B.sol ==== +contract C {} +==== Source: b ==== +import {C as msg} from "B.sol"; +// ---- +// Warning: (b:13-16): This declaration shadows a builtin symbol. diff --git a/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_imports.sol b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_imports.sol new file mode 100644 index 000000000..2121b1359 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_imports.sol @@ -0,0 +1,8 @@ +==== Source: B.sol ==== +contract X {} +==== Source: b ==== +import * as msg from "B.sol"; +contract C { +} +// ---- +// Warning: (b:0-29): This declaration shadows a builtin symbol. diff --git a/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_multiple_imports.sol b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_multiple_imports.sol new file mode 100644 index 000000000..3d82041af --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/shadowing_builtins_with_multiple_imports.sol @@ -0,0 +1,11 @@ +==== Source: B.sol ==== +contract msg {} contract block{} +==== Source: b ==== +import {msg, block} from "B.sol"; +contract C { +} +// ---- +// Warning: (B.sol:0-15): This declaration shadows a builtin symbol. +// Warning: (B.sol:16-32): This declaration shadows a builtin symbol. +// Warning: (b:8-11): This declaration shadows a builtin symbol. +// Warning: (b:13-18): This declaration shadows a builtin symbol. diff --git a/test/libsolidity/syntaxTests/imports/shadowing_via_import.sol b/test/libsolidity/syntaxTests/imports/shadowing_via_import.sol new file mode 100644 index 000000000..19f3697d5 --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/shadowing_via_import.sol @@ -0,0 +1,8 @@ +==== Source: a ==== +library A {} +==== Source: b ==== +library A {} +==== Source: c ==== +import {A} from "./a"; import {A} from "./b"; +// ---- +// DeclarationError: (c:31-32): Identifier already declared. diff --git a/test/libsolidity/syntaxTests/imports/simple_alias.sol b/test/libsolidity/syntaxTests/imports/simple_alias.sol new file mode 100644 index 000000000..776c5c42f --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/simple_alias.sol @@ -0,0 +1,4 @@ +==== Source: a ==== +contract A {} +==== Source: dir/a/b/c ==== +import "../../.././a" as x; contract B is x.A { function() external { x.A r = x.A(20); r; } } diff --git a/test/libsolidity/syntaxTests/imports/smoke_test.sol b/test/libsolidity/syntaxTests/imports/smoke_test.sol new file mode 100644 index 000000000..78268d5ab --- /dev/null +++ b/test/libsolidity/syntaxTests/imports/smoke_test.sol @@ -0,0 +1,2 @@ +==== Source: a ==== +contract C {} diff --git a/test/libsolidity/syntaxTests/inlineAssembly/istanbul.sol b/test/libsolidity/syntaxTests/inlineAssembly/istanbul.sol new file mode 100644 index 000000000..3e1a1cd2a --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/istanbul.sol @@ -0,0 +1,15 @@ +contract C { + function f() pure external { + assembly { + pop(chainid()) + } + } + function g() view external { + assembly { + pop(selfbalance()) + } + } +} +// ==== +// EVMVersion: >=istanbul +// ---- diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_fixed_types.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_fixed_types.sol index c46297c3f..7aef51a91 100644 --- a/test/libsolidity/syntaxTests/inline_arrays/inline_array_fixed_types.sol +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_fixed_types.sol @@ -4,5 +4,6 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (50-67): Unused local variable. // Warning: (20-119): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/inline_arrays/inline_array_rationals.sol b/test/libsolidity/syntaxTests/inline_arrays/inline_array_rationals.sol index bdc3c2c14..a46db5a60 100644 --- a/test/libsolidity/syntaxTests/inline_arrays/inline_array_rationals.sol +++ b/test/libsolidity/syntaxTests/inline_arrays/inline_array_rationals.sol @@ -4,5 +4,6 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (50-73): Unused local variable. // Warning: (20-118): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/lvalues/calldata_index_access.sol b/test/libsolidity/syntaxTests/lvalues/calldata_index_access.sol new file mode 100644 index 000000000..e58d86041 --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/calldata_index_access.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[] calldata x) external pure { + x[0] = 42; + } +} +// ---- +// TypeError: (74-78): Calldata arrays are read-only. diff --git a/test/libsolidity/syntaxTests/lvalues/calldata_member_access.sol b/test/libsolidity/syntaxTests/lvalues/calldata_member_access.sol new file mode 100644 index 000000000..35655a4ac --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/calldata_member_access.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; +contract C { + struct S { uint256 x; } + function f(S calldata s) external pure { + s.x = 42; + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (128-131): Calldata structs are read-only. diff --git a/test/libsolidity/syntaxTests/lvalues/external_reference_argument.sol b/test/libsolidity/syntaxTests/lvalues/external_reference_argument.sol new file mode 100644 index 000000000..16a37911a --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/external_reference_argument.sol @@ -0,0 +1,7 @@ +contract C { + function f(uint256[] calldata x, uint256[] calldata y) external pure { + x = y; + } +} +// ---- +// TypeError: (96-97): External function arguments of reference type are read-only. diff --git a/test/libsolidity/syntaxTests/lvalues/functions.sol b/test/libsolidity/syntaxTests/lvalues/functions.sol new file mode 100644 index 000000000..6aa73e0f2 --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/functions.sol @@ -0,0 +1,15 @@ +contract C { + function f() internal { + } + function g() internal { + g = f; + } + function h() external { + } + function i() external { + this.i = this.h; + } +} +// ---- +// TypeError: (83-84): Expression has to be an lvalue. +// TypeError: (166-172): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/lvalues/library_mapping.sol b/test/libsolidity/syntaxTests/lvalues/library_mapping.sol new file mode 100644 index 000000000..9aa4099c9 --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/library_mapping.sol @@ -0,0 +1,7 @@ +library L { + function f(mapping(uint=>uint) storage x, mapping(uint=>uint) storage y) external { + x = y; + } +} +// ---- +// TypeError: (108-109): Mappings cannot be assigned to. diff --git a/test/libsolidity/syntaxTests/lvalues/valid_lvalues.sol b/test/libsolidity/syntaxTests/lvalues/valid_lvalues.sol new file mode 100644 index 000000000..aaf94be33 --- /dev/null +++ b/test/libsolidity/syntaxTests/lvalues/valid_lvalues.sol @@ -0,0 +1,31 @@ +contract C { + struct S { uint256 x; } + function i() internal pure {} + function e() external pure {} + uint[] s1; + function f(uint x, bytes32 y) external { + x = 42; + y = bytes32(0); + (x, y) = (23, bytes32(0)); + S memory ms1; + S memory ms2; + ms1 = ms2; + ms1.x = x; + uint256[] memory a = new uint256[](2); + uint256[] memory b = new uint256[](3); + a = b; + a[0] = x; + s1[0] = x; + s1 = a; + } + function g(function() internal pure x) internal view { + x = i; + function(uint, bytes32) external y; + y = this.f; + } + function g(function() external pure x) external view { + x = this.e; + function(function() internal pure) internal view y; + y = g; + } +} diff --git a/test/libsolidity/syntaxTests/multiSource/error_in_first.sol b/test/libsolidity/syntaxTests/multiSource/error_in_first.sol new file mode 100644 index 000000000..abf0bce9b --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/error_in_first.sol @@ -0,0 +1,10 @@ +==== Source: A ==== +contract A { + function g() public { x; } +} +==== Source: B ==== +contract B { + function f() public { } +} +// ---- +// DeclarationError: (A:36-37): Undeclared identifier. diff --git a/test/libsolidity/syntaxTests/multiSource/import.sol b/test/libsolidity/syntaxTests/multiSource/import.sol new file mode 100644 index 000000000..4d9f6c686 --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/import.sol @@ -0,0 +1,12 @@ +==== Source: A ==== +contract A { + function g(uint256 x) public view returns(uint256) { return x; } +} +==== Source: B ==== +import "A"; +contract B is A { + function f(uint256 x) public view returns(uint256) { return x; } +} +// ---- +// Warning: (A:14-78): Function state mutability can be restricted to pure +// Warning: (B:31-95): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/multiSource/import_not_found.sol b/test/libsolidity/syntaxTests/multiSource/import_not_found.sol new file mode 100644 index 000000000..dd0d2db28 --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/import_not_found.sol @@ -0,0 +1,5 @@ +==== Source: a ==== +import "b"; +contract C {} +// ---- +// ParserError: (a:0-11): Source "b" not found: File not supplied initially. diff --git a/test/libsolidity/syntaxTests/multiSource/no_import.sol b/test/libsolidity/syntaxTests/multiSource/no_import.sol new file mode 100644 index 000000000..37b14b871 --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/no_import.sol @@ -0,0 +1,10 @@ +==== Source: A ==== +contract A { + function g(uint256 x) public view returns(uint256) { return x; } +} +==== Source: B ==== +contract B is A { + function f(uint256 x) public view returns(uint256) { return x; } +} +// ---- +// DeclarationError: (B:14-15): Identifier not found or not unique. diff --git a/test/libsolidity/syntaxTests/multiSource/one_source.sol b/test/libsolidity/syntaxTests/multiSource/one_source.sol new file mode 100644 index 000000000..0f0d129b5 --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/one_source.sol @@ -0,0 +1,7 @@ +==== Source: SourceName ==== +contract A { + uint256 x; + function f() public pure { x = 42; } +} +// ---- +// TypeError: (SourceName:53-54): Function declared as pure, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. diff --git a/test/libsolidity/syntaxTests/multiSource/warning_in_both.sol b/test/libsolidity/syntaxTests/multiSource/warning_in_both.sol new file mode 100644 index 000000000..1522e42aa --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/warning_in_both.sol @@ -0,0 +1,11 @@ +==== Source: A ==== +contract A { + function g(uint256 x) public view returns(uint256) { return x; } +} +==== Source: B ==== +contract B { + function f(uint256 x) public view returns(uint256) { return x; } +} +// ---- +// Warning: (A:14-78): Function state mutability can be restricted to pure +// Warning: (B:14-78): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/146_external_argument_assign.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/146_external_argument_assign.sol index d2c0245cf..8d01ab63a 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/146_external_argument_assign.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/146_external_argument_assign.sol @@ -1,5 +1,4 @@ contract c { - function f(uint a) external { a = 1; } + function f(uint a) external pure { a = 1; } } // ---- -// TypeError: (47-48): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/147_external_argument_increment.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/147_external_argument_increment.sol index 2bfba42bb..c80dbcd2a 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/147_external_argument_increment.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/147_external_argument_increment.sol @@ -1,5 +1,4 @@ contract c { - function f(uint a) external { a++; } + function f(uint a) external pure { a++; } } // ---- -// TypeError: (47-48): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/148_external_argument_delete.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/148_external_argument_delete.sol index 30eb204e1..3955023ea 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/148_external_argument_delete.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/148_external_argument_delete.sol @@ -2,4 +2,4 @@ contract c { function f(uint a) external { delete a; } } // ---- -// TypeError: (54-55): Expression has to be an lvalue. +// Warning: (17-58): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/219_memory_arrays_not_resizeable.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/219_memory_arrays_not_resizeable.sol index 93d8cd429..aba0fff48 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/219_memory_arrays_not_resizeable.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/219_memory_arrays_not_resizeable.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// TypeError: (72-80): Expression has to be an lvalue. +// TypeError: (72-80): Memory arrays cannot be resized. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/303_fixed_type_int_conversion.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/303_fixed_type_int_conversion.sol index ddf1e5fb0..6ef5da3bb 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/303_fixed_type_int_conversion.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/303_fixed_type_int_conversion.sol @@ -8,4 +8,5 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (20-147): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/304_fixed_type_rational_int_conversion.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/304_fixed_type_rational_int_conversion.sol index 16d2cbad2..e046df6ba 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/304_fixed_type_rational_int_conversion.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/304_fixed_type_rational_int_conversion.sol @@ -6,4 +6,5 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (20-104): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/305_fixed_type_rational_fraction_conversion.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/305_fixed_type_rational_fraction_conversion.sol index 270298601..f971dc299 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/305_fixed_type_rational_fraction_conversion.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/305_fixed_type_rational_fraction_conversion.sol @@ -6,4 +6,5 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (20-108): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/307_rational_unary_minus_operation.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/307_rational_unary_minus_operation.sol index 7827e57bf..b8b2f12b5 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/307_rational_unary_minus_operation.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/307_rational_unary_minus_operation.sol @@ -5,3 +5,5 @@ contract test { a; b; } } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/312_leading_zero_rationals_convert.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/312_leading_zero_rationals_convert.sol index 9fe7c6f7e..082514ebc 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/312_leading_zero_rationals_convert.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/312_leading_zero_rationals_convert.sol @@ -7,3 +7,5 @@ contract A { a; b; c; d; } } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/314_fixed_type_zero_handling.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/314_fixed_type_zero_handling.sol index 5f0d2909f..be6395792 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/314_fixed_type_zero_handling.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/314_fixed_type_zero_handling.sol @@ -5,4 +5,5 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (20-104): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/317_fixed_type_valid_explicit_conversions.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/317_fixed_type_valid_explicit_conversions.sol index 388014579..46750a533 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/317_fixed_type_valid_explicit_conversions.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/317_fixed_type_valid_explicit_conversions.sol @@ -6,4 +6,5 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (20-182): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/323_mapping_with_fixed_literal.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/323_mapping_with_fixed_literal.sol index 8e28d4e1f..20ebcb1fe 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/323_mapping_with_fixed_literal.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/323_mapping_with_fixed_literal.sol @@ -4,3 +4,5 @@ contract test { fixedString[0.5] = "Half"; } } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/324_fixed_points_inside_structs.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/324_fixed_points_inside_structs.sol index 8aa0abc2b..24a090127 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/324_fixed_points_inside_structs.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/324_fixed_points_inside_structs.sol @@ -5,3 +5,5 @@ contract test { } myStruct a = myStruct(3.125, 3); } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/328_rational_to_fixed_literal_expression.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/328_rational_to_fixed_literal_expression.sol index 35456fa6f..8582cc089 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/328_rational_to_fixed_literal_expression.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/328_rational_to_fixed_literal_expression.sol @@ -11,5 +11,6 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (238-252): This declaration shadows an existing declaration. // Warning: (20-339): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/343_integer_and_fixed_interaction.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/343_integer_and_fixed_interaction.sol index af4d048bb..3b7c273bd 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/343_integer_and_fixed_interaction.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/343_integer_and_fixed_interaction.sol @@ -4,5 +4,6 @@ contract test { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (50-58): Unused local variable. // Warning: (20-89): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/declaring_fixed_and_ufixed_variables.sol b/test/libsolidity/syntaxTests/parsing/declaring_fixed_and_ufixed_variables.sol index 6d88669af..2cbf29e0d 100644 --- a/test/libsolidity/syntaxTests/parsing/declaring_fixed_and_ufixed_variables.sol +++ b/test/libsolidity/syntaxTests/parsing/declaring_fixed_and_ufixed_variables.sol @@ -6,6 +6,7 @@ contract A { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. // Warning: (52-60): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (62-74): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (93-104): Unused local variable. diff --git a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol index 4910ee829..00ed4f654 100644 --- a/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol +++ b/test/libsolidity/syntaxTests/parsing/lexer_numbers_with_underscores_fixed.sol @@ -7,3 +7,4 @@ contract C { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/signed_rational_modulus.sol b/test/libsolidity/syntaxTests/signed_rational_modulus.sol index b37d33d0c..d01d2fabf 100644 --- a/test/libsolidity/syntaxTests/signed_rational_modulus.sol +++ b/test/libsolidity/syntaxTests/signed_rational_modulus.sol @@ -6,3 +6,5 @@ contract test { a; b; c; } } +// ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol b/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol index 70e537ad6..eb8de4963 100644 --- a/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol +++ b/test/libsolidity/syntaxTests/structs/calldata_array_assign.sol @@ -7,4 +7,4 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (144-147): Expression has to be an lvalue. +// TypeError: (144-147): Calldata structs are read-only. diff --git a/test/libsolidity/syntaxTests/structs/calldata_assign.sol b/test/libsolidity/syntaxTests/structs/calldata_assign.sol index f44730b4f..728fe8705 100644 --- a/test/libsolidity/syntaxTests/structs/calldata_assign.sol +++ b/test/libsolidity/syntaxTests/structs/calldata_assign.sol @@ -5,4 +5,4 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (114-117): Expression has to be an lvalue. +// TypeError: (114-117): Calldata structs are read-only. diff --git a/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol b/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol index 09b3ef294..2694b9ee6 100644 --- a/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol +++ b/test/libsolidity/syntaxTests/structs/memory_to_calldata.sol @@ -6,7 +6,7 @@ contract Test { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. -// TypeError: (114-115): Expression has to be an lvalue. +// TypeError: (114-115): External function arguments of reference type are read-only. // TypeError: (118-122): Type struct Test.S memory is not implicitly convertible to expected type struct Test.S calldata. -// TypeError: (178-179): Expression has to be an lvalue. +// TypeError: (178-179): External function arguments of reference type are read-only. // TypeError: (182-183): Type struct Test.S memory is not implicitly convertible to expected type struct Test.S calldata. diff --git a/test/libsolidity/syntaxTests/structs/recursion/static_array_of_recursive_structs.sol b/test/libsolidity/syntaxTests/structs/recursion/static_array_of_recursive_structs.sol new file mode 100644 index 000000000..4268317b3 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/recursion/static_array_of_recursive_structs.sol @@ -0,0 +1,10 @@ +contract Test { + struct RecursiveStruct { + RecursiveStruct[] vals; + } + + function func() private pure { + RecursiveStruct[1] memory val; + val; + } +} diff --git a/test/libsolidity/syntaxTests/types/bytesXX_index_assign.sol b/test/libsolidity/syntaxTests/types/bytesXX_index_assign.sol new file mode 100644 index 000000000..8c55a8038 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/bytesXX_index_assign.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + bytes32 x; + x[0] = 0x42; + } +} +// ---- +// TypeError: (71-75): Single bytes in fixed bytes arrays cannot be modified. diff --git a/test/libsolidity/syntaxTests/types/rational_number_literal_to_fixed_implicit.sol b/test/libsolidity/syntaxTests/types/rational_number_literal_to_fixed_implicit.sol index ca50e03be..5bceec609 100644 --- a/test/libsolidity/syntaxTests/types/rational_number_literal_to_fixed_implicit.sol +++ b/test/libsolidity/syntaxTests/types/rational_number_literal_to_fixed_implicit.sol @@ -13,3 +13,4 @@ contract C { } } // ---- +// UnimplementedFeatureError: Not yet implemented - FixedPointType. diff --git a/test/libsolidity/util/BytesUtils.cpp b/test/libsolidity/util/BytesUtils.cpp index 82d70dcb9..af104e86e 100644 --- a/test/libsolidity/util/BytesUtils.cpp +++ b/test/libsolidity/util/BytesUtils.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -202,22 +203,29 @@ string BytesUtils::formatString(bytes const& _bytes, size_t _cutOff) return os.str(); } -string BytesUtils::formatRawBytes(bytes const& _bytes) +string BytesUtils::formatRawBytes( + bytes const& _bytes, + dev::solidity::test::ParameterList const& _parameters, + string _linePrefix) { - if (_bytes.empty()) - return "[]"; - stringstream os; + ParameterList parameters; auto it = _bytes.begin(); - for (size_t i = 0; i < _bytes.size(); i += 32) + + if (_bytes.size() != ContractABIUtils::encodingSize(_parameters)) + parameters = ContractABIUtils::defaultParameters(ceil(_bytes.size() / 32)); + else + parameters = _parameters; + + for (auto const& parameter: parameters) { - bytes byteRange{it, it + 32}; + bytes byteRange{it, it + static_cast(parameter.abiType.size)}; - os << " " << byteRange; - - it += 32; - if (it != _bytes.end()) + os << _linePrefix << byteRange; + if (¶meter != ¶meters.back()) os << endl; + + it += static_cast(parameter.abiType.size); } return os.str(); @@ -272,9 +280,16 @@ string BytesUtils::formatBytesRange( ) { stringstream os; + ParameterList parameters; auto it = _bytes.begin(); - for (auto const& parameter: _parameters) + if (_bytes.size() != ContractABIUtils::encodingSize(_parameters)) + parameters = ContractABIUtils::defaultParameters(ceil(_bytes.size() / 32)); + else + parameters = _parameters; + + + for (auto const& parameter: parameters) { bytes byteRange{it, it + static_cast(parameter.abiType.size)}; @@ -287,11 +302,12 @@ string BytesUtils::formatBytesRange( else os << parameter.rawString; + if (¶meter != ¶meters.back()) + os << ", "; it += static_cast(parameter.abiType.size); - if (¶meter != &_parameters.back()) - os << ", "; } + return os.str(); } diff --git a/test/libsolidity/util/BytesUtils.h b/test/libsolidity/util/BytesUtils.h index 93ba06cd6..f83c9c3c2 100644 --- a/test/libsolidity/util/BytesUtils.h +++ b/test/libsolidity/util/BytesUtils.h @@ -101,10 +101,14 @@ public: return formatString(_bytes, _bytes.size()); } - /// Returns a string representation of given _bytes. Adds a newline - /// every 32 bytes to increase readability. /// Used to print returned bytes from function calls to the commandline. - static std::string formatRawBytes(bytes const& _bytes); + /// Returns a string representation of given _bytes in ranges of 32 bytes. + /// If _withSignature is true, the first 4 bytes will be formatted separately. + static std::string formatRawBytes( + bytes const& _bytes, + ParameterList const& _parameters, + std::string _linePrefix = "" + ); /// Formats given _bytes with type information passed in _abiType. static std::string formatBytes(bytes const& _bytes, ABIType const& _abiType); diff --git a/test/libsolidity/util/ContractABIUtils.cpp b/test/libsolidity/util/ContractABIUtils.cpp index eb40f31dd..3d1a7c79a 100644 --- a/test/libsolidity/util/ContractABIUtils.cpp +++ b/test/libsolidity/util/ContractABIUtils.cpp @@ -289,16 +289,51 @@ dev::solidity::test::ParameterList ContractABIUtils::preferredParameters( { if (_targetParameters.size() != _sourceParameters.size()) { - auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; }; - size_t encodingSize = accumulate(_targetParameters.begin(), _targetParameters.end(), size_t{0}, sizeFold); - _errorReporter.warning( "Encoding does not match byte range. The call returned " + to_string(_bytes.size()) + " bytes, but " + - to_string(encodingSize) + " bytes were expected." + to_string(encodingSize(_targetParameters)) + " bytes were expected." ); return _sourceParameters; } else return _targetParameters; } + +dev::solidity::test::ParameterList ContractABIUtils::defaultParameters(size_t count) +{ + ParameterList parameters; + + fill_n( + back_inserter(parameters), + count, + Parameter{bytes(), "", ABIType{ABIType::UnsignedDec}, FormatInfo{}} + ); + + return parameters; +} + +dev::solidity::test::ParameterList ContractABIUtils::failureParameters(bytes const _bytes) +{ + ParameterList parameters; + + parameters.push_back(Parameter{bytes(), "", ABIType{ABIType::HexString, ABIType::AlignNone, 4}, FormatInfo{}}); + parameters.push_back(Parameter{bytes(), "", ABIType{ABIType::Hex}, FormatInfo{}}); + parameters.push_back(Parameter{bytes(), "", ABIType{ABIType::UnsignedDec}, FormatInfo{}}); + + /// If _bytes contains at least a 1 byte message (function selector + tail pointer + message length + message) + /// append an additional string parameter to represent that message. + if (_bytes.size() > 68) + parameters.push_back(Parameter{bytes(), "", ABIType{ABIType::String}, FormatInfo{}}); + + return parameters; +} + +size_t ContractABIUtils::encodingSize( + dev::solidity::test::ParameterList const& _parameters +) +{ + auto sizeFold = [](size_t const _a, Parameter const& _b) { return _a + _b.abiType.size; }; + + return accumulate(_parameters.begin(), _parameters.end(), size_t{0}, sizeFold); +} diff --git a/test/libsolidity/util/ContractABIUtils.h b/test/libsolidity/util/ContractABIUtils.h index 13b58699b..3b1b0dc56 100644 --- a/test/libsolidity/util/ContractABIUtils.h +++ b/test/libsolidity/util/ContractABIUtils.h @@ -65,6 +65,20 @@ public: bytes const& _bytes ); + /// Returns a list of parameters corresponding to the encoding of + /// returned values in case of a failure. Creates an additional parameter + /// for the error message if _bytes is larger than 68 bytes + /// (function_selector + tail_ptr + message_length). + static ParameterList failureParameters(bytes const _bytes); + + /// Returns _count parameters with their type set to ABIType::UnsignedDec + /// and their size set to 32 bytes. + static ParameterList defaultParameters(size_t count = 0); + + /// Calculates the encoding size of given _parameters based + /// on the size of their types. + static size_t encodingSize(ParameterList const& _paremeters); + private: /// Parses and translates a single type and returns a list of /// internal type representations of isoltest. diff --git a/test/libsolidity/util/SoltestTypes.h b/test/libsolidity/util/SoltestTypes.h index 0e7a3072c..275b1e657 100644 --- a/test/libsolidity/util/SoltestTypes.h +++ b/test/libsolidity/util/SoltestTypes.h @@ -262,6 +262,9 @@ struct FunctionCall DisplayMode displayMode = DisplayMode::SingleLine; /// Marks this function call as the constructor. bool isConstructor = false; + /// If this function call's signature has no name and no arguments, + /// a low-level call with unstructured calldata will be issued. + bool useCallWithoutSignature = false; /// Marks this function call as "short-handed", meaning /// no `->` declared. bool omitsArrow = true; diff --git a/test/libsolidity/util/TestFileParser.cpp b/test/libsolidity/util/TestFileParser.cpp index ebb865b4f..f33853dfe 100644 --- a/test/libsolidity/util/TestFileParser.cpp +++ b/test/libsolidity/util/TestFileParser.cpp @@ -75,7 +75,7 @@ vector TestFileParser::parseFunctionCalls(siz try { - call.signature = parseFunctionSignature(); + tie(call.signature, call.useCallWithoutSignature) = parseFunctionSignature(); if (accept(Token::Comma, true)) call.value = parseFunctionCallValue(); if (accept(Token::Colon, true)) @@ -148,10 +148,17 @@ bool TestFileParser::expect(soltest::Token _token, bool const _advance) return true; } -string TestFileParser::parseFunctionSignature() +pair TestFileParser::parseFunctionSignature() { - string signature = m_scanner.currentLiteral(); - expect(Token::Identifier); + string signature; + bool hasName = false; + + if (accept(Token::Identifier, false)) + { + hasName = true; + signature = m_scanner.currentLiteral(); + expect(Token::Identifier); + } signature += formatToken(Token::LParen); expect(Token::LParen); @@ -169,11 +176,15 @@ string TestFileParser::parseFunctionSignature() if (accept(Token::Arrow, true)) throw Error(Error::Type::ParserError, "Invalid signature detected: " + signature); - signature += parameters; + if (!hasName && !parameters.empty()) + throw Error(Error::Type::ParserError, "Signatures without a name cannot have parameters: " + signature); + else + signature += parameters; expect(Token::RParen); signature += formatToken(Token::RParen); - return signature; + + return {signature, !hasName}; } u256 TestFileParser::parseFunctionCallValue() diff --git a/test/libsolidity/util/TestFileParser.h b/test/libsolidity/util/TestFileParser.h index dc3775357..a460f4d40 100644 --- a/test/libsolidity/util/TestFileParser.h +++ b/test/libsolidity/util/TestFileParser.h @@ -46,6 +46,8 @@ namespace test * // -> 2, 3 * // h(uint256), 1 ether: 42 * // -> FAILURE # If REVERT or other EVM failure was detected # + * // () # Call fallback function # + * // (), 1 ether # Call ether function # * ... */ class TestFileParser @@ -128,8 +130,10 @@ private: bool accept(soltest::Token _token, bool const _expect = false); bool expect(soltest::Token _token, bool const _advance = true); - /// Parses a function call signature in the form of f(uint256, ...). - std::string parseFunctionSignature(); + /// Parses a function call signature in the form of `f(uint256, ...)` and + /// returns the signature and a flag that indicates if the function name was + /// empty. If so, the signature is not allowed to define any parameters. + std::pair parseFunctionSignature(); /// Parses the optional ether value that can be passed alongside the /// function call arguments. Throws an InvalidEtherValueEncoding exception diff --git a/test/libsolidity/util/TestFileParserTests.cpp b/test/libsolidity/util/TestFileParserTests.cpp index 72ac9210d..3ef9b6b81 100644 --- a/test/libsolidity/util/TestFileParserTests.cpp +++ b/test/libsolidity/util/TestFileParserTests.cpp @@ -219,6 +219,23 @@ BOOST_AUTO_TEST_CASE(non_existent_call_revert) testFunctionCall(calls.at(0), Mode::MultiLine, "i_am_not_there()", true); } +BOOST_AUTO_TEST_CASE(call_revert_message) +{ + char const* source = R"( + // f() -> FAILURE, hex"08c379a0", 0x20, 6, "Revert" + )"; + auto const calls = parse(source); + BOOST_REQUIRE_EQUAL(calls.size(), 1); + testFunctionCall( + calls.at(0), + Mode::SingleLine, + "f()", + true, + fmt::encodeArgs(), + fromHex("08c379a0") + fmt::encodeDyn(string{"Revert"}) + ); +} + BOOST_AUTO_TEST_CASE(call_expectations_empty_single_line) { char const* source = R"( diff --git a/test/libsolidity/util/TestFunctionCall.cpp b/test/libsolidity/util/TestFunctionCall.cpp index 272950782..25b60a184 100644 --- a/test/libsolidity/util/TestFunctionCall.cpp +++ b/test/libsolidity/util/TestFunctionCall.cpp @@ -101,7 +101,7 @@ string TestFunctionCall::format( { bool const isFailure = m_call.expectations.failure; result = isFailure ? - failure : + formatFailure(_errorReporter, m_call, m_rawBytes, _renderResult, highlight) : formatRawParameters(m_call.expectations.result); if (!result.empty()) AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result; @@ -111,7 +111,7 @@ string TestFunctionCall::format( bytes output = m_rawBytes; bool const isFailure = m_failure; result = isFailure ? - failure : + formatFailure(_errorReporter, m_call, output, _renderResult, highlight) : matchesExpectation() ? formatRawParameters(m_call.expectations.result) : formatBytesParameters( @@ -122,6 +122,36 @@ string TestFunctionCall::format( highlight ); + if (!matchesExpectation()) + { + boost::optional abiParams; + + if (isFailure) + { + if (!output.empty()) + abiParams = boost::make_optional(ContractABIUtils::failureParameters(output)); + } + else + abiParams = ContractABIUtils::parametersFromJsonOutputs( + _errorReporter, + m_contractABI, + m_call.signature + ); + + string bytesOutput = abiParams ? + BytesUtils::formatRawBytes(output, abiParams.get(), _linePrefix) : + BytesUtils::formatRawBytes( + output, + ContractABIUtils::defaultParameters(ceil(output.size() / 32)), + _linePrefix + ); + + _errorReporter.warning( + "The call to \"" + m_call.signature + "\" returned \n" + + bytesOutput + ); + } + if (isFailure) AnsiColorized(stream, highlight, {dev::formatting::RED_BACKGROUND}) << ws << result; else @@ -155,49 +185,90 @@ string TestFunctionCall::formatBytesParameters( bytes const& _bytes, string const& _signature, dev::solidity::test::ParameterList const& _parameters, - bool _highlight + bool _highlight, + bool _failure ) const { using ParameterList = dev::solidity::test::ParameterList; stringstream os; + if (_bytes.empty()) return {}; - _errorReporter.warning("The call to \"" + _signature + "\" returned \n" + BytesUtils::formatRawBytes(_bytes)); - - boost::optional abiParams = ContractABIUtils::parametersFromJsonOutputs( - _errorReporter, - m_contractABI, - _signature - ); - - if (abiParams) + if (_failure) { - boost::optional preferredParams = ContractABIUtils::preferredParameters( - _errorReporter, - _parameters, - abiParams.get(), - _bytes + os << BytesUtils::formatBytesRange( + _bytes, + ContractABIUtils::failureParameters(_bytes), + _highlight ); - if (preferredParams) - { - ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.get(), abiParams.get()); - os << BytesUtils::formatBytesRange(_bytes, preferredParams.get(), _highlight); - } + return os.str(); } else { - ParameterList defaultParameters; - fill_n( - back_inserter(defaultParameters), - ceil(_bytes.size() / 32), - Parameter{bytes(), "", ABIType{ABIType::Hex}, FormatInfo{}} + boost::optional abiParams = ContractABIUtils::parametersFromJsonOutputs( + _errorReporter, + m_contractABI, + _signature ); - ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters); - os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight); + + if (abiParams) + { + boost::optional preferredParams = ContractABIUtils::preferredParameters( + _errorReporter, + _parameters, + abiParams.get(), + _bytes + ); + + if (preferredParams) + { + ContractABIUtils::overwriteParameters(_errorReporter, preferredParams.get(), abiParams.get()); + os << BytesUtils::formatBytesRange(_bytes, preferredParams.get(), _highlight); + } + } + else + { + ParameterList defaultParameters = ContractABIUtils::defaultParameters(ceil(_bytes.size() / 32)); + + ContractABIUtils::overwriteParameters(_errorReporter, defaultParameters, _parameters); + os << BytesUtils::formatBytesRange(_bytes, defaultParameters, _highlight); + } + return os.str(); } +} + +string TestFunctionCall::formatFailure( + ErrorReporter& _errorReporter, + dev::solidity::test::FunctionCall const& _call, + bytes const& _output, + bool _renderResult, + bool _highlight +) const +{ + using Token = soltest::Token; + + stringstream os; + + os << formatToken(Token::Failure); + + if (!_output.empty()) + os << ", "; + + if (_renderResult) + os << formatBytesParameters( + _errorReporter, + _output, + _call.signature, + _call.expectations.result, + _highlight, + true + ); + else + os << formatRawParameters(_call.expectations.result); + return os.str(); } diff --git a/test/libsolidity/util/TestFunctionCall.h b/test/libsolidity/util/TestFunctionCall.h index 9a49777fd..8edb2afc6 100644 --- a/test/libsolidity/util/TestFunctionCall.h +++ b/test/libsolidity/util/TestFunctionCall.h @@ -95,7 +95,8 @@ private: bytes const& _bytes, std::string const& _signature, ParameterList const& _params, - bool highlight = false + bool highlight = false, + bool failure = false ) const; /// Formats a given _bytes applying the _abiType. @@ -104,6 +105,15 @@ private: ABIType const& _abiType ) const; + /// Formats a FAILURE plus additional parameters, if e.g. a revert message was returned. + std::string formatFailure( + ErrorReporter& _errorReporter, + FunctionCall const& _call, + bytes const& _output, + bool _renderResult, + bool _highlight + ) const; + /// Formats the given parameters using their raw string representation. std::string formatRawParameters( ParameterList const& _params, diff --git a/test/libyul/FunctionSideEffects.cpp b/test/libyul/FunctionSideEffects.cpp new file mode 100644 index 000000000..6ae6df8ab --- /dev/null +++ b/test/libyul/FunctionSideEffects.cpp @@ -0,0 +1,125 @@ +/* + 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 + + +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; + +namespace +{ +string toString(SideEffects const& _sideEffects) +{ + vector ret; + if (_sideEffects.movable) + ret.push_back("movable"); + if (_sideEffects.sideEffectFree) + ret.push_back("sideEffectFree"); + if (_sideEffects.sideEffectFreeIfNoMSize) + ret.push_back("sideEffectFreeIfNoMSize"); + if (_sideEffects.invalidatesStorage) + ret.push_back("invalidatesStorage"); + if (_sideEffects.invalidatesMemory) + ret.push_back("invalidatesMemory"); + return joinHumanReadable(ret); +} +} + +FunctionSideEffects::FunctionSideEffects(string const& _filename) +{ + ifstream file(_filename); + if (!file) + BOOST_THROW_EXCEPTION(runtime_error("Cannot open test input: \"" + _filename + "\".")); + file.exceptions(ios::badbit); + + m_source = parseSourceAndSettings(file); + m_expectation = parseSimpleExpectations(file);} + +TestCase::TestResult FunctionSideEffects::run(ostream& _stream, string const& _linePrefix, bool _formatted) +{ + Object obj; + std::tie(obj.code, obj.analysisInfo) = yul::test::parse(m_source, false); + if (!obj.code) + BOOST_THROW_EXCEPTION(runtime_error("Parsing input failed.")); + + map functionSideEffects = SideEffectsPropagator::sideEffects( + EVMDialect::strictAssemblyForEVM(langutil::EVMVersion()), + CallGraphGenerator::callGraph(*obj.code) + ); + + std::map functionSideEffectsStr; + for (auto const& fun: functionSideEffects) + functionSideEffectsStr[fun.first.str()] = toString(fun.second); + + m_obtainedResult.clear(); + for (auto const& fun: functionSideEffectsStr) + m_obtainedResult += fun.first + ": " + fun.second + "\n"; + + if (m_expectation != m_obtainedResult) + { + string nextIndentLevel = _linePrefix + " "; + AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl; + 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 FunctionSideEffects::printSource(ostream& _stream, string const& _linePrefix, bool const) const +{ + printIndented(_stream, m_source, _linePrefix); +} + +void FunctionSideEffects::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const +{ + printIndented(_stream, m_obtainedResult, _linePrefix); +} + +void FunctionSideEffects::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const +{ + stringstream output(_output); + string line; + while (getline(output, line)) + if (line.empty()) + // Avoid trailing spaces. + _stream << boost::trim_right_copy(_linePrefix) << endl; + else + _stream << _linePrefix << line << endl; +} diff --git a/test/libyul/FunctionSideEffects.h b/test/libyul/FunctionSideEffects.h new file mode 100644 index 000000000..8fad2961c --- /dev/null +++ b/test/libyul/FunctionSideEffects.h @@ -0,0 +1,54 @@ +/* + 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 +#include +#include +#include + +namespace yul +{ +namespace test +{ + +class FunctionSideEffects: public dev::solidity::test::TestCase +{ +public: + static std::unique_ptr create(Config const& _config) + { return std::unique_ptr(new FunctionSideEffects(_config.filename)); } + explicit FunctionSideEffects(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; + + std::string m_source; + std::string m_expectation; + std::string m_obtainedResult; +}; + +} +} diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index e747f66bd..899f40f72 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis) { return _name == "builtin"_yulstring ? &f : nullptr; } - BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), false, false}; + BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), {}}; }; SimpleDialect dialect; diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 141dd1ae2..4a57e0b47 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -121,7 +121,6 @@ string YulInterpreterTest::interpret() InterpreterState state; state.maxTraceSize = 10000; state.maxSteps = 10000; - state.maxMemSize = 0x20000000; Interpreter interpreter(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); try { diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 019536b9d..c8fb703f2 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -43,8 +45,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -111,21 +115,23 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line return TestResult::FatalError; soltestAssert(m_dialect, "Dialect not set."); + + updateContext(); + if (m_optimizerStep == "disambiguator") disambiguate(); else if (m_optimizerStep == "nameDisplacer") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; NameDisplacer{ - nameDispenser, + *m_nameDispenser, {"illegal1"_yulstring, "illegal2"_yulstring, "illegal3"_yulstring, "illegal4"_yulstring, "illegal5"_yulstring} }(*m_ast); } else if (m_optimizerStep == "blockFlattener") { disambiguate(); - BlockFlattener{}(*m_ast); + BlockFlattener::run(*m_context, *m_ast); } else if (m_optimizerStep == "constantOptimiser") { @@ -133,191 +139,182 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line ConstantOptimiser{dynamic_cast(*m_dialect), meter}(*m_ast); } else if (m_optimizerStep == "varDeclInitializer") - VarDeclInitializer{}(*m_ast); + VarDeclInitializer::run(*m_context, *m_ast); else if (m_optimizerStep == "varNameCleaner") - VarNameCleaner{*m_ast, *m_dialect}(*m_ast); + VarNameCleaner::run(*m_context, *m_ast); else if (m_optimizerStep == "forLoopConditionIntoBody") { disambiguate(); - ForLoopConditionIntoBody{}(*m_ast); + ForLoopConditionIntoBody::run(*m_context, *m_ast); } else if (m_optimizerStep == "forLoopInitRewriter") { disambiguate(); - ForLoopInitRewriter{}(*m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); } else if (m_optimizerStep == "commonSubexpressionEliminator") { disambiguate(); - (CommonSubexpressionEliminator{*m_dialect})(*m_ast); + CommonSubexpressionEliminator::run(*m_context, *m_ast); } else if (m_optimizerStep == "expressionSplitter") - { - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - } + ExpressionSplitter::run(*m_context, *m_ast); else if (m_optimizerStep == "expressionJoiner") { disambiguate(); - ExpressionJoiner::run(*m_ast); + ExpressionJoiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "splitJoin") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - ExpressionJoiner::run(*m_ast); - ExpressionJoiner::run(*m_ast); + ExpressionSplitter::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "functionGrouper") { disambiguate(); - (FunctionGrouper{})(*m_ast); + FunctionGrouper::run(*m_context, *m_ast); } else if (m_optimizerStep == "functionHoister") { disambiguate(); - (FunctionHoister{})(*m_ast); + FunctionHoister::run(*m_context, *m_ast); } else if (m_optimizerStep == "expressionInliner") { disambiguate(); - ExpressionInliner(*m_dialect, *m_ast).run(); + ExpressionInliner::run(*m_context, *m_ast); } else if (m_optimizerStep == "fullInliner") { disambiguate(); - (FunctionHoister{})(*m_ast); - (FunctionGrouper{})(*m_ast); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - FullInliner(*m_ast, nameDispenser).run(); - ExpressionJoiner::run(*m_ast); + FunctionHoister::run(*m_context, *m_ast); + FunctionGrouper::run(*m_context, *m_ast); + ExpressionSplitter::run(*m_context, *m_ast); + FullInliner::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "mainFunction") { disambiguate(); - (FunctionGrouper{})(*m_ast); - (MainFunction{})(*m_ast); + FunctionGrouper::run(*m_context, *m_ast); + MainFunction::run(*m_context, *m_ast); } else if (m_optimizerStep == "rematerialiser") { disambiguate(); - Rematerialiser::run(*m_dialect, *m_ast); + Rematerialiser::run(*m_context, *m_ast); } else if (m_optimizerStep == "expressionSimplifier") { disambiguate(); - ExpressionSimplifier::run(*m_dialect, *m_ast); - ExpressionSimplifier::run(*m_dialect, *m_ast); - ExpressionSimplifier::run(*m_dialect, *m_ast); + ExpressionSimplifier::run(*m_context, *m_ast); + ExpressionSimplifier::run(*m_context, *m_ast); + ExpressionSimplifier::run(*m_context, *m_ast); } else if (m_optimizerStep == "fullSimplify") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - ForLoopInitRewriter{}(*m_ast); - CommonSubexpressionEliminator{*m_dialect}(*m_ast); - ExpressionSimplifier::run(*m_dialect, *m_ast); - UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); - DeadCodeEliminator{*m_dialect}(*m_ast); - ExpressionJoiner::run(*m_ast); - ExpressionJoiner::run(*m_ast); + ExpressionSplitter::run(*m_context, *m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); + CommonSubexpressionEliminator::run(*m_context, *m_ast); + ExpressionSimplifier::run(*m_context, *m_ast); + UnusedPruner::run(*m_context, *m_ast); + DeadCodeEliminator::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "unusedPruner") { disambiguate(); - UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + UnusedPruner::run(*m_context, *m_ast); } else if (m_optimizerStep == "deadCodeEliminator") { disambiguate(); - ForLoopInitRewriter{}(*m_ast); - DeadCodeEliminator{*m_dialect}(*m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); + DeadCodeEliminator::run(*m_context, *m_ast); } else if (m_optimizerStep == "ssaTransform") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - SSATransform::run(*m_ast, nameDispenser); + SSATransform::run(*m_context, *m_ast); } else if (m_optimizerStep == "redundantAssignEliminator") { disambiguate(); - RedundantAssignEliminator::run(*m_dialect, *m_ast); + RedundantAssignEliminator::run(*m_context, *m_ast); } else if (m_optimizerStep == "ssaPlusCleanup") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - SSATransform::run(*m_ast, nameDispenser); - RedundantAssignEliminator::run(*m_dialect, *m_ast); + SSATransform::run(*m_context, *m_ast); + RedundantAssignEliminator::run(*m_context, *m_ast); } else if (m_optimizerStep == "loadResolver") { disambiguate(); - ForLoopInitRewriter{}(*m_ast); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - CommonSubexpressionEliminator{*m_dialect}(*m_ast); - ExpressionSimplifier::run(*m_dialect, *m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); + ExpressionSplitter::run(*m_context, *m_ast); + CommonSubexpressionEliminator::run(*m_context, *m_ast); + ExpressionSimplifier::run(*m_context, *m_ast); - LoadResolver::run(*m_dialect, *m_ast); + LoadResolver::run(*m_context, *m_ast); - UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); - ExpressionJoiner::run(*m_ast); - ExpressionJoiner::run(*m_ast); + UnusedPruner::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); + ExpressionJoiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); - ControlFlowSimplifier{*m_dialect}(*m_ast); + ControlFlowSimplifier::run(*m_context, *m_ast); } else if (m_optimizerStep == "structuralSimplifier") { disambiguate(); - StructuralSimplifier{*m_dialect}(*m_ast); + ForLoopInitRewriter::run(*m_context, *m_ast); + LiteralRematerialiser::run(*m_context, *m_ast); + StructuralSimplifier::run(*m_context, *m_ast); } else if (m_optimizerStep == "equivalentFunctionCombiner") { disambiguate(); - EquivalentFunctionCombiner::run(*m_ast); + EquivalentFunctionCombiner::run(*m_context, *m_ast); } else if (m_optimizerStep == "ssaReverser") { disambiguate(); - SSAReverser::run(*m_ast); + SSAReverser::run(*m_context, *m_ast); } else if (m_optimizerStep == "ssaAndBack") { disambiguate(); // apply SSA - NameDispenser nameDispenser{*m_dialect, *m_ast}; - SSATransform::run(*m_ast, nameDispenser); - RedundantAssignEliminator::run(*m_dialect, *m_ast); + SSATransform::run(*m_context, *m_ast); + RedundantAssignEliminator::run(*m_context, *m_ast); // reverse SSA - SSAReverser::run(*m_ast); - CommonSubexpressionEliminator{*m_dialect}(*m_ast); - UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + SSAReverser::run(*m_context, *m_ast); + CommonSubexpressionEliminator::run(*m_context, *m_ast); + UnusedPruner::run(*m_context, *m_ast); } else if (m_optimizerStep == "stackCompressor") { disambiguate(); - (FunctionGrouper{})(*m_ast); + FunctionGrouper::run(*m_context, *m_ast); size_t maxIterations = 16; Object obj; obj.code = m_ast; StackCompressor::run(*m_dialect, obj, true, maxIterations); m_ast = obj.code; - (BlockFlattener{})(*m_ast); + BlockFlattener::run(*m_context, *m_ast); } else if (m_optimizerStep == "wordSizeTransform") { disambiguate(); - NameDispenser nameDispenser{*m_dialect, *m_ast}; - ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - WordSizeTransform::run(*m_dialect, *m_ast, nameDispenser); + ExpressionSplitter::run(*m_context, *m_ast); + WordSizeTransform::run(*m_dialect, *m_ast, *m_nameDispenser); } else if (m_optimizerStep == "fullSuite") { @@ -408,6 +405,13 @@ void YulOptimizerTest::disambiguate() { *m_ast = boost::get(Disambiguator(*m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); + updateContext(); +} + +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}); } void YulOptimizerTest::printErrors(ostream& _stream, ErrorList const& _errors) diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index 16bafcf0b..87248cfad 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -19,6 +19,14 @@ #include +#include +#include + +#include + +#include +#include + namespace langutil { class Scanner; @@ -58,6 +66,7 @@ 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); void disambiguate(); + void updateContext(); static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors); @@ -67,6 +76,10 @@ private: std::string m_expectation; Dialect const* m_dialect = nullptr; + std::set m_reservedIdentifiers; + std::unique_ptr m_nameDispenser; + std::unique_ptr m_context; + std::shared_ptr m_ast; std::shared_ptr m_analysisInfo; std::string m_obtainedResult; diff --git a/test/libyul/functionSideEffects/cyclic_graph.yul b/test/libyul/functionSideEffects/cyclic_graph.yul new file mode 100644 index 000000000..742d74087 --- /dev/null +++ b/test/libyul/functionSideEffects/cyclic_graph.yul @@ -0,0 +1,10 @@ +{ + function a() { b() } + function b() { c() } + function c() { b() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// b: movable, sideEffectFree, sideEffectFreeIfNoMSize +// c: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/doubly_recursive_function.yul b/test/libyul/functionSideEffects/doubly_recursive_function.yul new file mode 100644 index 000000000..cbc594f38 --- /dev/null +++ b/test/libyul/functionSideEffects/doubly_recursive_function.yul @@ -0,0 +1,8 @@ +{ + function a() { b() } + function b() { a() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// b: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/empty.yul b/test/libyul/functionSideEffects/empty.yul new file mode 100644 index 000000000..9e85e64b7 --- /dev/null +++ b/test/libyul/functionSideEffects/empty.yul @@ -0,0 +1,4 @@ +{ +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/empty_with_sstore.yul b/test/libyul/functionSideEffects/empty_with_sstore.yul new file mode 100644 index 000000000..42a1b564c --- /dev/null +++ b/test/libyul/functionSideEffects/empty_with_sstore.yul @@ -0,0 +1,5 @@ +{ + sstore(0, 1) +} +// ---- +// : invalidatesStorage diff --git a/test/libyul/functionSideEffects/multi_calls.yul b/test/libyul/functionSideEffects/multi_calls.yul new file mode 100644 index 000000000..f60b15e3d --- /dev/null +++ b/test/libyul/functionSideEffects/multi_calls.yul @@ -0,0 +1,22 @@ +{ + function a() { + b() + } + function b() { + sstore(0, 1) + b() + } + function c() { + mstore(0, 1) + a() + d() + } + function d() { + } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: invalidatesStorage +// b: invalidatesStorage +// c: invalidatesStorage, invalidatesMemory +// d: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/recursive_function.yul b/test/libyul/functionSideEffects/recursive_function.yul new file mode 100644 index 000000000..cacefefb4 --- /dev/null +++ b/test/libyul/functionSideEffects/recursive_function.yul @@ -0,0 +1,6 @@ +{ + function a() { a() } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/simple_functions.yul b/test/libyul/functionSideEffects/simple_functions.yul new file mode 100644 index 000000000..fb0f5378e --- /dev/null +++ b/test/libyul/functionSideEffects/simple_functions.yul @@ -0,0 +1,14 @@ +{ + function a() {} + function f() { mstore(0, 1) } + function g() { sstore(0, 1) } + function h() { let x := msize() } + function i() { let z := mload(0) } +} +// ---- +// : movable, sideEffectFree, sideEffectFreeIfNoMSize +// a: movable, sideEffectFree, sideEffectFreeIfNoMSize +// f: invalidatesMemory +// g: invalidatesStorage +// h: sideEffectFree, sideEffectFreeIfNoMSize +// i: sideEffectFreeIfNoMSize diff --git a/test/libyul/functionSideEffects/structures.yul b/test/libyul/functionSideEffects/structures.yul new file mode 100644 index 000000000..de1fa5b0c --- /dev/null +++ b/test/libyul/functionSideEffects/structures.yul @@ -0,0 +1,40 @@ +{ + if calldataload(0) + { + f() + } + g() + + function f() { + pop(mload(0)) + } + function g() { + if sload(0) + { + h() + } + } + function h() { + switch t() + case 1 { + i() + } + } + function t() -> x { + mstore(0, 1) + } + function i() { + sstore(0, 1) + } + function r(a) -> b { + b := mul(a, 2) + } +} +// ---- +// : invalidatesStorage, invalidatesMemory +// f: sideEffectFreeIfNoMSize +// g: invalidatesStorage, invalidatesMemory +// h: invalidatesStorage, invalidatesMemory +// i: invalidatesStorage +// r: movable, sideEffectFree, sideEffectFreeIfNoMSize +// t: invalidatesMemory diff --git a/test/libyul/yulInterpreterTests/access_large_memory_offsets.yul b/test/libyul/yulInterpreterTests/access_large_memory_offsets.yul new file mode 100644 index 000000000..483220bc1 --- /dev/null +++ b/test/libyul/yulInterpreterTests/access_large_memory_offsets.yul @@ -0,0 +1,14 @@ +{ + mstore(0, 7) + sstore(0, mload(0)) + mstore(sub(0, 1), sub(0, 1)) + sstore(1, mload(sub(0, 1))) +} +// ---- +// Trace: +// Memory dump: +// 0: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff07 +// FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0: 00000000000000000000000000000000000000000000000000000000000000ff +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000007 +// 0000000000000000000000000000000000000000000000000000000000000001: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/test/libyul/yulInterpreterTests/function_scopes.yul b/test/libyul/yulInterpreterTests/function_scopes.yul new file mode 100644 index 000000000..0150fa990 --- /dev/null +++ b/test/libyul/yulInterpreterTests/function_scopes.yul @@ -0,0 +1,20 @@ +{ + f(1) + function f(i) { + if i { g(1) } + function g(j) { + if j { h() } + f(0) + function h() { + g(0) + } + } + sstore(i, add(i, 7)) + } +} +// ---- +// Trace: +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000007 +// 0000000000000000000000000000000000000000000000000000000000000001: 0000000000000000000000000000000000000000000000000000000000000008 diff --git a/test/libyul/yulInterpreterTests/recursion.yul b/test/libyul/yulInterpreterTests/recursion.yul new file mode 100644 index 000000000..0d80faf30 --- /dev/null +++ b/test/libyul/yulInterpreterTests/recursion.yul @@ -0,0 +1,14 @@ +{ + function fib(i) -> y { + y := 1 + if gt(i, 2) { + y := add(fib(sub(i, 1)), fib(sub(i, 2))) + } + } + sstore(0, fib(8)) +} +// ---- +// Trace: +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000000: 0000000000000000000000000000000000000000000000000000000000000015 diff --git a/test/libyul/yulInterpreterTests/shadowed_symbol.yul b/test/libyul/yulInterpreterTests/shadowed_symbol.yul new file mode 100644 index 000000000..335f98a02 --- /dev/null +++ b/test/libyul/yulInterpreterTests/shadowed_symbol.yul @@ -0,0 +1,19 @@ +{ + function f() + { + // Variable declaration does not shadow namesake function declaration + // because latter not visible here. + let shadow_id + } + { + // Function named `shadow_id` is in scope now. + f() + function shadow_id() {} + } +} +// ==== +// EVMVersion: >=constantinople +// ---- +// Trace: +// Memory dump: +// Storage dump: diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul new file mode 100644 index 000000000..4725a444e --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul @@ -0,0 +1,77 @@ +{ + let _1 := 0 + let _33 := calldataload(_1) + let sum_50_141 := _1 + let sum_50_146 := _1 + let sum_50 := _1 + let length_51 := calldataload(_33) + let i_53_142 := _1 + let i_53_147 := _1 + let i_53 := _1 + for { } + 1 + { + let _108 := 1 + let i_53_121 := add(i_53, _108) + let i_53_144 := i_53_121 + let i_53_149 := i_53_121 + i_53 := i_53_121 + } + { + let _109 := lt(i_53, length_51) + let _110 := iszero(_109) + if _110 { break } + let _114_128 := iszero(_109) + if _114_128 { revert(_1, _1) } + let _13_129 := 0x20 + let _115_130 := mul(i_53, _13_129) + let _116_131 := add(_33, _115_130) + let _117_132 := add(_116_131, _13_129) + let v_122_133 := calldataload(_117_132) + let sum_50_120 := add(sum_50, v_122_133) + let sum_50_143 := sum_50_120 + let sum_50_148 := sum_50_120 + sum_50 := sum_50_120 + } + sstore(_1, sum_50) +} +// ==== +// step: commonSubexpressionEliminator +// ---- +// { +// let _1 := 0 +// let _33 := calldataload(_1) +// let sum_50_141 := _1 +// let sum_50_146 := _1 +// let sum_50 := _1 +// let length_51 := calldataload(_33) +// let i_53_142 := _1 +// let i_53_147 := _1 +// let i_53 := _1 +// for { } +// 1 +// { +// let _108 := 1 +// let i_53_121 := add(i_53, _108) +// let i_53_144 := i_53_121 +// let i_53_149 := i_53_121 +// i_53 := i_53_121 +// } +// { +// let _109 := lt(i_53, length_51) +// let _110 := iszero(_109) +// if _110 { break } +// let _114_128 := _110 +// if _110 { revert(_1, _1) } +// let _13_129 := 0x20 +// let _115_130 := mul(i_53, _13_129) +// let _116_131 := add(_33, _115_130) +// let _117_132 := add(_116_131, _13_129) +// let v_122_133 := calldataload(_117_132) +// let sum_50_120 := add(sum_50, v_122_133) +// let sum_50_143 := sum_50_120 +// let sum_50_148 := sum_50_120 +// sum_50 := sum_50_120 +// } +// sstore(_1, sum_50) +// } diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/movable_functions.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/movable_functions.yul new file mode 100644 index 000000000..3b3e65dbd --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/movable_functions.yul @@ -0,0 +1,26 @@ +{ + function double(x) -> y { y := add(x, x) } + function double_with_se(x) -> y { y := add(x, x) mstore(40, 4) } + let i := mload(3) + let a := double(i) + let b := double(i) + let c := double_with_se(i) + let d := double_with_se(i) +} +// ==== +// step: commonSubexpressionEliminator +// ---- +// { +// function double(x) -> y +// { y := add(x, x) } +// function double_with_se(x_1) -> y_2 +// { +// y_2 := add(x_1, x_1) +// mstore(40, 4) +// } +// let i := mload(3) +// let a := double(i) +// let b := a +// let c := double_with_se(i) +// let d := double_with_se(i) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/create2_and_mask.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/create2_and_mask.yul new file mode 100644 index 000000000..cd74d73ad --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/create2_and_mask.yul @@ -0,0 +1,12 @@ +{ + let a := and(create2(0, 0, 0x20, 0), 0xffffffffffffffffffffffffffffffffffffffff) + let b := and(0xffffffffffffffffffffffffffffffffffffffff, create2(0, 0, 0x20, 0)) +} +// ==== +// step: expressionSimplifier +// EVMVersion: >=constantinople +// ---- +// { +// let a := create2(0, 0, 0x20, 0) +// let b := create2(0, 0, 0x20, 0) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/create_and_mask.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/create_and_mask.yul new file mode 100644 index 000000000..c7cc887dc --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/create_and_mask.yul @@ -0,0 +1,11 @@ +{ + let a := and(create(0, 0, 0x20), 0xffffffffffffffffffffffffffffffffffffffff) + let b := and(0xffffffffffffffffffffffffffffffffffffffff, create(0, 0, 0x20)) +} +// ==== +// step: expressionSimplifier +// ---- +// { +// let a := create(0, 0, 0x20) +// let b := create(0, 0, 0x20) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/large_byte_access.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/large_byte_access.yul new file mode 100644 index 000000000..d5e7c0ad4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/large_byte_access.yul @@ -0,0 +1,16 @@ +{ + let a := calldataload(0) + let b := byte(33, a) + let c := byte(20, a) + // create cannot be removed. + let d := byte(33, create(0, 0, 0x20)) +} +// ==== +// step: expressionSimplifier +// ---- +// { +// let a := calldataload(0) +// let b := 0 +// let c := byte(20, a) +// let d := byte(33, create(0, 0, 0x20)) +// } diff --git a/test/libyul/yulOptimizerTests/expressionSimplifier/side_effects_in_for_condition.yul b/test/libyul/yulOptimizerTests/expressionSimplifier/side_effects_in_for_condition.yul new file mode 100644 index 000000000..7e181a097 --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSimplifier/side_effects_in_for_condition.yul @@ -0,0 +1,13 @@ +{ + for {} div(create(0, 1, 0), shl(msize(), 1)) {} + { + } +} +// ==== +// step: expressionSimplifier +// EVMVersion: >byzantium +// ---- +// { +// for { } div(create(0, 1, 0), shl(msize(), 1)) { } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul index bb5b90b8b..11de59aba 100644 --- a/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul +++ b/test/libyul/yulOptimizerTests/fullSimplify/not_applied_function_call_equality_not_movable.yul @@ -1,6 +1,6 @@ // Even if the functions pass the equality check, they are not movable. { - function f() -> a { } + function f() -> a { mstore(1, 2) } let b := sub(f(), f()) mstore(0, b) } @@ -9,6 +9,6 @@ // ---- // { // function f() -> a -// { } +// { mstore(1, 2) } // mstore(0, sub(f(), f())) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index fdb6e724e..4838eab02 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -462,34 +462,33 @@ // ---- // { // { -// let _1 := 0x20 -// let _2 := 0 -// let _3 := mload(_2) -// let pos := _1 -// let length := mload(_3) -// mstore(_1, length) +// let _1 := 0 +// let _2 := mload(_1) +// let pos := 0x20 +// let length := mload(_2) +// mstore(pos, length) // pos := 64 -// let srcPtr := add(_3, _1) -// let i := _2 +// let srcPtr := add(_2, 0x20) +// let i := _1 // for { } lt(i, length) { i := add(i, 1) } // { // abi_encode_t_array$_t_contract$_C_$55_$3_memory_to_t_array$_t_address_$3_memory_ptr(mload(srcPtr), pos) -// srcPtr := add(srcPtr, _1) +// srcPtr := add(srcPtr, 0x20) // pos := add(pos, 0x60) // } -// let _4 := mload(64) -// let _5 := mload(_1) -// if slt(sub(_4, _5), 128) { revert(_2, _2) } -// let offset := calldataload(add(_5, 64)) -// let _6 := 0xffffffffffffffff -// if gt(offset, _6) { revert(_2, _2) } -// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_5, offset), _4) -// let offset_1 := calldataload(add(_5, 96)) -// if gt(offset_1, _6) { revert(_2, _2) } -// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_5, offset_1), _4) -// sstore(calldataload(_5), calldataload(add(_5, _1))) +// 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))) // sstore(value2, value3) -// sstore(_2, pos) +// sstore(_1, pos) // } // function abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(offset, end) -> array // { @@ -562,12 +561,12 @@ // } // function array_allocation_size_t_array$_t_address_$dyn_memory(length) -> size // { -// if gt(length, 0xffffffffffffffff) { revert(0, 0) } +// if gt(length, 0xffffffffffffffff) { revert(size, size) } // size := add(mul(length, 0x20), 0x20) // } // function array_allocation_size_t_array$_t_uint256_$2_memory(length) -> size // { -// if gt(length, 0xffffffffffffffff) { revert(0, 0) } +// if gt(length, 0xffffffffffffffff) { revert(size, size) } // size := mul(length, 0x20) // } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 82a6dcb6a..0b82de7e9 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -233,13 +233,14 @@ // ---- // { // { -// mstore(0x80, 7673901602397024137095011250362199966051872585513276903826533215767972925880) +// let _1 := 0x80 +// mstore(_1, 7673901602397024137095011250362199966051872585513276903826533215767972925880) // mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) // let notes := add(0x04, calldataload(0x04)) // let m := calldataload(0x24) // let n := calldataload(notes) -// let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 -// let challenge := mod(calldataload(0x44), gen_order) +// let _2 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// let challenge := mod(calldataload(0x44), _2) // if gt(m, n) // { // mstore(0x00, 404) @@ -249,78 +250,75 @@ // mstore(0x2a0, caller()) // mstore(0x2c0, kn) // mstore(0x2e0, m) -// kn := mulmod(sub(gen_order, kn), challenge, gen_order) +// kn := mulmod(sub(_2, kn), challenge, _2) // hashCommitments(notes, n) -// let b := add(0x300, mul(n, 0x80)) +// let b := add(0x300, mul(n, _1)) // let i := 0 -// let i_1 := i // for { } lt(i, n) { i := add(i, 0x01) } // { -// let _1 := add(calldataload(0x04), mul(i, 0xc0)) -// let noteIndex := add(_1, 0x24) -// let k := i_1 -// let a := calldataload(add(_1, 0x44)) +// let _3 := add(calldataload(0x04), mul(i, 0xc0)) +// let noteIndex := add(_3, 0x24) +// let k := 0 +// let a := calldataload(add(_3, 0x44)) // let c := challenge -// let _2 := add(i, 0x01) -// switch eq(_2, n) +// let _4 := add(i, 0x01) +// switch eq(_4, n) // case 1 { // k := kn -// if eq(m, n) { k := sub(gen_order, kn) } +// if eq(m, n) { k := sub(_2, kn) } // } // case 0 { k := calldataload(noteIndex) } // validateCommitment(noteIndex, k, a) -// switch gt(_2, m) +// switch gt(_4, m) // case 1 { -// kn := addmod(kn, sub(gen_order, k), gen_order) -// let x := mod(mload(i_1), gen_order) -// k := mulmod(k, x, gen_order) -// a := mulmod(a, x, gen_order) -// c := mulmod(challenge, x, gen_order) -// mstore(i_1, keccak256(i_1, 0x20)) +// kn := addmod(kn, sub(_2, k), _2) +// let x := mod(mload(0), _2) +// k := mulmod(k, x, _2) +// a := mulmod(a, x, _2) +// c := mulmod(challenge, x, _2) +// mstore(0, keccak256(0, 0x20)) // } -// case 0 { -// kn := addmod(kn, k, gen_order) -// } -// let _3 := 0x40 -// calldatacopy(0xe0, add(_1, 164), _3) -// calldatacopy(0x20, add(_1, 100), _3) -// mstore(0x120, sub(gen_order, c)) +// case 0 { kn := addmod(kn, k, _2) } +// let _5 := 0x40 +// calldatacopy(0xe0, add(_3, 164), _5) +// calldatacopy(0x20, add(_3, 100), _5) +// mstore(0x120, sub(_2, c)) // mstore(0x60, k) // mstore(0xc0, a) -// let result := call(gas(), 7, i_1, 0xe0, 0x60, 0x1a0, _3) -// let result_1 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x120, _3)) -// let result_2 := and(result_1, call(gas(), 7, i_1, 0x80, 0x60, 0x160, _3)) -// let result_3 := and(result_2, call(gas(), 6, i_1, 0x120, 0x80, 0x160, _3)) -// result := and(result_3, call(gas(), 6, i_1, 0x160, 0x80, b, _3)) +// let result := call(gas(), 7, 0, 0xe0, 0x60, 0x1a0, _5) +// let result_1 := and(result, call(gas(), 7, 0, 0x20, 0x60, 0x120, _5)) +// let result_2 := and(result_1, call(gas(), 7, 0, _1, 0x60, 0x160, _5)) +// let result_3 := and(result_2, call(gas(), 6, 0, 0x120, _1, 0x160, _5)) +// result := and(result_3, call(gas(), 6, 0, 0x160, _1, b, _5)) // if eq(i, m) // { // mstore(0x260, mload(0x20)) -// mstore(0x280, mload(_3)) +// mstore(0x280, mload(_5)) // mstore(0x1e0, mload(0xe0)) // mstore(0x200, sub(0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47, mload(0x100))) // } // if gt(i, m) // { // mstore(0x60, c) -// let result_4 := and(result, call(gas(), 7, i_1, 0x20, 0x60, 0x220, _3)) -// let result_5 := and(result_4, call(gas(), 6, i_1, 0x220, 0x80, 0x260, _3)) -// result := and(result_5, call(gas(), 6, i_1, 0x1a0, 0x80, 0x1e0, _3)) +// let result_4 := and(result, call(gas(), 7, 0, 0x20, 0x60, 0x220, _5)) +// let result_5 := and(result_4, call(gas(), 6, 0, 0x220, _1, 0x260, _5)) +// result := and(result_5, call(gas(), 6, 0, 0x1a0, _1, 0x1e0, _5)) // } // if iszero(result) // { -// mstore(i_1, 400) -// revert(i_1, 0x20) +// mstore(0, 400) +// revert(0, 0x20) // } -// b := add(b, _3) +// b := add(b, _5) // } // if lt(m, n) { validatePairing(0x64) } -// if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), gen_order), challenge)) +// if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), _2), challenge)) // { -// mstore(i_1, 404) -// revert(i_1, 0x20) +// mstore(0, 404) +// revert(0, 0x20) // } -// mstore(i_1, 0x01) -// return(i_1, 0x20) +// mstore(0, 0x01) +// return(0, 0x20) // } // function validatePairing(t2) // { @@ -360,13 +358,13 @@ // } // function validateCommitment(note, k, a) // { -// let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 -// let field_order := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 // let gammaX := calldataload(add(note, 0x40)) // let gammaY := calldataload(add(note, 0x60)) // let sigmaX := calldataload(add(note, 0x80)) // let sigmaY := calldataload(add(note, 0xa0)) -// if iszero(and(and(and(eq(mod(a, gen_order), a), gt(a, 1)), and(eq(mod(k, gen_order), k), gt(k, 1))), and(eq(addmod(mulmod(mulmod(sigmaX, sigmaX, field_order), sigmaX, field_order), 3, field_order), mulmod(sigmaY, sigmaY, field_order)), eq(addmod(mulmod(mulmod(gammaX, gammaX, field_order), gammaX, field_order), 3, field_order), mulmod(gammaY, gammaY, field_order))))) +// let _1 := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 +// let _2 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// if iszero(and(and(and(eq(mod(a, _2), a), gt(a, 1)), and(eq(mod(k, _2), k), gt(k, 1))), and(eq(addmod(mulmod(mulmod(sigmaX, sigmaX, _1), sigmaX, _1), 3, _1), mulmod(sigmaY, sigmaY, _1)), eq(addmod(mulmod(mulmod(gammaX, gammaX, _1), gammaX, _1), 3, _1), mulmod(gammaY, gammaY, _1))))) // { // mstore(0x00, 400) // revert(0x00, 0x20) diff --git a/test/libyul/yulOptimizerTests/fullSuite/medium.yul b/test/libyul/yulOptimizerTests/fullSuite/medium.yul index bebb8c418..a27acf809 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/medium.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/medium.yul @@ -21,11 +21,10 @@ // ---- // { // { -// let _1 := 0x40 -// mstore(_1, add(mload(_1), 0x20)) -// let p := mload(_1) -// mstore(_1, add(p, _1)) -// mstore(add(p, 96), 2) -// mstore(_1, 0x20) +// let _1 := mload(0x40) +// mstore(0x40, add(_1, 0x20)) +// mstore(0x40, add(_1, 96)) +// mstore(add(_1, 128), 2) +// mstore(0x40, 0x20) // } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul index 25e279f67..c4a70b03e 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul @@ -47,9 +47,9 @@ // } // function abi_decode_t_bytes_calldata_ptr(offset, end) -> arrayPos, length // { -// if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } +// if iszero(slt(add(offset, 0x1f), end)) { revert(arrayPos, arrayPos) } // length := calldataload(offset) -// if gt(length, 0xffffffffffffffff) { revert(0, 0) } +// if gt(length, 0xffffffffffffffff) { revert(arrayPos, arrayPos) } // arrayPos := add(offset, 0x20) // if gt(add(add(offset, length), 0x20), end) { revert(0, 0) } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/storage.yul b/test/libyul/yulOptimizerTests/fullSuite/storage.yul index 9867345fa..4daff379f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/storage.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/storage.yul @@ -10,6 +10,6 @@ // { // sstore(4, 5) // sstore(4, 3) -// sstore(8, sload(4)) +// sstore(8, 3) // } // } diff --git a/test/libyul/yulOptimizerTests/loadResolver/loop.yul b/test/libyul/yulOptimizerTests/loadResolver/loop.yul new file mode 100644 index 000000000..4f7b27734 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/loop.yul @@ -0,0 +1,18 @@ +{ + sstore(0, 123213) + for {let x := 0 let y} lt(x, sload(0)) { + x := add(x, 1)} {y := add(x, y) + } +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := 123213 +// let _2 := 0 +// sstore(_2, _1) +// let x := _2 +// let y +// for { } lt(x, _1) { x := add(x, 1) } +// { y := add(x, y) } +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul index 0bd777cf8..a9487afa7 100644 --- a/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul @@ -31,8 +31,5 @@ // mstore8(calldataload(_5), 4) // sstore(_5, mload(_2)) // mstore(_2, _17) -// g() -// sstore(_5, mload(_2)) -// function g() -// { } +// sstore(_5, _17) // } diff --git a/test/libyul/yulOptimizerTests/loadResolver/re_store_memory.yul b/test/libyul/yulOptimizerTests/loadResolver/re_store_memory.yul new file mode 100644 index 000000000..7fb27c996 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/re_store_memory.yul @@ -0,0 +1,21 @@ +{ + let a := 0 + let b := 1 + let c := 2 + mstore(a, b) + sstore(0, mload(a)) + mstore(a, c) + sstore(10, mload(a)) +} +// ==== +// step: loadResolver +// ---- +// { +// let a := 0 +// let b := 1 +// let c := 2 +// mstore(a, b) +// sstore(a, b) +// mstore(a, c) +// sstore(10, c) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/re_store_storage.yul b/test/libyul/yulOptimizerTests/loadResolver/re_store_storage.yul new file mode 100644 index 000000000..aed91e002 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/re_store_storage.yul @@ -0,0 +1,21 @@ +{ + let a := 0 + let b := 1 + let c := 2 + sstore(a, b) + mstore(0, sload(a)) + sstore(a, c) + mstore(32, sload(a)) +} +// ==== +// step: loadResolver +// ---- +// { +// let a := 0 +// let b := 1 +// let c := 2 +// sstore(a, b) +// mstore(a, b) +// sstore(a, c) +// mstore(32, c) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul b/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul new file mode 100644 index 000000000..b07883c3a --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul @@ -0,0 +1,28 @@ +{ + function stores() { mstore(0, 1) } + function reads() { sstore(9, mload(7)) } + + mstore(2, 9) + reads() + sstore(0, mload(2)) + stores() + sstore(0, mload(2)) +} +// ==== +// step: loadResolver +// ---- +// { +// function stores() +// { mstore(0, 1) } +// function reads() +// { sstore(9, mload(7)) } +// let _6 := 9 +// let _7 := 2 +// mstore(_7, _6) +// reads() +// let _9 := _6 +// let _10 := 0 +// sstore(_10, _9) +// stores() +// sstore(_10, mload(_7)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/staticcall.yul b/test/libyul/yulOptimizerTests/loadResolver/staticcall.yul new file mode 100644 index 000000000..75307ad5d --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/staticcall.yul @@ -0,0 +1,27 @@ +{ + let a := 0 + let b := 1 + let c := 2 + sstore(a, b) + mstore(900, 7) + let d := staticcall(10000, 10, 0, 200, 0, 200) + sstore(add(a, 1), mload(900)) + // Main test objective: replace this sload. + mstore(0, sload(a)) +} +// ==== +// step: loadResolver +// EVMVersion: >=byzantium +// ---- +// { +// let a := 0 +// let b := 1 +// sstore(a, b) +// let _1 := 7 +// let _2 := 900 +// mstore(_2, _1) +// let _3 := 200 +// pop(staticcall(10000, 10, a, _3, a, _3)) +// sstore(1, mload(_2)) +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul index df26cae3e..e4787540d 100644 --- a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul +++ b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul @@ -23,8 +23,8 @@ // let b := mload(1) // for { } lt(mload(a), mload(b)) { a := mload(b) } // { -// let b_3 := mload(a) -// let a_6 := mload(b_3) -// b := mload(a_6) +// let b_4 := mload(a) +// a := mload(b_4) +// b := mload(a) // } // } diff --git a/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul b/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul index 501737088..28520d3c5 100644 --- a/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul +++ b/test/libyul/yulOptimizerTests/ssaPlusCleanup/control_structures.yul @@ -16,21 +16,25 @@ // { // function copy(from, to) -> length // { -// let length_1 := mload(from) +// let from_6 := from +// let to_7 := to +// let length_1 := mload(from_6) // length := length_1 -// mstore(to, length_1) -// let from_2 := add(from, 0x20) -// let to_3 := add(to, 0x20) +// mstore(to_7, length_1) +// let from_2 := add(from_6, 0x20) +// let to_3 := add(to_7, 0x20) // let x_4 := 1 // let x := x_4 // for { } // lt(x, length_1) // { -// let x_5 := add(x, 0x20) +// let x_9 := x +// let x_5 := add(x_9, 0x20) // x := x_5 // } // { -// mstore(add(to_3, x), mload(add(from_2, x))) +// let x_8 := x +// mstore(add(to_3, x_8), mload(add(from_2, x_8))) // } // } // } diff --git a/test/libyul/yulOptimizerTests/ssaReverser/self_assign.yul b/test/libyul/yulOptimizerTests/ssaReverser/self_assign.yul new file mode 100644 index 000000000..48b9d39cf --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaReverser/self_assign.yul @@ -0,0 +1,8 @@ +{ + let a := calldataload(0) + a := a +} +// ==== +// step: ssaReverser +// ---- +// { let a := calldataload(0) } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/branches.yul b/test/libyul/yulOptimizerTests/ssaTransform/branches.yul index c2fc3c774..76a459160 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/branches.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/branches.yul @@ -20,7 +20,8 @@ // let a_3 := add(a_2, 1) // a := a_3 // } -// let a_4 := add(a, 1) +// let a_5 := a +// let a_4 := add(a_5, 1) // a := a_4 // mstore(a_4, 1) // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul index 3e7fe480e..6901f9f17 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_body.yul @@ -12,10 +12,17 @@ // { // let a_1 := mload(0) // let a := a_1 -// for { mstore(0, a_1) } a { mstore(0, a) } +// for { mstore(0, a_1) } +// a // { -// let a_2 := add(a, 3) +// let a_4 := a +// mstore(0, a_4) +// } +// { +// let a_3 := a +// let a_2 := add(a_3, 3) // a := a_2 // } -// mstore(0, a) +// let a_5 := a +// mstore(0, a_5) // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul index eeb1cc182..fcf1a4478 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_init.yul @@ -17,7 +17,14 @@ // a := a_2 // } // a -// { mstore(0, a) } -// { mstore(0, a) } -// mstore(0, a) +// { +// let a_4 := a +// mstore(0, a_4) +// } +// { +// let a_3 := a +// mstore(0, a_3) +// } +// let a_5 := a +// mstore(0, a_5) // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul index bd174c2f0..217a043ad 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_reassign_post.yul @@ -15,9 +15,14 @@ // for { mstore(0, a_1) } // a // { -// let a_2 := add(a, 3) +// let a_4 := a +// let a_2 := add(a_4, 3) // a := a_2 // } -// { mstore(0, a) } -// mstore(0, a) +// { +// let a_3 := a +// mstore(0, a_3) +// } +// let a_5 := a +// mstore(0, a_5) // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul index d5e1a5404..cec419633 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/for_simple.yul @@ -26,23 +26,28 @@ // let a_3 := add(a_2, 2) // a := a_3 // } +// let a_9 := a // { -// let a_4 := add(a, 4) +// let a_4 := add(a_9, 4) // a := a_4 // } +// let a_10 := a // for { -// let a_5 := add(a, 3) +// let a_5 := add(a_10, 3) // a := a_5 // } // a // { -// let a_7 := add(a, 6) -// a := a_7 -// } -// { -// let a_6 := add(a, 12) +// let a_12 := a +// let a_6 := add(a_12, 6) // a := a_6 // } -// let a_8 := add(a, 8) +// { +// let a_11 := a +// let a_7 := add(a_11, 12) +// a := a_7 +// } +// let a_13 := a +// let a_8 := add(a_13, 8) // a := a_8 // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/function.yul b/test/libyul/yulOptimizerTests/ssaTransform/function.yul index 16f5b03cd..b6e120f40 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/function.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/function.yul @@ -12,13 +12,15 @@ // { // function f(a, b) -> c, d // { -// let b_1 := add(b, a) +// let b_5 := b +// let a_6 := a +// let b_1 := add(b_5, a_6) // b := b_1 // let c_2 := add(c, b_1) // c := c_2 // let d_3 := add(d, c_2) // d := d_3 -// let a_4 := add(a, d_3) +// let a_4 := add(a_6, d_3) // a := a_4 // } // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/nested.yul b/test/libyul/yulOptimizerTests/ssaTransform/nested.yul index 7f3af38c2..a48de0002 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/nested.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/nested.yul @@ -28,6 +28,7 @@ // let a_6 := 4 // a := a_6 // } -// let a_7 := add(b_4, a) +// let a_8 := a +// let a_7 := add(b_4, a_8) // a := a_7 // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/nested_reassign.yul b/test/libyul/yulOptimizerTests/ssaTransform/nested_reassign.yul new file mode 100644 index 000000000..7d2d7bc33 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaTransform/nested_reassign.yul @@ -0,0 +1,32 @@ +{ + let a + let b + let x + if a { + if b { + x := 2 + } + } + // Should create new SSA variables for x here, + // but not above because end of block + mstore(0, x) +} +// ==== +// step: ssaTransform +// ---- +// { +// let a +// let b +// let x_1 +// let x := x_1 +// if a +// { +// if b +// { +// let x_2 := 2 +// x := x_2 +// } +// } +// let x_3 := x +// mstore(0, x_3) +// } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/switch.yul b/test/libyul/yulOptimizerTests/ssaTransform/switch.yul index 404736923..32d6b339b 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/switch.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/switch.yul @@ -20,8 +20,10 @@ // a := a_2 // } // default { -// let a_3 := add(a, 8) +// let a_4 := a +// let a_3 := add(a_4, 8) // a := a_3 // } -// mstore(0, a) +// let a_5 := a +// mstore(0, a_5) // } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/switch_reassign.yul b/test/libyul/yulOptimizerTests/ssaTransform/switch_reassign.yul new file mode 100644 index 000000000..775df55f4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaTransform/switch_reassign.yul @@ -0,0 +1,23 @@ +{ + let a := mload(0) + switch a + case 0 { a := add(a, 4) } + default { } + // should still create an SSA variable for a + mstore(0, a) +} +// ==== +// step: ssaTransform +// ---- +// { +// let a_1 := mload(0) +// let a := a_1 +// switch a_1 +// case 0 { +// let a_2 := add(a_1, 4) +// a := a_2 +// } +// default { } +// let a_3 := a +// mstore(0, a_3) +// } diff --git a/test/libyul/yulOptimizerTests/ssaTransform/used.yul b/test/libyul/yulOptimizerTests/ssaTransform/used.yul index ef104512b..c23bc6ee8 100644 --- a/test/libyul/yulOptimizerTests/ssaTransform/used.yul +++ b/test/libyul/yulOptimizerTests/ssaTransform/used.yul @@ -33,7 +33,8 @@ // a := a_4 // mstore(a_4, 0) // } -// mstore(a, 0) +// let a_6 := a +// mstore(a_6, 0) // let a_5 := 4 // a := a_5 // mstore(a_5, 0) diff --git a/test/libyul/yulOptimizerTests/unusedPruner/movable_user_defined_function.yul b/test/libyul/yulOptimizerTests/unusedPruner/movable_user_defined_function.yul new file mode 100644 index 000000000..0d49d713b --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedPruner/movable_user_defined_function.yul @@ -0,0 +1,13 @@ +{ + function f(x) -> t { + let b := 7 + } + function g(x) -> t { + t := x + } + let x := f(g(2)) +} +// ==== +// step: unusedPruner +// ---- +// { } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul b/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul index 58c969e6d..b4f6a5a7e 100644 --- a/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul +++ b/test/libyul/yulOptimizerTests/unusedPruner/multi_declare.yul @@ -5,8 +5,4 @@ // ==== // step: unusedPruner // ---- -// { -// function f() -> x, y -// { } -// let a, b := f() -// } +// { } diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index a8922061b..f02b092bd 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -30,6 +30,8 @@ add_executable(isoltest ../libsolidity/ABIJsonTest.cpp ../libsolidity/ASTJSONTest.cpp ../libsolidity/SMTCheckerJSONTest.cpp + ../libyul/Common.cpp + ../libyul/FunctionSideEffects.cpp ../libyul/ObjectCompilerTest.cpp ../libyul/YulOptimizerTest.cpp ../libyul/YulInterpreterTest.cpp diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp index 0a193b410..edeb1e554 100644 --- a/test/tools/fuzzer_common.cpp +++ b/test/tools/fuzzer_common.cpp @@ -28,6 +28,15 @@ using namespace std; using namespace dev; using namespace dev::eth; +static vector s_evmVersions = { + "homestead", + "tangerineWhistle", + "spuriousDragon", + "byzantium", + "constantinople", + "petersburg" +}; + void FuzzerUtil::runCompiler(string const& _input, bool _quiet) { if (!_quiet) @@ -76,6 +85,7 @@ void FuzzerUtil::testCompiler(string const& _input, bool _optimize, bool _quiet) config["settings"]["optimizer"] = Json::objectValue; config["settings"]["optimizer"]["enabled"] = _optimize; config["settings"]["optimizer"]["runs"] = 200; + config["settings"]["evmVersion"] = s_evmVersions[_input.size() % s_evmVersions.size()]; // Enable all SourceUnit-level outputs. config["settings"]["outputSelection"]["*"][""][0] = "*"; diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index aa3877695..08b11f40b 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -287,7 +287,7 @@ TestStats TestTool::processPath( _testCaseCreator, _options, fullpath, - currentPath.string() + currentPath.generic_path().string() ); auto result = testTool.process(); @@ -417,9 +417,9 @@ int main(int argc, char const *argv[]) bool disableSemantics = !dev::test::EVMHost::getVM(options.evmonePath.string()); if (disableSemantics) { - cout << "Unable to find libevmone.so. Please provide the path using --evmonepath ." << endl; + cout << "Unable to find " << dev::test::evmoneFilename << ". Please provide the path using --evmonepath ." << endl; cout << "You can download it at" << endl; - cout << "https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-linux-x86_64.tar.gz" << endl; + cout << dev::test::evmoneDownloadLink << endl; cout << endl << "--- SKIPPING ALL SEMANTICS TESTS ---" << endl << endl; } @@ -462,7 +462,7 @@ int main(int argc, char const *argv[]) cout << "." << endl; if (disableSemantics) - cout << "\nNOTE: Skipped semantics tests because libevmone.so could not be found.\n" << endl; + cout << "\nNOTE: Skipped semantics tests because " << dev::test::evmoneFilename << " could not be found.\n" << endl; return global_stats ? 0 : 1; } diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 4528583de..2481c63c3 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -17,24 +17,29 @@ if (OSSFUZZ) endif() if (OSSFUZZ) - #[[FuzzingEngine.a is provided by oss-fuzz's Dockerized build environment]] add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp) - target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) + target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm) + set_target_properties(solc_opt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp) - target_link_libraries(solc_noopt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) + target_link_libraries(solc_noopt_ossfuzz PRIVATE libsolc evmasm) + set_target_properties(solc_noopt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(const_opt_ossfuzz const_opt_ossfuzz.cpp ../fuzzer_common.cpp) - target_link_libraries(const_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) + target_link_libraries(const_opt_ossfuzz PRIVATE libsolc evmasm) + set_target_properties(const_opt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(strictasm_diff_ossfuzz strictasm_diff_ossfuzz.cpp yulFuzzerCommon.cpp) - target_link_libraries(strictasm_diff_ossfuzz PRIVATE libsolc evmasm yulInterpreter FuzzingEngine.a) + target_link_libraries(strictasm_diff_ossfuzz PRIVATE libsolc evmasm yulInterpreter) + set_target_properties(strictasm_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(strictasm_opt_ossfuzz strictasm_opt_ossfuzz.cpp) - target_link_libraries(strictasm_opt_ossfuzz PRIVATE yul FuzzingEngine.a) + target_link_libraries(strictasm_opt_ossfuzz PRIVATE yul) + set_target_properties(strictasm_opt_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(strictasm_assembly_ossfuzz strictasm_assembly_ossfuzz.cpp) - target_link_libraries(strictasm_assembly_ossfuzz PRIVATE yul FuzzingEngine.a) + target_link_libraries(strictasm_assembly_ossfuzz PRIVATE yul) + set_target_properties(strictasm_assembly_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(yul_proto_ossfuzz yulProtoFuzzer.cpp protoToYul.cpp yulProto.pb.cc) target_include_directories(yul_proto_ossfuzz PRIVATE /usr/include/libprotobuf-mutator) @@ -42,7 +47,8 @@ if (OSSFUZZ) protobuf-mutator-libfuzzer.a protobuf-mutator.a protobuf.a - FuzzingEngine.a) + ) + set_target_properties(yul_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(yul_proto_diff_ossfuzz yulProto_diff_ossfuzz.cpp yulFuzzerCommon.cpp protoToYul.cpp yulProto.pb.cc) target_include_directories(yul_proto_diff_ossfuzz PRIVATE /usr/include/libprotobuf-mutator) @@ -51,7 +57,8 @@ if (OSSFUZZ) protobuf-mutator-libfuzzer.a protobuf-mutator.a protobuf.a - FuzzingEngine.a) + ) + set_target_properties(yul_proto_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) add_executable(abiv2_proto_ossfuzz ../../EVMHost.cpp @@ -68,8 +75,8 @@ if (OSSFUZZ) protobuf-mutator-libfuzzer.a protobuf-mutator.a protobuf.a - FuzzingEngine.a ) + set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) else() add_library(solc_opt_ossfuzz solc_opt_ossfuzz.cpp diff --git a/test/tools/ossfuzz/protoToAbiV2.cpp b/test/tools/ossfuzz/protoToAbiV2.cpp index e44437662..b20fca810 100644 --- a/test/tools/ossfuzz/protoToAbiV2.cpp +++ b/test/tools/ossfuzz/protoToAbiV2.cpp @@ -204,14 +204,23 @@ std::string ProtoConverter::addressValueAsString(unsigned _counter) .render(); } -std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) +std::string ProtoConverter::croppedString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral +) { + // _numBytes can not be zero or exceed 32 bytes solAssert( - (_width >= 1 && _width <= 32), - "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" + _numBytes > 0 && _numBytes <= 32, + "Proto ABIv2 fuzzer: Too short or too long a cropped string" ); - // Masked value must contain twice the number of nibble "f"'s as _width - unsigned numMaskNibbles = _width * 2; + + // Number of masked nibbles is twice the number of bytes for a + // hex literal of _numBytes bytes. For a string literal, each nibble + // is treated as a character. + unsigned numMaskNibbles = _isHexLiteral ? _numBytes * 2 : _numBytes; + // Start position of substring equals totalHexStringLength - numMaskNibbles // totalHexStringLength = 64 + 2 = 66 // e.g., 0x12345678901234567890123456789012 is a total of 66 characters @@ -220,16 +229,133 @@ std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _co // <-----------total length ---------> // Note: This assumes that maskUnsignedIntToHex() invokes toHex(..., HexPrefix::Add) unsigned startPos = 66 - numMaskNibbles; + // Extracts the least significant numMaskNibbles from the result + // of maskUnsignedIntToHex(). + return maskUnsignedIntToHex( + _counter, + numMaskNibbles + ).substr(startPos, numMaskNibbles); +} + +std::string ProtoConverter::hexValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral, + bool _decorate +) +{ + solAssert(_numBytes > 0 && _numBytes <= 32, + "Proto ABIv2 fuzzer: Invalid hex length" + ); + + // If _decorate is set, then we return a hex"" or a "" string. + if (_numBytes == 0) + return Whiskers(R"(hex"")") + ("decorate", _decorate) + ("isHex", _isHexLiteral) + .render(); - // Extracts the least significant numMaskNibbles from the result of "maskUnsignedIntToHex", - // and replaces "0x" with "hex\"...\"" string. // This is needed because solidity interprets a 20-byte 0x prefixed hex literal as an address // payable type. - return Whiskers(R"(hex"")") - ("value", maskUnsignedIntToHex(_counter, numMaskNibbles).substr(startPos, numMaskNibbles)) + return Whiskers(R"(hex"")") + ("decorate", _decorate) + ("isHex", _isHexLiteral) + ("value", croppedString(_numBytes, _counter, _isHexLiteral)) .render(); } +std::string ProtoConverter::variableLengthValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral +) +{ + solAssert(_numBytes >= 0 && _numBytes <= s_maxDynArrayLength, + "Proto ABIv2 fuzzer: Invalid hex length" + ); + if (_numBytes == 0) + return Whiskers(R"(hex"")") + ("isHex", _isHexLiteral) + .render(); + + unsigned numBytesRemaining = _numBytes; + // Stores the literal + string output{}; + // If requested value is shorter than or exactly 32 bytes, + // the literal is the return value of hexValueAsString. + if (numBytesRemaining <= 32) + output = hexValueAsString( + numBytesRemaining, + _counter, + _isHexLiteral, + /*decorate=*/false + ); + // If requested value is longer than 32 bytes, the literal + // is obtained by duplicating the return value of hexValueAsString + // until we reach a value of the requested size. + else + { + // Create a 32-byte value to be duplicated and + // update number of bytes to be appended. + // Stores the cached literal that saves us + // (expensive) calls to keccak256. + string cachedString = hexValueAsString( + /*numBytes=*/32, + _counter, + _isHexLiteral, + /*decorate=*/false + ); + output = cachedString; + numBytesRemaining -= 32; + + // Append bytes from cachedString until + // we create a value of desired length. + unsigned numAppendedBytes; + while (numBytesRemaining > 0) + { + // We append at most 32 bytes at a time + numAppendedBytes = numBytesRemaining >= 32 ? 32 : numBytesRemaining; + output += cachedString.substr( + 0, + // Double the substring length for hex literals since each + // character is actually half a byte (or a nibble). + _isHexLiteral ? numAppendedBytes * 2 : numAppendedBytes + ); + numBytesRemaining -= numAppendedBytes; + } + solAssert( + numBytesRemaining == 0, + "Proto ABIv2 fuzzer: Logic flaw in variable literal creation" + ); + } + + if (_isHexLiteral) + solAssert( + output.size() == 2 * _numBytes, + "Proto ABIv2 fuzzer: Generated hex literal is of incorrect length" + ); + else + solAssert( + output.size() == _numBytes, + "Proto ABIv2 fuzzer: Generated string literal is of incorrect length" + ); + + // Decorate output + return Whiskers(R"(hex"")") + ("isHexLiteral", _isHexLiteral) + ("value", output) + .render(); +} + +std::string ProtoConverter::fixedByteValueAsString(unsigned _width, unsigned _counter) +{ + solAssert( + (_width >= 1 && _width <= 32), + "Proto ABIv2 Fuzzer: Fixed byte width is not between 1--32" + ); + return hexValueAsString(_width, _counter, /*isHexLiteral=*/true); +} + std::string ProtoConverter::integerValueAsString(bool _sign, unsigned _width, unsigned _counter) { if (_sign) @@ -314,11 +440,17 @@ void ProtoConverter::visit(ValueType const& _x) void ProtoConverter::visit(DynamicByteArrayType const& _x) { + bool isBytes = _x.type() == DynamicByteArrayType::BYTES; visitType( - (_x.type() == DynamicByteArrayType::BYTES) ? DataType::BYTES : DataType::STRING, + isBytes ? DataType::BYTES : DataType::STRING, bytesArrayTypeAsString(_x), - bytesArrayValueAsString(getNextCounter()) + bytesArrayValueAsString( + getNextCounter(), + isBytes + ) ); + // Update right padding of type + m_isLastDynParamRightPadded = true; } // TODO: Implement struct visitor @@ -367,7 +499,10 @@ std::string ProtoConverter::getValueByBaseType(ArrayType const& _x) case ArrayType::kBoolty: return boolValueAsString(getNextCounter()); case ArrayType::kDynbytesty: - return bytesArrayValueAsString(getNextCounter()); + return bytesArrayValueAsString( + getNextCounter(), + _x.dynbytesty().type() == DynamicByteArrayType::BYTES + ); // TODO: Implement structs. case ArrayType::kStty: case ArrayType::BASE_TYPE_ONEOF_NOT_SET: @@ -518,6 +653,19 @@ void ProtoConverter::visit(ArrayType const& _x) if (_x.info_size() == 0 || _x.info_size() > (int)s_maxArrayDimensions) return; + // Array type is dynamically encoded if one of the following is true + // - array base type is "bytes" or "string" + // - at least one array dimension is dynamically sized. + if (_x.base_type_oneof_case() == ArrayType::kDynbytesty) + m_isLastDynParamRightPadded = true; + else + for (auto const& dim: _x.info()) + if (!dim.is_static()) + { + m_isLastDynParamRightPadded = true; + break; + } + string baseType = {}; switch (_x.base_type_oneof_case()) { @@ -687,28 +835,42 @@ void ProtoConverter::visit(TestFunction const& _x) visit(_x.local_vars()); m_output << Whiskers(R"( - uint returnVal = this.coder_public(); + uint returnVal = this.coder_public(); if (returnVal != 0) return returnVal; - returnVal = this.coder_external(); + returnVal = this.coder_external(); if (returnVal != 0) return uint(200000) + returnVal; - bytes memory argumentEncoding = abi.encode(); + + bytes memory argumentEncoding = abi.encode(); - returnVal = checkEncodedCall(this.coder_public.selector, argumentEncoding, ); + returnVal = checkEncodedCall( + this.coder_public.selector, + argumentEncoding, + , + + ); if (returnVal != 0) return returnVal; - returnVal = checkEncodedCall(this.coder_external.selector, argumentEncoding, ); + returnVal = checkEncodedCall( + this.coder_external.selector, + argumentEncoding, + , + + ); if (returnVal != 0) return uint(200000) + returnVal; + return 0; } )") - ("parameter_names", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) + ("parameterNames", dev::suffixedVariableNameList(s_varNamePrefix, 0, m_varCounter)) ("invalidLengthFuzz", std::to_string(_x.invalid_encoding_length())) + ("isRightPadded", isLastDynParamRightPadded() ? "true" : "false") + ("atLeastOneVar", m_varCounter > 0) .render(); } @@ -724,14 +886,31 @@ void ProtoConverter::writeHelperFunctions() return true; } - /// Accepts function selector, correct argument encoding, and length of invalid encoding and returns - /// the correct and incorrect abi encoding for calling the function specified by the function selector. - function createEncoding(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) - internal pure returns (bytes memory, bytes memory) + /// Accepts function selector, correct argument encoding, and length of + /// invalid encoding and returns the correct and incorrect abi encoding + /// for calling the function specified by the function selector. + function createEncoding( + bytes4 funcSelector, + bytes memory argumentEncoding, + uint invalidLengthFuzz, + bool isRightPadded + ) internal pure returns (bytes memory, bytes memory) { bytes memory validEncoding = new bytes(4 + argumentEncoding.length); - // Ensure that invalidEncoding crops at least one and at most all bytes from correct encoding. - uint invalidLength = invalidLengthFuzz % argumentEncoding.length; + // Ensure that invalidEncoding crops at least 32 bytes (padding length + // is at most 31 bytes) if `isRightPadded` is true. + // This is because shorter bytes/string values (whose encoding is right + // padded) can lead to successful decoding when fewer than 32 bytes have + // been cropped in the worst case. In other words, if `isRightPadded` is + // true, then + // 0 <= invalidLength <= argumentEncoding.length - 32 + // otherwise + // 0 <= invalidLength <= argumentEncoding.length - 1 + uint invalidLength; + if (isRightPadded) + invalidLength = invalidLengthFuzz % (argumentEncoding.length - 31); + else + invalidLength = invalidLengthFuzz % argumentEncoding.length; bytes memory invalidEncoding = new bytes(4 + invalidLength); for (uint i = 0; i < 4; i++) validEncoding[i] = invalidEncoding[i] = funcSelector[i]; @@ -742,12 +921,23 @@ void ProtoConverter::writeHelperFunctions() return (validEncoding, invalidEncoding); } - /// Accepts function selector, correct argument encoding, and an invalid encoding length as input. - /// Returns a non-zero value if either call with correct encoding fails or call with incorrect encoding - /// succeeds. Returns zero if both calls meet expectation. - function checkEncodedCall(bytes4 funcSelector, bytes memory argumentEncoding, uint invalidLengthFuzz) public returns (uint) + /// Accepts function selector, correct argument encoding, and an invalid + /// encoding length as input. Returns a non-zero value if either call with + /// correct encoding fails or call with incorrect encoding succeeds. + /// Returns zero if both calls meet expectation. + function checkEncodedCall( + bytes4 funcSelector, + bytes memory argumentEncoding, + uint invalidLengthFuzz, + bool isRightPadded + ) public returns (uint) { - (bytes memory validEncoding, bytes memory invalidEncoding) = createEncoding(funcSelector, argumentEncoding, invalidLengthFuzz); + (bytes memory validEncoding, bytes memory invalidEncoding) = createEncoding( + funcSelector, + argumentEncoding, + invalidLengthFuzz, + isRightPadded + ); (bool success, bytes memory returnVal) = address(this).call(validEncoding); uint returnCode = abi.decode(returnVal, (uint)); // Return non-zero value if call fails for correct encoding diff --git a/test/tools/ossfuzz/protoToAbiV2.h b/test/tools/ossfuzz/protoToAbiV2.h index f302c0b74..401867364 100644 --- a/test/tools/ossfuzz/protoToAbiV2.h +++ b/test/tools/ossfuzz/protoToAbiV2.h @@ -102,7 +102,8 @@ public: m_isStateVar(true), m_counter(0), m_varCounter(0), - m_returnValue(1) + m_returnValue(1), + m_isLastDynParamRightPadded(false) {} ProtoConverter(ProtoConverter const&) = delete; @@ -268,18 +269,16 @@ private: ); } - // String and bytes literals are derived by hashing a monotonically increasing - // counter and enclosing the said hash inside double quotes. - std::string bytesArrayValueAsString(unsigned _counter) - { - return "\"" + toHex(hashUnsignedInt(_counter), HexPrefix::DontAdd) + "\""; - } - std::string getQualifier(DataType _dataType) { return ((isValueType(_dataType) || m_isStateVar) ? "" : "memory"); } + bool isLastDynParamRightPadded() + { + return m_isLastDynParamRightPadded; + } + // Static declarations static std::string structTypeAsString(StructType const& _x); static std::string boolValueAsString(unsigned _counter); @@ -288,6 +287,34 @@ private: static std::string integerValueAsString(bool _sign, unsigned _width, unsigned _counter); static std::string addressValueAsString(unsigned _counter); static std::string fixedByteValueAsString(unsigned _width, unsigned _counter); + + /// Returns a hex literal if _isHexLiteral is true, a string literal otherwise. + /// The size of the returned literal is _numBytes bytes. + /// @param _decorate If true, the returned string is enclosed within double quotes + /// if _isHexLiteral is false. + /// @param _isHexLiteral If true, the returned string is enclosed within + /// double quotes prefixed by the string "hex" if _decorate is true. If + /// _decorate is false, the returned string is returned as-is. + /// @return hex value as string + static std::string hexValueAsString( + unsigned _numBytes, + unsigned _counter, + bool _isHexLiteral, + bool _decorate = true + ); + + /// Concatenates the hash value obtained from monotonically increasing counter + /// until the desired number of bytes determined by _numBytes. + /// @param _width Desired number of bytes for hex value + /// @param _counter A counter value used for creating a keccak256 hash + /// @param _isHexLiteral Since this routine may be used to construct + /// string or hex literals, this flag is used to construct a valid output. + /// @return Valid hex or string literal of size _width bytes + static std::string variableLengthValueAsString( + unsigned _width, + unsigned _counter, + bool _isHexLiteral + ); static std::vector> arrayDimensionsAsPairVector(ArrayType const& _x); static std::string arrayDimInfoAsString(ArrayDimensionInfo const& _x); static void arrayDimensionsAsStringVector( @@ -297,6 +324,7 @@ private: static std::string bytesArrayTypeAsString(DynamicByteArrayType const& _x); static std::string arrayTypeAsString(std::string const&, ArrayType const&); static std::string delimiterToString(Delimiter _delimiter); + static std::string croppedString(unsigned _numBytes, unsigned _counter, bool _isHexLiteral); // Static function definitions static bool isValueType(DataType _dataType) @@ -347,6 +375,12 @@ private: return DataType::BYTES; } + /// Returns true if input is either a string or bytes, false otherwise. + static bool isDataTypeBytesOrString(DataType _type) + { + return _type == DataType::STRING || _type == DataType::BYTES; + } + // Convert _counter to string and return its keccak256 hash static u256 hashUnsignedInt(unsigned _counter) { @@ -389,6 +423,33 @@ private: ); } + /// Returns a pseudo-random value for the size of a string/hex + /// literal. Used for creating variable length hex/string literals. + /// @param _counter Monotonically increasing counter value + static unsigned getVarLength(unsigned _counter) + { + // Since _counter values are usually small, we use + // this linear equation to make the number derived from + // _counter approach a uniform distribution over + // [0, s_maxDynArrayLength] + return (_counter + 879) * 32 % (s_maxDynArrayLength + 1); + } + + /// Returns a hex/string literal of variable length whose value and + /// size are pseudo-randomly determined from the counter value. + /// @param _counter A monotonically increasing counter value + /// @param _isHexLiteral Flag that indicates whether hex (if true) or + /// string literal (false) is desired + /// @return A variable length hex/string value + static std::string bytesArrayValueAsString(unsigned _counter, bool _isHexLiteral) + { + return variableLengthValueAsString( + getVarLength(_counter), + _counter, + _isHexLiteral + ); + } + /// Contains the test program std::ostringstream m_output; /// Temporary storage for state variable definitions @@ -405,8 +466,13 @@ private: unsigned m_varCounter; /// Monotonically increasing return value for error reporting unsigned m_returnValue; + /// Flag that indicates if last dynamically encoded parameter + /// passed to a function call is of a type that is going to be + /// right padded by the ABI encoder. + bool m_isLastDynParamRightPadded; static unsigned constexpr s_maxArrayLength = 4; static unsigned constexpr s_maxArrayDimensions = 4; + static unsigned constexpr s_maxDynArrayLength = 256; /// Prefixes for declared and parameterized variable names static auto constexpr s_varNamePrefix = "x_"; static auto constexpr s_paramNamePrefix = "c_"; diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 7d2e1b80a..345797afb 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -16,15 +16,31 @@ */ #include -#include -#include +#include + #include +#include + +#include +#include +#include +#include + using namespace std; using namespace yul::test::yul_fuzzer; using namespace dev; -string ProtoConverter::createHex(string const& _hexBytes) const +string ProtoConverter::dictionaryToken(HexPrefix _p) +{ + unsigned indexVar = m_inputSize * m_inputSize + counter(); + std::string token = hexDictionary[indexVar % hexDictionary.size()]; + yulAssert(token.size() <= 64, "Proto Fuzzer: Dictionary token too large"); + + return _p == HexPrefix::Add ? "0x" + token : token; +} + +string ProtoConverter::createHex(string const& _hexBytes) { string tmp{_hexBytes}; if (!tmp.empty()) @@ -35,12 +51,18 @@ string ProtoConverter::createHex(string const& _hexBytes) const tmp = tmp.substr(0, 64); } // We need this awkward if case because hex literals cannot be empty. + // Use a dictionary token. if (tmp.empty()) - tmp = "1"; + tmp = dictionaryToken(HexPrefix::DontAdd); + // Hex literals must have even number of digits + if (tmp.size() % 2) + tmp.insert(0, "0"); + + yulAssert(tmp.size() <= 64, "Proto Fuzzer: Dictionary token too large"); return tmp; } -string ProtoConverter::createAlphaNum(string const& _strBytes) const +string ProtoConverter::createAlphaNum(string const& _strBytes) { string tmp{_strBytes}; if (!tmp.empty()) @@ -53,59 +75,49 @@ string ProtoConverter::createAlphaNum(string const& _strBytes) const return tmp; } -bool ProtoConverter::isCaseLiteralUnique(Literal const& _x) -{ - dev::u256 mpCaseLiteralValue; - bool isUnique = false; - - switch (_x.literal_oneof_case()) - { - case Literal::kIntval: - mpCaseLiteralValue = dev::u256(_x.intval()); - break; - case Literal::kHexval: - // We need to ask boost mp library to treat this - // as a hex value. Hence the "0x" prefix. - mpCaseLiteralValue = dev::u256("0x" + createHex(_x.hexval())); - break; - case Literal::kStrval: - mpCaseLiteralValue = dev::u256(dev::h256(createAlphaNum(_x.strval()), dev::h256::FromBinary, dev::h256::AlignLeft)); - break; - case Literal::LITERAL_ONEOF_NOT_SET: - // If the proto generator does not generate a valid Literal - // we generate a case 1: - mpCaseLiteralValue = 1; - break; - } - - isUnique = m_switchLiteralSetPerScope.top().insert(mpCaseLiteralValue).second; - return isUnique; -} - -void ProtoConverter::visit(Literal const& _x) +string ProtoConverter::visit(Literal const& _x) { switch (_x.literal_oneof_case()) { case Literal::kIntval: - m_output << _x.intval(); - break; + return to_string(_x.intval()); case Literal::kHexval: - m_output << "0x" << createHex(_x.hexval()); - break; + return "0x" + createHex(_x.hexval()); case Literal::kStrval: - m_output << "\"" << createAlphaNum(_x.strval()) << "\""; - break; + return "\"" + createAlphaNum(_x.strval()) + "\""; case Literal::LITERAL_ONEOF_NOT_SET: - m_output << "1"; - break; + return dictionaryToken(); } } -// Reference any index in [0, m_numLiveVars-1] +bool ProtoConverter::varDeclAvailable() +{ + if (m_inFunctionDef) + return m_scopeVars.top().size() > 0; + else + return m_variables.size() > 0; +} + +bool ProtoConverter::functionCallNotPossible(FunctionCall_Returns _type) +{ + return _type == FunctionCall::SINGLE || + (_type == FunctionCall::MULTIASSIGN && !varDeclAvailable()); +} + void ProtoConverter::visit(VarRef const& _x) { - yulAssert(m_numLiveVars > 0, "Proto fuzzer: No variables to reference."); - m_output << "x_" << (static_cast(_x.varnum()) % m_numLiveVars); + 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()]; + } + 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()]; + } } void ProtoConverter::visit(Expression const& _x) @@ -113,10 +125,16 @@ void ProtoConverter::visit(Expression const& _x) switch (_x.expr_oneof_case()) { case Expression::kVarref: - visit(_x.varref()); + // If the expression requires a variable reference that we cannot provide + // (because there are no variables in scope), we silently output a literal + // expression from the optimizer dictionary. + if (!varDeclAvailable()) + m_output << dictionaryToken(); + else + visit(_x.varref()); break; case Expression::kCons: - visit(_x.cons()); + m_output << visit(_x.cons()); break; case Expression::kBinop: visit(_x.binop()); @@ -131,10 +149,27 @@ void ProtoConverter::visit(Expression const& _x) visit(_x.nop()); break; case Expression::kFuncExpr: - visit(_x.func_expr()); + // FunctionCall must return a single value, otherwise + // we output a trivial expression "1". + if (_x.func_expr().ret() == FunctionCall::SINGLE) + visit(_x.func_expr()); + else + m_output << dictionaryToken(); + break; + case Expression::kLowcall: + visit(_x.lowcall()); + break; + case Expression::kCreate: + visit(_x.create()); + break; + case Expression::kUnopdata: + if (m_isObject) + visit(_x.unopdata()); + else + m_output << dictionaryToken(); break; case Expression::EXPR_ONEOF_NOT_SET: - m_output << "1"; + m_output << dictionaryToken(); break; } } @@ -219,54 +254,18 @@ void ProtoConverter::visit(BinaryOp const& _x) void ProtoConverter::visit(VarDecl const& _x) { - m_output << "let x_" << m_numLiveVars << " := "; + string varName = newVarName(); + m_output << "let " << varName << " := "; visit(_x.expr()); - m_numVarsPerScope.top()++; - m_numLiveVars++; m_output << "\n"; -} - -void ProtoConverter::visit(EmptyVarDecl const&) -{ - m_output << "let x_" << m_numLiveVars++ << "\n"; - m_numVarsPerScope.top()++; -} - -void ProtoConverter::visit(MultiVarDecl const& _x) -{ - size_t funcId = (static_cast(_x.func_index()) % m_functionVecMultiReturnValue.size()); - - int numInParams = m_functionVecMultiReturnValue.at(funcId).first; - int numOutParams = m_functionVecMultiReturnValue.at(funcId).second; - - // Ensure that the chosen function returns at least 2 and at most 4 values - yulAssert( - ((numOutParams >= 2) && (numOutParams <= 4)), - "Proto fuzzer: Multi variable declaration calls a function with either too few or too many output params." - ); - - // We must start variable numbering past the number of live variables at this point in time. - // This creates let x_p,..., x_k := - // (k-p)+1 = numOutParams - m_output << - "let " << - dev::suffixedVariableNameList("x_", m_numLiveVars, m_numLiveVars + numOutParams) << - " := "; - - // Create RHS of multi var decl - m_output << "foo_" << functionTypeToString(NumFunctionReturns::Multiple) << "_" << funcId; - m_output << "("; - visitFunctionInputParams(_x, numInParams); - m_output << ")\n"; - // Update live variables in scope and in total to account for the variables created by this - // multi variable declaration. - m_numVarsPerScope.top() += numOutParams; - m_numLiveVars += numOutParams; + m_scopeVars.top().push_back(varName); + m_variables.push_back(varName); } void ProtoConverter::visit(TypedVarDecl const& _x) { - m_output << "let x_" << m_numLiveVars; + string varName = newVarName(); + m_output << "let " << varName; switch (_x.type()) { case TypedVarDecl::BOOL: @@ -325,8 +324,8 @@ void ProtoConverter::visit(TypedVarDecl const& _x) m_output << " : u256\n"; break; } - m_numVarsPerScope.top()++; - m_numLiveVars++; + m_scopeVars.top().push_back(varName); + m_variables.push_back(varName); } void ProtoConverter::visit(UnaryOp const& _x) @@ -354,6 +353,12 @@ void ProtoConverter::visit(UnaryOp const& _x) case UnaryOp::EXTCODEHASH: m_output << "extcodehash"; break; + case UnaryOp::BALANCE: + m_output << "balance"; + break; + case UnaryOp::BLOCKHASH: + m_output << "blockhash"; + break; } m_output << "("; visit(_x.operand()); @@ -402,12 +407,49 @@ void ProtoConverter::visit(NullaryOp const& _x) case NullaryOp::RETURNDATASIZE: m_output << "returndatasize()"; break; + case NullaryOp::ADDRESS: + m_output << "address()"; + break; + case NullaryOp::ORIGIN: + m_output << "origin()"; + break; + case NullaryOp::CALLER: + m_output << "caller()"; + break; + case NullaryOp::CALLVALUE: + m_output << "callvalue()"; + break; + case NullaryOp::GASPRICE: + m_output << "gasprice()"; + break; + case NullaryOp::COINBASE: + m_output << "coinbase()"; + break; + case NullaryOp::TIMESTAMP: + m_output << "timestamp()"; + break; + case NullaryOp::NUMBER: + m_output << "number()"; + break; + case NullaryOp::DIFFICULTY: + m_output << "difficulty()"; + break; + case NullaryOp::GASLIMIT: + m_output << "gaslimit()"; + break; } } void ProtoConverter::visit(CopyFunc const& _x) { - switch (_x.ct()) + CopyFunc_CopyType type = _x.ct(); + + // datacopy() is valid only if we are inside + // a yul object. + if (type == CopyFunc::DATA && !m_isObject) + return; + + switch (type) { case CopyFunc::CALLDATA: m_output << "calldatacopy"; @@ -418,6 +460,9 @@ void ProtoConverter::visit(CopyFunc const& _x) case CopyFunc::RETURNDATA: m_output << "returndatacopy"; break; + case CopyFunc::DATA: + m_output << "datacopy"; + break; } m_output << "("; visit(_x.target()); @@ -517,9 +562,7 @@ void ProtoConverter::visit(AssignmentStatement const& _x) m_output << "\n"; } -// Called at the time function call is being made -template -void ProtoConverter::visitFunctionInputParams(T const& _x, unsigned _numInputParams) +void ProtoConverter::visitFunctionInputParams(FunctionCall const& _x, unsigned _numInputParams) { // We reverse the order of function input visits since it helps keep this switch case concise. switch (_numInputParams) @@ -547,87 +590,217 @@ void ProtoConverter::visitFunctionInputParams(T const& _x, unsigned _numInputPar } } -void ProtoConverter::visit(MultiAssignment const& _x) +bool ProtoConverter::functionValid(FunctionCall_Returns _type, unsigned _numOutParams) { - size_t funcId = (static_cast(_x.func_index()) % m_functionVecMultiReturnValue.size()); - unsigned numInParams = m_functionVecMultiReturnValue.at(funcId).first; - unsigned numOutParams = m_functionVecMultiReturnValue.at(funcId).second; - yulAssert( - ((numOutParams >= 2) && (numOutParams <= 4)), - "Proto fuzzer: Multi assignment calls a function that has either too many or too few output parameters." - ); - - // Convert LHS of multi assignment - // We reverse the order of out param visits since the order does not matter. This helps reduce the size of this - // switch statement. - switch (numOutParams) + switch (_type) { - case 4: - visit(_x.out_param4()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 3: - visit(_x.out_param3()); - m_output << ", "; - BOOST_FALLTHROUGH; - case 2: - visit(_x.out_param2()); - m_output << ", "; - visit(_x.out_param1()); - break; - default: - yulAssert(false, "Proto fuzzer: Function call with too many input parameters."); - break; + case FunctionCall::ZERO: + return _numOutParams == 0; + case FunctionCall::SINGLE: + return _numOutParams == 1; + case FunctionCall::MULTIDECL: + case FunctionCall::MULTIASSIGN: + return _numOutParams > 1; } - m_output << " := "; - - // Convert RHS of multi assignment - m_output << "foo_" << functionTypeToString(NumFunctionReturns::Multiple) << "_" << funcId; - m_output << "("; - visitFunctionInputParams(_x, numInParams); - m_output << ")\n"; } -void ProtoConverter::visit(FunctionCallNoReturnVal const& _x) +void ProtoConverter::convertFunctionCall( + FunctionCall const& _x, + std::string _name, + unsigned _numInParams, + bool _newLine +) { - size_t funcId = (static_cast(_x.func_index()) % m_functionVecNoReturnValue.size()); - unsigned numInParams = m_functionVecNoReturnValue.at(funcId); - m_output << "foo_" << functionTypeToString(NumFunctionReturns::None) << "_" << funcId; - m_output << "("; - visitFunctionInputParams(_x, numInParams); - m_output << ")\n"; -} - -void ProtoConverter::visit(FunctionCallSingleReturnVal const& _x) -{ - size_t funcId = (static_cast(_x.func_index()) % m_functionVecSingleReturnValue.size()); - unsigned numInParams = m_functionVecSingleReturnValue.at(funcId); - m_output << "foo_" << functionTypeToString(NumFunctionReturns::Single) << "_" << funcId; - m_output << "("; - visitFunctionInputParams(_x, numInParams); + m_output << _name << "("; + visitFunctionInputParams(_x, _numInParams); m_output << ")"; + if (_newLine) + m_output << "\n"; +} + +vector ProtoConverter::createVarDecls(unsigned _start, unsigned _end, bool _isAssignment) +{ + m_output << "let "; + vector varsVec = createVars(_start, _end); + if (_isAssignment) + m_output << " := "; + else + m_output << "\n"; + return varsVec; } void ProtoConverter::visit(FunctionCall const& _x) { - switch (_x.functioncall_oneof_case()) + bool functionAvailable = m_functionSigMap.size() > 0; + unsigned numInParams, numOutParams; + string funcName; + FunctionCall_Returns funcType = _x.ret(); + if (functionAvailable) { - case FunctionCall::kCallZero: - visit(_x.call_zero()); + yulAssert(m_functions.size() > 0, "Proto fuzzer: No function in scope"); + funcName = m_functions[_x.func_index() % m_functions.size()]; + auto ret = m_functionSigMap.at(funcName); + numInParams = ret.first; + numOutParams = ret.second; + } + else + { + // If there are no functions available, calls to functions that + // return a single value may be replaced by a dictionary token. + if (funcType == FunctionCall::SINGLE) + m_output << dictionaryToken(); + return; + } + + // If function selected for function call does not meet interface + // requirements (num output values) for the function type + // specified, then we return early unless it is a function call + // that returns a single value (which may be replaced by a + // dictionary token. + if (!functionValid(funcType, numOutParams)) + { + if (funcType == FunctionCall::SINGLE) + m_output << dictionaryToken(); + return; + } + + // If we are here, it means that we have at least one valid + // function for making the function call + switch (funcType) + { + case FunctionCall::ZERO: + convertFunctionCall(_x, funcName, numInParams); break; - case FunctionCall::kCallMultidecl: - // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" + case FunctionCall::SINGLE: + // Since functions that return a single value are used as expressions + // we do not print a newline because it is done by the expression + // visitor. + convertFunctionCall(_x, funcName, numInParams, /*newLine=*/false); + break; + case FunctionCall::MULTIDECL: + // Hack: Disallow (multi) variable declarations until scope extension + // is implemented for "for-init" if (!m_inForInitScope) - visit(_x.call_multidecl()); + { + // Ensure that the chosen function returns at most 4 values + yulAssert( + numOutParams <= 4, + "Proto fuzzer: Function call with too many output params encountered." + ); + + // Obtain variable name suffix + unsigned startIdx = counter(); + vector varsVec = createVarDecls( + startIdx, + startIdx + numOutParams, + /*isAssignment=*/true + ); + + // Create RHS of multi var decl + convertFunctionCall(_x, funcName, numInParams); + // Add newly minted vars in the multidecl statement to current scope + addVarsToScope(varsVec); + } break; - case FunctionCall::kCallMultiassign: - visit(_x.call_multiassign()); - break; - case FunctionCall::FUNCTIONCALL_ONEOF_NOT_SET: + case FunctionCall::MULTIASSIGN: + // Ensure that the chosen function returns at most 4 values + yulAssert( + numOutParams <= 4, + "Proto fuzzer: Function call with too many output params encountered." + ); + + // Convert LHS of multi assignment + // We reverse the order of out param visits since the order does not matter. + // This helps reduce the size of this switch statement. + switch (numOutParams) + { + case 4: + visit(_x.out_param4()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 3: + visit(_x.out_param3()); + m_output << ", "; + BOOST_FALLTHROUGH; + case 2: + visit(_x.out_param2()); + m_output << ", "; + visit(_x.out_param1()); + break; + default: + yulAssert(false, "Proto fuzzer: Function call with too many or too few input parameters."); + break; + } + m_output << " := "; + + // Convert RHS of multi assignment + convertFunctionCall(_x, funcName, numInParams); break; } } +void ProtoConverter::visit(LowLevelCall const& _x) +{ + LowLevelCall_Type type = _x.callty(); + switch (type) + { + case LowLevelCall::CALL: + m_output << "call("; + break; + case LowLevelCall::CALLCODE: + m_output << "callcode("; + break; + case LowLevelCall::DELEGATECALL: + m_output << "delegatecall("; + break; + case LowLevelCall::STATICCALL: + m_output << "staticcall("; + break; + } + visit(_x.gas()); + m_output << ", "; + visit(_x.addr()); + m_output << ", "; + if (type == LowLevelCall::CALL || type == LowLevelCall::CALLCODE) + { + visit(_x.wei()); + m_output << ", "; + } + visit(_x.in()); + m_output << ", "; + visit(_x.insize()); + m_output << ", "; + visit(_x.out()); + m_output << ", "; + visit(_x.outsize()); + m_output << ")"; +} + +void ProtoConverter::visit(Create const& _x) +{ + Create_Type type = _x.createty(); + switch (type) + { + case Create::CREATE: + m_output << "create("; + break; + case Create::CREATE2: + m_output << "create2("; + break; + } + visit(_x.wei()); + m_output << ", "; + visit(_x.position()); + m_output << ", "; + visit(_x.size()); + if (type == Create::CREATE2) + { + m_output << ", "; + visit(_x.value()); + } + m_output << ")"; +} + void ProtoConverter::visit(IfStmt const& _x) { m_output << "if "; @@ -693,12 +866,64 @@ void ProtoConverter::visit(BoundedForStmt const& _x) void ProtoConverter::visit(CaseStmt const& _x) { - // Silently ignore duplicate case literals - if (isCaseLiteralUnique(_x.case_lit())) + string literal = visit(_x.case_lit()); + // u256 value of literal + u256 literalVal; + + // Convert string to u256 before looking for duplicate case literals + if (_x.case_lit().has_strval()) { - m_output << "case "; - visit(_x.case_lit()); - m_output << " "; + // Since string literals returned by the Literal visitor are enclosed within + // double quotes (like this "\"\""), their size is at least two in the worst case + // that is empty. Here we assert this invariant. + yulAssert(literal.size() >= 2, "Proto fuzzer: String literal too short"); + // This variable stores the part i.e., literal minus the first and last + // double quote characters. This is used to compute the keccak256 hash of the + // string literal. The hashing is done to check whether we are about to create + // a case statement containing a case literal that has already been used in a + // previous case statement. If the hash (u256 value) matches a previous hash, + // then we simply don't create a new case statement. + string noDoubleQuoteStr{""}; + if (literal.size() > 2) + { + // Ensure that all characters in the string literal except the first + // and the last (double quote characters) are alphanumeric. + yulAssert( + boost::algorithm::all_of(literal.begin() + 1, literal.end() - 2, [=](char c) -> bool { + return std::isalpha(c) || std::isdigit(c); + }), + "Proto fuzzer: Invalid string literal encountered" + ); + + // Make a copy because literal will need to be used later + noDoubleQuoteStr = literal.substr(1, literal.size() - 2); + } + // Hash the result to check for duplicate case literal strings + literalVal = u256(h256(noDoubleQuoteStr, h256::FromBinary, h256::AlignLeft)); + + // Make sure that an empty string literal evaluates to zero. This is to detect creation of + // duplicate case literals like so + // switch (x) + // { + // case "": { x := 0 } + // case 0: { x:= 1 } // Case statement with duplicate literal is invalid + // } // This snippet will not be parsed successfully. + if (noDoubleQuoteStr.empty()) + yulAssert(literalVal == 0, "Proto fuzzer: Empty string does not evaluate to zero"); + } + else + literalVal = u256(literal); + + // Check if set insertion fails (case literal present) or succeeds (case literal + // absent). + bool isUnique = m_switchLiteralSetPerScope.top().insert(literalVal).second; + + // It is fine to bail out if we encounter a duplicate case literal because + // we can be assured that the switch statement is well-formed i.e., contains + // at least one case statement or a default block. + if (isUnique) + { + m_output << "case " << literal << " "; visit(_x.case_block()); } } @@ -707,7 +932,7 @@ void ProtoConverter::visit(SwitchStmt const& _x) { if (_x.case_stmt_size() > 0 || _x.has_default_block()) { - std::set s; + std::set s; m_switchLiteralSetPerScope.push(s); m_output << "switch "; visit(_x.switch_expr()); @@ -783,6 +1008,23 @@ void ProtoConverter::visit(TerminatingStmt const& _x) } } +void ProtoConverter::visit(UnaryOpData const& _x) +{ + switch (_x.op()) + { + case UnaryOpData::SIZE: + m_output << Whiskers(R"(datasize(""))") + ("id", getObjectIdentifier(_x.identifier())) + .render(); + break; + case UnaryOpData::OFFSET: + m_output << Whiskers(R"(dataoffset(""))") + ("id", getObjectIdentifier(_x.identifier())) + .render(); + break; + } +} + void ProtoConverter::visit(Statement const& _x) { switch (_x.stmt_oneof_case()) @@ -793,7 +1035,10 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.decl()); break; case Statement::kAssignment: - visit(_x.assignment()); + // Create an assignment statement only if there is at least one variable + // declaration that is in scope. + if (varDeclAvailable()) + visit(_x.assignment()); break; case Statement::kIfstmt: visit(_x.ifstmt()); @@ -834,158 +1079,394 @@ void ProtoConverter::visit(Statement const& _x) visit(_x.terminatestmt()); break; case Statement::kFunctioncall: + // Return early if a function call cannot be created + if (functionCallNotPossible(_x.functioncall().ret())) + return; visit(_x.functioncall()); break; + case Statement::kFuncdef: + if (!m_inForInitScope) + visit(_x.funcdef()); + break; + case Statement::kPop: + visit(_x.pop()); + break; case Statement::STMT_ONEOF_NOT_SET: break; } } -void ProtoConverter::visit(Block const& _x) +void ProtoConverter::openScope(vector const& _funcParams) { + m_scopeVars.push({}); + m_scopeFuncs.push({}); + if (!_funcParams.empty()) + addVarsToScope(_funcParams); +} + +void ProtoConverter::updateFunctionMaps(string const& _var) +{ + unsigned erased = m_functionSigMap.erase(_var); + + for (auto const& i: m_functionDefMap) + if (i.second == _var) + { + erased += m_functionDefMap.erase(i.first); + break; + } + + yulAssert(erased == 2, "Proto fuzzer: Function maps not updated"); +} + +void ProtoConverter::closeScope() +{ + 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()) + { + unsigned numFuncsRemoved = m_functions.size(); + m_functions.erase(remove(m_functions.begin(), m_functions.end(), f), m_functions.end()); + numFuncsRemoved -= m_functions.size(); + yulAssert( + numFuncsRemoved == 1, + "Proto fuzzer: Nothing or too much went out of scope" + ); + updateFunctionMaps(f); + } + m_scopeFuncs.pop(); +} + +void ProtoConverter::addVarsToScope(vector const& _vars) +{ + for (string const& i: _vars) + { + m_variables.push_back(i); + m_scopeVars.top().push_back(i); + } +} + +void ProtoConverter::visit(Block const& _x, vector _funcParams) +{ + openScope(_funcParams); + + // Register function declarations in this scope unless this + // scope belongs to for-init (in which function declarations + // are forbidden). + for (auto const& statement: _x.statements()) + if (statement.has_funcdef() && !m_inForInitScope) + registerFunction(&statement.funcdef()); + if (_x.statements_size() > 0) { - m_numVarsPerScope.push(0); m_output << "{\n"; for (auto const& st: _x.statements()) visit(st); m_output << "}\n"; - m_numLiveVars -= m_numVarsPerScope.top(); - m_numVarsPerScope.pop(); } else m_output << "{}\n"; + closeScope(); } -void ProtoConverter::visit(SpecialBlock const& _x) +vector ProtoConverter::createVars(unsigned _startIdx, unsigned _endIdx) { - m_numVarsPerScope.push(0); - m_output << "{\n"; - visit(_x.var()); - if (_x.statements_size() > 0) - for (auto const& st: _x.statements()) - visit(st); - m_numLiveVars -= m_numVarsPerScope.top(); - m_numVarsPerScope.pop(); - m_output << "}\n"; -} - -template -void ProtoConverter::createFunctionDefAndCall(T const& _x, unsigned _numInParams, unsigned _numOutParams, NumFunctionReturns _type) -{ - yulAssert( - ((_numInParams <= modInputParams - 1) && (_numOutParams <= modOutputParams - 1)), - "Proto fuzzer: Too many function I/O parameters requested." + yulAssert(_endIdx > _startIdx, "Proto fuzzer: Variable indices not in range"); + string varsStr = dev::suffixedVariableNameList("x_", _startIdx, _endIdx); + m_output << varsStr; + vector varsVec; + boost::split( + varsVec, + varsStr, + boost::algorithm::is_any_of(", "), + boost::algorithm::token_compress_on ); - // At the time of function definition creation, the number of live variables must be 0. - // This is because we always create only as many variables as we need within function scope. - yulAssert(m_numLiveVars == 0, "Proto fuzzer: Unused live variable found."); + yulAssert( + varsVec.size() == (_endIdx - _startIdx), + "Proto fuzzer: Variable count mismatch during function definition" + ); + m_counter += varsVec.size(); + return varsVec; +} - // Signature - // This creates function foo__(x_0,...,x_n) - m_output << "function foo_" << functionTypeToString(_type) << "_" << m_numFunctionSets; - m_output << "("; - if (_numInParams > 0) - m_output << dev::suffixedVariableNameList("x_", 0, _numInParams); - m_output << ")"; +void ProtoConverter::registerFunction(FunctionDef const* _x) +{ + unsigned numInParams = _x->num_input_params() % s_modInputParams; + unsigned numOutParams = _x->num_output_params() % s_modOutputParams; + NumFunctionReturns numReturns; + if (numOutParams == 0) + numReturns = NumFunctionReturns::None; + else if (numOutParams == 1) + numReturns = NumFunctionReturns::Single; + else + numReturns = NumFunctionReturns::Multiple; - // Book keeping for variables in function scope and in nested scopes - m_numVarsPerScope.push(_numInParams); - m_numLiveVars += _numInParams; + // Generate function name + string funcName = functionName(numReturns); - // This creates -> x_n+1,...,x_r - if (_numOutParams > 0) - { - m_output << " -> " << dev::suffixedVariableNameList("x_", _numInParams, _numInParams + _numOutParams); - // More bookkeeping - m_numVarsPerScope.top() += _numOutParams; - m_numLiveVars += _numOutParams; - } - m_output << "\n"; + // Register function + 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_functionDefMap.emplace(make_pair(_x, funcName)); +} - // Body - visit(_x.statements()); - - // Ensure that variable stack is balanced - m_numLiveVars -= m_numVarsPerScope.top(); - m_numVarsPerScope.pop(); - yulAssert(m_numLiveVars == 0, "Proto fuzzer: Variable stack after function definition is unbalanced."); - - // Manually create a multi assignment using global variables - // This prints a_0, ..., a_k-1 for this function that returns "k" values - if (_numOutParams > 0) - m_output << dev::suffixedVariableNameList("a_", 0, _numOutParams) << " := "; - - // Call the function with the correct number of input parameters via calls to calldataload with - // incremental addresses. - m_output << "foo_" << functionTypeToString(_type) << "_" << std::to_string(m_numFunctionSets); - m_output << "("; +void ProtoConverter::fillFunctionCallInput(unsigned _numInParams) +{ for (unsigned i = 0; i < _numInParams; i++) { - m_output << "calldataload(" << std::to_string(i*32) << ")"; + // Throw a 4-sided dice to choose whether to populate function input + // argument from a pseudo-randomly chosen slot in one of the following + // locations: calldata, memory, storage, or yul optimizer dictionary. + unsigned diceValue = counter() % 4; + // Pseudo-randomly choose one of the first ten 32-byte + // aligned slots. + string slot = to_string((counter() % 10) * 32); + switch (diceValue) + { + case 0: + m_output << "calldataload(" << slot << ")"; + break; + case 1: + m_output << "mload(" << slot << ")"; + break; + case 2: + m_output << "sload(" << slot << ")"; + break; + case 3: + // Call to dictionaryToken() automatically picks a token + // at a pseudo-random location. + m_output << dictionaryToken(); + break; + } if (i < _numInParams - 1) m_output << ","; } +} + +void ProtoConverter::saveFunctionCallOutput(vector const& _varsVec) +{ + for (auto const& var: _varsVec) + { + // Flip a dice to choose whether to save output values + // in storage or memory. + bool coinFlip = counter() % 2 == 0; + // Pseudo-randomly choose one of the first ten 32-byte + // aligned slots. + string slot = to_string((counter() % 10) * 32); + if (coinFlip) + m_output << "sstore(" << slot << ", " << var << ")\n"; + else + m_output << "mstore(" << slot << ", " << var << ")\n"; + } +} + +void ProtoConverter::createFunctionCall( + string _funcName, + unsigned _numInParams, + unsigned _numOutParams +) +{ + vector varsVec{}; + if (_numOutParams > 0) + { + unsigned startIdx = counter(); + // Prints the following to output stream "let x_i,...,x_n := " + varsVec = createVarDecls( + startIdx, + startIdx + _numOutParams, + /*isAssignment=*/true + ); + } + + // Call the function with the correct number of input parameters + m_output << _funcName << "("; + if (_numInParams > 0) + fillFunctionCallInput(_numInParams); m_output << ")\n"; - for (unsigned i = 0; i < _numOutParams; i++) - m_output << "sstore(" << std::to_string(i*32) << ", a_" << std::to_string(i) << ")\n"; + if (!varsVec.empty()) + { + // Save values returned by function so that they are reflected + // in the interpreter trace. + saveFunctionCallOutput(varsVec); + // Add newly minted vars to current scope + addVarsToScope(varsVec); + } + else + yulAssert(_numOutParams == 0, "Proto fuzzer: Function return value not saved"); } -void ProtoConverter::visit(FunctionDefinitionNoReturnVal const& _x) +void ProtoConverter::createFunctionDefAndCall( + FunctionDef const& _x, + unsigned _numInParams, + unsigned _numOutParams +) { - unsigned numInParams = _x.num_input_params() % modInputParams; - unsigned numOutParams = 0; - createFunctionDefAndCall(_x, numInParams, numOutParams, NumFunctionReturns::None); + yulAssert( + ((_numInParams <= s_modInputParams - 1) && (_numOutParams <= s_modOutputParams - 1)), + "Proto fuzzer: Too many function I/O parameters requested." + ); + + // Obtain function name + yulAssert(m_functionDefMap.count(&_x), "Proto fuzzer: Unregistered function"); + string funcName = m_functionDefMap.at(&_x); + + vector varsVec = {}; + m_output << "function " << funcName << "("; + unsigned startIdx = counter(); + if (_numInParams > 0) + varsVec = createVars(startIdx, startIdx + _numInParams); + m_output << ")"; + + vector outVarsVec = {}; + // This creates -> x_n+1,...,x_r + if (_numOutParams > 0) + { + m_output << " -> "; + if (varsVec.empty()) + { + yulAssert(_numInParams == 0, "Proto fuzzer: Input parameters not processed correctly"); + varsVec = createVars(startIdx, startIdx + _numOutParams); + } + else + { + outVarsVec = createVars(startIdx + _numInParams, startIdx + _numInParams + _numOutParams); + varsVec.insert(varsVec.end(), outVarsVec.begin(), outVarsVec.end()); + } + } + yulAssert(varsVec.size() == _numInParams + _numOutParams, "Proto fuzzer: Function parameters not processed correctly"); + + m_output << "\n"; + + // If function definition is in for-loop body, update + bool wasInForBody = m_inForBodyScope; + m_inForBodyScope = false; + + bool wasInFunctionDef = m_inFunctionDef; + m_inFunctionDef = true; + + // Body + visit(_x.block(), varsVec); + + m_inForBodyScope = wasInForBody; + m_inFunctionDef = wasInFunctionDef; + + yulAssert( + !m_inForInitScope, + "Proto fuzzer: Trying to create function call inside for-init block" + ); + createFunctionCall(funcName, _numInParams, _numOutParams); } -void ProtoConverter::visit(FunctionDefinitionSingleReturnVal const& _x) +void ProtoConverter::visit(FunctionDef const& _x) { - unsigned numInParams = _x.num_input_params() % modInputParams; - unsigned numOutParams = 1; - createFunctionDefAndCall(_x, numInParams, numOutParams, NumFunctionReturns::Single); + unsigned numInParams = _x.num_input_params() % s_modInputParams; + unsigned numOutParams = _x.num_output_params() % s_modOutputParams; + createFunctionDefAndCall(_x, numInParams, numOutParams); } -void ProtoConverter::visit(FunctionDefinitionMultiReturnVal const& _x) +void ProtoConverter::visit(PopStmt const& _x) { - unsigned numInParams = _x.num_input_params() % modInputParams; - // Synthesize at least 2 return parameters and at most (modOutputParams - 1) - unsigned numOutParams = std::max(2, _x.num_output_params() % modOutputParams); - createFunctionDefAndCall(_x, numInParams, numOutParams, NumFunctionReturns::Multiple); + m_output << "pop("; + visit(_x.expr()); + m_output << ")\n"; } -void ProtoConverter::visit(FunctionDefinition const& _x) +string ProtoConverter::getObjectIdentifier(ObjectId const& _x) { - visit(_x.fd_zero()); - visit(_x.fd_one()); - visit(_x.fd_multi()); - m_numFunctionSets++; + unsigned currentId = currentObjectId(); + yulAssert(m_objectScopeTree.size() > currentId, "Proto fuzzer: Error referencing object"); + std::vector objectIdsInScope = m_objectScopeTree[currentId]; + return objectIdsInScope[_x.id() % objectIdsInScope.size()]; +} + +void ProtoConverter::visit(Code const& _x) +{ + m_output << "code {\n"; + visit(_x.block()); + m_output << "}\n"; +} + +void ProtoConverter::visit(Data const& _x) +{ + // TODO: Generate random data block identifier + m_output << "data \"" << s_dataIdentifier << "\" hex\"" << createHex(_x.hex()) << "\"\n"; +} + +void ProtoConverter::visit(Object const& _x) +{ + // object "object" { + // ... + // } + m_output << "object " << newObjectId() << " {\n"; + visit(_x.code()); + if (_x.has_data()) + visit(_x.data()); + if (_x.has_sub_obj()) + visit(_x.sub_obj()); + m_output << "}\n"; +} + +void ProtoConverter::buildObjectScopeTree(Object const& _x) +{ + // Identifies object being visited + string objectId = newObjectId(false); + vector node{objectId}; + if (_x.has_data()) + node.push_back(s_dataIdentifier); + if (_x.has_sub_obj()) + { + // Identifies sub object whose numeric suffix is + // m_objectId + string subObjectId = "object" + to_string(m_objectId); + node.push_back(subObjectId); + // TODO: Add sub-object to object's ancestors once + // nested access is implemented. + m_objectScopeTree.push_back(node); + buildObjectScopeTree(_x.sub_obj()); + } + else + m_objectScopeTree.push_back(node); } void ProtoConverter::visit(Program const& _x) { - /* Program template is as follows - * Four Globals a_0, a_1, a_2, and a_3 to hold up to four function return values - * - * Repeated function definitions followed by function calls of the respective function - * Example: function foo(x_0) -> x_1 {} - * a_0 := foo(calldataload(0)) - * sstore(0, a_0) - */ - m_output << "{\n"; - // Create globals at the beginning - // This creates let a_0, a_1, a_2, a_3 (followed by a new line) - m_output << "let " << dev::suffixedVariableNameList("a_", 0, modOutputParams - 1) << "\n"; - // Register function interface. Useful while visiting multi var decl/assignment statements. - for (auto const& f: _x.funcs()) - registerFunction(f); + // Initialize input size + m_inputSize = _x.ByteSizeLong(); - for (auto const& f: _x.funcs()) - visit(f); - - yulAssert((unsigned)_x.funcs_size() == m_numFunctionSets, "Proto fuzzer: Functions not correctly registered."); - m_output << "}\n"; + // Program is either a yul object or a block of + // statements. + switch (_x.program_oneof_case()) + { + case Program::kBlock: + m_output << "{\n"; + visit(_x.block()); + m_output << "}\n"; + break; + case Program::kObj: + m_isObject = true; + buildObjectScopeTree(_x.obj()); + // Reset object id counter + m_objectId = 0; + visit(_x.obj()); + break; + case Program::PROGRAM_ONEOF_NOT_SET: + // {} is a trivial yul program + m_output << "{}"; + break; + } } string ProtoConverter::programToString(Program const& _input) @@ -994,25 +1475,15 @@ string ProtoConverter::programToString(Program const& _input) return m_output.str(); } -void ProtoConverter::registerFunction(FunctionDefinition const& _x) -{ - // No return and single return functions explicitly state the number of values returned - registerFunction(_x.fd_zero(), NumFunctionReturns::None); - registerFunction(_x.fd_one(), NumFunctionReturns::Single); - // A multi return function can have between two and (modOutputParams - 1) parameters - unsigned numOutParams = std::max(2, _x.fd_multi().num_output_params() % modOutputParams); - registerFunction(_x.fd_multi(), NumFunctionReturns::Multiple, numOutParams); -} - std::string ProtoConverter::functionTypeToString(NumFunctionReturns _type) { switch (_type) { case NumFunctionReturns::None: - return "noreturn"; + return "n"; case NumFunctionReturns::Single: - return "singlereturn"; + return "s"; case NumFunctionReturns::Multiple: - return "multireturn"; + return "m"; } -} +} \ No newline at end of file diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index bea31b1e5..ef149d198 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -26,8 +26,10 @@ #include #include + #include #include +#include namespace yul { @@ -40,12 +42,14 @@ class ProtoConverter public: ProtoConverter() { - m_numLiveVars = 0; - m_numVarsPerScope.push(m_numLiveVars); - m_numFunctionSets = 0; m_inForBodyScope = false; m_inForInitScope = false; m_numNestedForLoops = 0; + m_counter = 0; + m_inputSize = 0; + m_inFunctionDef = false; + m_objectId = 0; + m_isObject = false; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; @@ -53,22 +57,23 @@ public: private: void visit(BinaryOp const&); - void visit(Block const&); - void visit(SpecialBlock const&); - void visit(Literal const&); + + /// Visits a basic block optionally adding @a _funcParams to scope. + /// @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 = {}); + + std::string visit(Literal const&); void visit(VarRef const&); void visit(Expression const&); void visit(VarDecl const&); - void visit(EmptyVarDecl const&); - void visit(MultiVarDecl const&); void visit(TypedVarDecl const&); void visit(UnaryOp const&); void visit(AssignmentStatement const&); - void visit(MultiAssignment const&); void visit(IfStmt const&); void visit(StoreFunc const&); void visit(Statement const&); - void visit(FunctionDefinition const&); void visit(ForStmt const&); void visit(BoundedForStmt const&); void visit(CaseStmt const&); @@ -82,20 +87,37 @@ private: void visit(RetRevStmt const&); void visit(SelfDestructStmt const&); void visit(TerminatingStmt const&); - void visit(FunctionCallNoReturnVal const&); - void visit(FunctionCallSingleReturnVal const&); void visit(FunctionCall const&); - void visit(FunctionDefinitionNoReturnVal const&); - void visit(FunctionDefinitionSingleReturnVal const&); - void visit(FunctionDefinitionMultiReturnVal const&); + void visit(FunctionDef const&); + void visit(PopStmt const&); + void visit(LowLevelCall const&); + void visit(Create const&); + void visit(UnaryOpData const&); + void visit(Object const&); + void visit(Data const&); + void visit(Code const&); void visit(Program const&); - template - void visit(google::protobuf::RepeatedPtrField const& _repeated_field); - void registerFunction(FunctionDefinition const&); - std::string createHex(std::string const& _hexBytes) const; - std::string createAlphaNum(std::string const& _strBytes) const; - bool isCaseLiteralUnique(Literal const&); + /// Creates a new scope, and adds @a _funcParams to it if it + /// is non-empty. + void openScope(std::vector const& _funcParams); + /// Closes current scope + void closeScope(); + /// Adds @a _vars to current scope + void addVarsToScope(std::vector const& _vars); + + std::string createHex(std::string const& _hexBytes); + + /// Returns a new variable name. + std::string newVarName() + { + return "x_" + std::to_string(counter()); + } + + /// Accepts an arbitrary string, removes all characters that are neither + /// alphabets nor digits from it and returns the said string. + std::string createAlphaNum(std::string const& _strBytes); + enum class NumFunctionReturns { None, @@ -103,54 +125,216 @@ private: Multiple }; - template - void visitFunctionInputParams(T const&, unsigned); + void visitFunctionInputParams(FunctionCall const&, unsigned); + void createFunctionDefAndCall(FunctionDef const&, unsigned, unsigned); - template - void createFunctionDefAndCall(T const&, unsigned, unsigned, NumFunctionReturns); + /// Convert function type to a string to be used while naming a + /// function that is created by a function declaration statement. + /// @param _type Type classified according to the number of + /// values returned by function. + /// @return A string as follows. If _type is + /// None -> "n" + /// Single -> "s" + /// Multiple -> "m" std::string functionTypeToString(NumFunctionReturns _type); - template - void registerFunction(T const& _x, NumFunctionReturns _type, unsigned _numOutputParams = 0) + /// Return true if at least one variable declaration is in scope, + /// false otherwise. + /// @return True in the following cases: + /// - If we are inside a function that has already declared a variable + /// - If there is at least one variable declaration that is + /// in scope + bool varDeclAvailable(); + + /// Return true if a function call cannot be made, false otherwise. + /// @param _type is an enum denoting the type of function call. It + /// can be one of NONE, SINGLE, MULTIDECL, MULTIASSIGN. + /// NONE -> Function call does not return a value + /// SINGLE -> Function call returns a single value + /// MULTIDECL -> Function call returns more than one value + /// and it is used to create a multi declaration + /// statement + /// MULTIASSIGN -> Function call returns more than one value + /// and it is used to create a multi assignment + /// statement + /// @return True if the function call cannot be created for one of the + /// following reasons + // - It is a SINGLE function call (we reserve SINGLE functions for + // expressions) + // - It is a MULTIASSIGN function call and we do not have any + // variables available for assignment. + bool functionCallNotPossible(FunctionCall_Returns _type); + + /// Checks if function call of type @a _type returns the correct number + /// of values. + /// @param _type Function call type of the function being checked + /// @param _numOutParams Number of values returned by the function + /// being checked + /// @return true if the function returns the correct number of values, + /// false otherwise + bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams); + + /// Converts protobuf function call to a yul function call and appends + /// it to output stream. + /// @param _x Protobuf function call + /// @param _name Function name + /// @param _numInParams Number of input arguments accepted by function + /// @param _newLine Flag that prints a new line to the output stream if + /// true. Default value for the flag is true. + void convertFunctionCall( + FunctionCall const& _x, + std::string _name, + unsigned _numInParams, + bool _newLine = true + ); + + /// Prints a yul formatted variable declaration statement to the output + /// stream. + /// Example 1: createVarDecls(0, 1, true) returns {"x_0"} and prints + /// let x_0 := + /// Example 2: createVarDecls(0, 2, false) returns {"x_0", "x_1"} and prints + /// let x_0, x_1 + /// @param _start Start index of variable (inclusive) + /// @param _end End index of variable (exclusive) + /// @param _isAssignment Flag indicating if variable declaration is also + /// an assignment. If true, the string " := " follows the variable + /// declaration. Otherwise, a new line is follows the variable + /// declaration. + /// @return A vector of strings containing the variable names used in + /// the declaration statement. + std::vector createVarDecls(unsigned _start, unsigned _end, bool _isAssignment); + + /// Prints comma separated variable names to output stream and + /// returns a vector containing the printed variable names. + /// Example: createVars(0, 2) returns {"x_0", "x_1"} and prints + /// x_0, x_1 + /// @param _startIdx Start index of variable (inclusive) + /// @param _endIdx End index of variable (exclusive) + /// @return A vector of strings containing the printed variable names. + std::vector createVars(unsigned _startIdx, unsigned _endIdx); + + /// Print the yul syntax to make a call to a function named @a _funcName to + /// the output stream. + /// @param _funcName Name of the function to be called + /// @param _numInParams Number of input parameters in function signature + /// @param _numOutParams Number of output parameters in function signature + void createFunctionCall(std::string _funcName, unsigned _numInParams, unsigned _numOutParams); + + /// Print the yul syntax to pass input arguments to a function that has + /// @a _numInParams number of input parameters to the output stream. + /// The input arguments are pseudo-randomly chosen from calldata, memory, + /// storage, or the yul optimizer hex dictionary. + /// @param _numInParams Number of input arguments to fill + void fillFunctionCallInput(unsigned _numInParams); + + /// Print the yul syntax to save values returned by a function call + /// to the output stream. The values are either stored to memory or + /// storage based on a simulated coin flip. The saved location is + /// decided pseudo-randomly. + /// @param _varsVec A vector of strings that reference variables + /// holding the return values of a function call. + void saveFunctionCallOutput(std::vector const& _varsVec); + + /// Register a function declaration + /// @param _f Pointer to a FunctionDef object + void registerFunction(FunctionDef const* _f); + + /// Removes entry from m_functionMap and m_functionName + void updateFunctionMaps(std::string const& _x); + + /// Build a tree of objects that contains the object/data + /// identifiers that are in scope in a given object. + /// @param _x root object of the yul protobuf specification. + void buildObjectScopeTree(Object const& _x); + + /// Returns a pseudo-random dictionary token. + /// @param _p Enum that decides if the returned token is hex prefixed ("0x") or not + /// @return Dictionary token at the index computed using a + /// monotonically increasing counter as follows: + /// index = (m_inputSize * m_inputSize + counter) % dictionarySize + /// where m_inputSize is the size of the protobuf input and + /// dictionarySize is the total number of entries in the dictionary. + std::string dictionaryToken(dev::HexPrefix _p = dev::HexPrefix::Add); + + /// Returns a monotonically increasing counter that starts from zero. + unsigned counter() { - unsigned numInputParams = _x.num_input_params() % modInputParams; - switch (_type) - { - case NumFunctionReturns::None: - m_functionVecNoReturnValue.push_back(numInputParams); - break; - case NumFunctionReturns::Single: - m_functionVecSingleReturnValue.push_back(numInputParams); - break; - case NumFunctionReturns::Multiple: - m_functionVecMultiReturnValue.push_back(std::make_pair(numInputParams, _numOutputParams)); - break; - } + return m_counter++; + } + + /// Generate function name of the form "foo__". + /// @param _type Type classified according to the number of + /// values returned by function. + std::string functionName(NumFunctionReturns _type) + { + return "foo_" + functionTypeToString(_type) + "_" + std::to_string(counter()); + } + + /// Returns a pseudo-randomly chosen object identifier that is in the + /// scope of the Yul object being visited. + std::string getObjectIdentifier(ObjectId const& _x); + + /// Return new object identifier as string. Identifier string + /// is a template of the form "\"object\"" where is + /// a monotonically increasing object ID counter. + /// @param _decorate If true (default value), object ID is + /// enclosed within double quotes. + std::string newObjectId(bool _decorate = true) + { + return dev::Whiskers(R"("object")") + ("decorate", _decorate) + ("id", std::to_string(m_objectId++)) + .render(); + } + + /// Returns the object counter value corresponding to the object + /// being visited. + unsigned currentObjectId() + { + return m_objectId - 1; } std::ostringstream m_output; - // Number of live variables in inner scope of a function - std::stack m_numVarsPerScope; - // Number of live variables in function scope - unsigned m_numLiveVars; + /// Variables in current scope + std::stack> m_scopeVars; + /// Functions in current scope + std::stack> m_scopeFuncs; + /// Variables + std::vector m_variables; + /// Functions + std::vector m_functions; + /// Maps FunctionDef object to its name + std::map m_functionDefMap; // Set that is used for deduplicating switch case literals std::stack> m_switchLiteralSetPerScope; - // Total number of function sets. A function set contains one function of each type defined by - // NumFunctionReturns - unsigned m_numFunctionSets; // Look-up table per function type that holds the number of input (output) function parameters - std::vector m_functionVecNoReturnValue; - std::vector m_functionVecSingleReturnValue; - std::vector> m_functionVecMultiReturnValue; + std::map> m_functionSigMap; + /// Tree of objects and their scopes + std::vector> m_objectScopeTree; // mod input/output parameters impose an upper bound on the number of input/output parameters a function may have. - static unsigned constexpr modInputParams = 5; - static unsigned constexpr modOutputParams = 5; - // predicate to keep track of for body scope + static unsigned constexpr s_modInputParams = 5; + static unsigned constexpr s_modOutputParams = 5; + /// Hard-coded identifier for a Yul object's data block + static auto constexpr s_dataIdentifier = "datablock"; + /// Predicate to keep track of for body scope. If true, break/continue + /// statements can not be created. bool m_inForBodyScope; // Index used for naming loop variable of bounded for loops unsigned m_numNestedForLoops; - // predicate to keep track of for loop init scope + /// Predicate to keep track of for loop init scope. If true, variable + /// or function declarations can not be created. bool m_inForInitScope; + /// Monotonically increasing counter + unsigned m_counter; + /// Size of protobuf input + unsigned m_inputSize; + /// Predicate that is true if inside function definition, false otherwise + bool m_inFunctionDef; + /// Index used for naming objects + unsigned m_objectId; + /// Flag to track whether program is an object (true) or a statement block + /// (false: default value) + bool m_isObject; }; } } diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index 0931b8e41..f6c0cff23 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -25,14 +25,12 @@ void yulFuzzerUtil::interpret( shared_ptr _ast, Dialect const& _dialect, size_t _maxSteps, - size_t _maxTraceSize, - size_t _maxMemory + size_t _maxTraceSize ) { InterpreterState state; state.maxTraceSize = _maxTraceSize; state.maxSteps = _maxSteps; - state.maxMemSize = _maxMemory; Interpreter interpreter(state, _dialect); interpreter(*_ast); state.dumpTraceAndState(_os); diff --git a/test/tools/ossfuzz/yulFuzzerCommon.h b/test/tools/ossfuzz/yulFuzzerCommon.h index 0392ef40c..fd48bb796 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.h +++ b/test/tools/ossfuzz/yulFuzzerCommon.h @@ -30,12 +30,10 @@ struct yulFuzzerUtil std::shared_ptr _ast, Dialect const& _dialect, size_t _maxSteps = maxSteps, - size_t _maxTraceSize = maxTraceSize, - size_t _maxMemory = maxMemory + size_t _maxTraceSize = maxTraceSize ); static size_t constexpr maxSteps = 100; static size_t constexpr maxTraceSize = 75; - static size_t constexpr maxMemory = 0x200; }; } } diff --git a/test/tools/ossfuzz/yulOptimizerFuzzDictionary.h b/test/tools/ossfuzz/yulOptimizerFuzzDictionary.h new file mode 100644 index 000000000..22ba98ce9 --- /dev/null +++ b/test/tools/ossfuzz/yulOptimizerFuzzDictionary.h @@ -0,0 +1,777 @@ + +static const std::vector hexDictionary = { + "0", + "1", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1f", + "9f", + "a0", + "a1", + "ff", + "100", + "101", + "10", + "100", + "1000", + "10000", + "100000", + "1000000", + "10000000", + "100000000", + "1000000000", + "10000000000", + "100000000000", + "1000000000000", + "10000000000000", + "100000000000000", + "1000000000000000", + "10000000000000000", + "100000000000000000", + "1000000000000000000", + "10000000000000000000", + "100000000000000000000", + "1000000000000000000000", + "10000000000000000000000", + "100000000000000000000000", + "1000000000000000000000000", + "10000000000000000000000000", + "100000000000000000000000000", + "1000000000000000000000000000", + "10000000000000000000000000000", + "100000000000000000000000000000", + "1000000000000000000000000000000", + "10000000000000000000000000000000", + "100000000000000000000000000000000", + "1000000000000000000000000000000000", + "10000000000000000000000000000000000", + "100000000000000000000000000000000000", + "1000000000000000000000000000000000000", + "10000000000000000000000000000000000000", + "100000000000000000000000000000000000000", + "1000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000000000", + "10000000000000000000000000000000000000000000000000000000000000", + "100000000000000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000000000000", + "1000000000000000000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000000001", + "10000000000000000000000000000000000000000001", + "1000000000000000000000000000000000000000001", + "100000000000000000000000000000000000000001", + "10000000000000000000000000000000000000001", + "1000000000000000000000000000000000000001", + "100000000000000000000000000000000000001", + "10000000000000000000000000000000000001", + "1000000000000000000000000000000000001", + "100000000000000000000000000000000001", + "10000000000000000000000000000000001", + "1000000000000000000000000000000001", + "100000000000000000000000000000001", + "10000000000000000000000000000001", + "1000000000000000000000000000001", + "100000000000000000000000000001", + "10000000000000000000000000001", + "1000000000000000000000000001", + "100000000000000000000000001", + "10000000000000000000000001", + "1000000000000000000000001", + "100000000000000000000001", + "10000000000000000000001", + "1000000000000000000001", + "100000000000000000001", + "10000000000000000001", + "1000000000000000001", + "100000000000000001", + "10000000000000001", + "1000000000000001", + "100000000000001", + "10000000000001", + "1000000000001", + "100000000001", + "10000000001", + "1000000001", + "100000001", + "10000001", + "1000001", + "100001", + "10001", + "1001", + "101", + "11", + "1f", + "1ff", + "1fff", + "1ffff", + "1fffff", + "1ffffff", + "1fffffff", + "1ffffffff", + "1fffffffff", + "1ffffffffff", + "1fffffffffff", + "1ffffffffffff", + "1fffffffffffff", + "1ffffffffffffff", + "1fffffffffffffff", + "1ffffffffffffffff", + "1fffffffffffffffff", + "1ffffffffffffffffff", + "1fffffffffffffffffff", + "1ffffffffffffffffffff", + "1fffffffffffffffffffff", + "1ffffffffffffffffffffff", + "1fffffffffffffffffffffff", + "1ffffffffffffffffffffffff", + "1fffffffffffffffffffffffff", + "1ffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "2", + "20", + "200", + "2000", + "20000", + "200000", + "2000000", + "20000000", + "200000000", + "2000000000", + "20000000000", + "200000000000", + "2000000000000", + "20000000000000", + "200000000000000", + "2000000000000000", + "20000000000000000", + "200000000000000000", + "2000000000000000000", + "20000000000000000000", + "200000000000000000000", + "2000000000000000000000", + "20000000000000000000000", + "200000000000000000000000", + "2000000000000000000000000", + "20000000000000000000000000", + "200000000000000000000000000", + "2000000000000000000000000000", + "20000000000000000000000000000", + "200000000000000000000000000000", + "2000000000000000000000000000000", + "20000000000000000000000000000000", + "200000000000000000000000000000000", + "2000000000000000000000000000000000", + "20000000000000000000000000000000000", + "200000000000000000000000000000000000", + "2000000000000000000000000000000000000", + "20000000000000000000000000000000000000", + "200000000000000000000000000000000000000", + "2000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000000000000", + "20000000000000000000000000000000000000000000000000000000000000", + "200000000000000000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000000000000000", + "2000000000000000000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000000001", + "20000000000000000000000000000000000000000001", + "2000000000000000000000000000000000000000001", + "200000000000000000000000000000000000000001", + "20000000000000000000000000000000000000001", + "2000000000000000000000000000000000000001", + "200000000000000000000000000000000000001", + "20000000000000000000000000000000000001", + "2000000000000000000000000000000000001", + "200000000000000000000000000000000001", + "20000000000000000000000000000000001", + "2000000000000000000000000000000001", + "200000000000000000000000000000001", + "20000000000000000000000000000001", + "2000000000000000000000000000001", + "200000000000000000000000000001", + "20000000000000000000000000001", + "2000000000000000000000000001", + "200000000000000000000000001", + "20000000000000000000000001", + "2000000000000000000000001", + "200000000000000000000001", + "20000000000000000000001", + "2000000000000000000001", + "200000000000000000001", + "20000000000000000001", + "2000000000000000001", + "200000000000000001", + "20000000000000001", + "2000000000000001", + "200000000000001", + "20000000000001", + "2000000000001", + "200000000001", + "20000000001", + "2000000001", + "200000001", + "20000001", + "2000001", + "200001", + "20001", + "2001", + "201", + "21", + "3", + "3f", + "3ff", + "3fff", + "3ffff", + "3fffff", + "3ffffff", + "3fffffff", + "3ffffffff", + "3fffffffff", + "3ffffffffff", + "3fffffffffff", + "3ffffffffffff", + "3fffffffffffff", + "3ffffffffffffff", + "3fffffffffffffff", + "3ffffffffffffffff", + "3fffffffffffffffff", + "3ffffffffffffffffff", + "3fffffffffffffffffff", + "3ffffffffffffffffffff", + "3fffffffffffffffffffff", + "3ffffffffffffffffffffff", + "3fffffffffffffffffffffff", + "3ffffffffffffffffffffffff", + "3fffffffffffffffffffffffff", + "3ffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "4", + "40", + "400", + "4000", + "40000", + "400000", + "4000000", + "40000000", + "400000000", + "4000000000", + "40000000000", + "400000000000", + "4000000000000", + "40000000000000", + "400000000000000", + "4000000000000000", + "40000000000000000", + "400000000000000000", + "4000000000000000000", + "40000000000000000000", + "400000000000000000000", + "4000000000000000000000", + "40000000000000000000000", + "400000000000000000000000", + "4000000000000000000000000", + "40000000000000000000000000", + "400000000000000000000000000", + "4000000000000000000000000000", + "40000000000000000000000000000", + "400000000000000000000000000000", + "4000000000000000000000000000000", + "40000000000000000000000000000000", + "400000000000000000000000000000000", + "4000000000000000000000000000000000", + "40000000000000000000000000000000000", + "400000000000000000000000000000000000", + "4000000000000000000000000000000000000", + "40000000000000000000000000000000000000", + "400000000000000000000000000000000000000", + "4000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000000000000", + "40000000000000000000000000000000000000000000000000000000000000", + "400000000000000000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000000000000000", + "4000000000000000000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000000001", + "40000000000000000000000000000000000000000001", + "4000000000000000000000000000000000000000001", + "400000000000000000000000000000000000000001", + "40000000000000000000000000000000000000001", + "4000000000000000000000000000000000000001", + "400000000000000000000000000000000000001", + "40000000000000000000000000000000000001", + "4000000000000000000000000000000000001", + "400000000000000000000000000000000001", + "40000000000000000000000000000000001", + "4000000000000000000000000000000001", + "400000000000000000000000000000001", + "40000000000000000000000000000001", + "4000000000000000000000000000001", + "400000000000000000000000000001", + "40000000000000000000000000001", + "4000000000000000000000000001", + "400000000000000000000000001", + "40000000000000000000000001", + "4000000000000000000000001", + "400000000000000000000001", + "40000000000000000000001", + "4000000000000000000001", + "400000000000000000001", + "40000000000000000001", + "4000000000000000001", + "400000000000000001", + "40000000000000001", + "4000000000000001", + "400000000000001", + "40000000000001", + "4000000000001", + "400000000001", + "40000000001", + "4000000001", + "400000001", + "40000001", + "4000001", + "400001", + "40001", + "4001", + "401", + "41", + "5", + "7", + "7f", + "7ff", + "7fff", + "7ffff", + "7fffff", + "7ffffff", + "7fffffff", + "7ffffffff", + "7fffffffff", + "7ffffffffff", + "7fffffffffff", + "7ffffffffffff", + "7fffffffffffff", + "7ffffffffffffff", + "7fffffffffffffff", + "7ffffffffffffffff", + "7fffffffffffffffff", + "7ffffffffffffffffff", + "7fffffffffffffffffff", + "7ffffffffffffffffffff", + "7fffffffffffffffffffff", + "7ffffffffffffffffffffff", + "7fffffffffffffffffffffff", + "7ffffffffffffffffffffffff", + "7fffffffffffffffffffffffff", + "7ffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "8", + "80", + "800", + "8000", + "80000", + "800000", + "8000000", + "80000000", + "800000000", + "8000000000", + "80000000000", + "800000000000", + "8000000000000", + "80000000000000", + "800000000000000", + "8000000000000000", + "80000000000000000", + "800000000000000000", + "8000000000000000000", + "80000000000000000000", + "800000000000000000000", + "8000000000000000000000", + "80000000000000000000000", + "800000000000000000000000", + "8000000000000000000000000", + "80000000000000000000000000", + "800000000000000000000000000", + "8000000000000000000000000000", + "80000000000000000000000000000", + "800000000000000000000000000000", + "8000000000000000000000000000000", + "80000000000000000000000000000000", + "800000000000000000000000000000000", + "8000000000000000000000000000000000", + "80000000000000000000000000000000000", + "800000000000000000000000000000000000", + "8000000000000000000000000000000000000", + "80000000000000000000000000000000000000", + "800000000000000000000000000000000000000", + "8000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000000000000", + "80000000000000000000000000000000000000000000000000000000000000", + "800000000000000000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000000000000000", + "8000000000000000000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000000001", + "80000000000000000000000000000000000000000001", + "8000000000000000000000000000000000000000001", + "800000000000000000000000000000000000000001", + "80000000000000000000000000000000000000001", + "8000000000000000000000000000000000000001", + "800000000000000000000000000000000000001", + "80000000000000000000000000000000000001", + "8000000000000000000000000000000000001", + "800000000000000000000000000000000001", + "80000000000000000000000000000000001", + "8000000000000000000000000000000001", + "800000000000000000000000000000001", + "80000000000000000000000000000001", + "8000000000000000000000000000001", + "800000000000000000000000000001", + "80000000000000000000000000001", + "8000000000000000000000000001", + "800000000000000000000000001", + "80000000000000000000000001", + "8000000000000000000000001", + "800000000000000000000001", + "80000000000000000000001", + "8000000000000000000001", + "800000000000000000001", + "80000000000000000001", + "8000000000000000001", + "800000000000000001", + "80000000000000001", + "8000000000000001", + "800000000000001", + "80000000000001", + "8000000000001", + "800000000001", + "80000000001", + "8000000001", + "800000001", + "80000001", + "8000001", + "800001", + "80001", + "8001", + "801", + "81", + "9", + "f", + "ff", + "fff", + "ffff", + "fffff", + "ffffff", + "fffffff", + "ffffffff", + "fffffffff", + "ffffffffff", + "fffffffffff", + "ffffffffffff", + "fffffffffffff", + "ffffffffffffff", + "fffffffffffffff", + "ffffffffffffffff", + "fffffffffffffffff", + "ffffffffffffffffff", + "fffffffffffffffffff", + "ffffffffffffffffffff", + "fffffffffffffffffffff", + "ffffffffffffffffffffff", + "fffffffffffffffffffffff", + "ffffffffffffffffffffffff", + "fffffffffffffffffffffffff", + "ffffffffffffffffffffffffff", + "fffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +}; diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 4a39ab258..6871a63ac 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -21,54 +21,55 @@ message VarDecl { required Expression expr = 1; } -message FunctionCallNoReturnVal { - // Indexes a function that does not return anything - required uint32 func_index = 1; - required Expression in_param1 = 2; - required Expression in_param2 = 3; - required Expression in_param3 = 4; - required Expression in_param4 = 5; -} - -// Used by Expression -message FunctionCallSingleReturnVal { - // Indexes a function that returns exactly one value - required uint32 func_index = 1; - required Expression in_param1 = 2; - required Expression in_param2 = 3; - required Expression in_param3 = 4; - required Expression in_param4 = 5; -} - -message MultiVarDecl { - // Indexes a function that returns more than one value - required uint32 func_index = 1; - required Expression in_param1 = 2; - required Expression in_param2 = 3; - required Expression in_param3 = 4; - required Expression in_param4 = 5; -} - -message MultiAssignment { - // Indexes a function that returns more than one value - required uint32 func_index = 1; - required Expression in_param1 = 2; - required Expression in_param2 = 3; - required Expression in_param3 = 4; - required Expression in_param4 = 5; - required VarRef out_param1 = 6; - required VarRef out_param2 = 7; - required VarRef out_param3 = 8; - required VarRef out_param4 = 9; -} - -// We exclude function calls with single return value here and use them as expressions -message FunctionCall { - oneof functioncall_oneof { - FunctionCallNoReturnVal call_zero = 1; - MultiVarDecl call_multidecl = 2; - MultiAssignment call_multiassign = 3; +message LowLevelCall { + enum Type { + CALL = 0; + CALLCODE = 1; + DELEGATECALL = 2; + STATICCALL = 3; } + required Type callty = 1; + required Expression gas = 2; + required Expression addr = 3; + // Valid for call and callcode only + required Expression wei = 4; + required Expression in = 5; + required Expression insize = 6; + required Expression out = 7; + required Expression outsize = 8; +} + +message Create { + enum Type { + CREATE = 0; + CREATE2 = 1; + } + required Type createty = 1; + required Expression wei = 2; + required Expression position = 3; + required Expression size = 4; + // Valid for create2 only + required Expression value = 5; +} + +message FunctionCall { + enum Returns { + ZERO = 1; + SINGLE = 2; + MULTIDECL = 3; + MULTIASSIGN = 4; + } + required Returns ret = 1; + // Indexes an existing function + required uint32 func_index = 2; + required Expression in_param1 = 3; + required Expression in_param2 = 4; + required Expression in_param3 = 5; + required Expression in_param4 = 6; + required VarRef out_param1 = 7; + required VarRef out_param2 = 8; + required VarRef out_param3 = 9; + required VarRef out_param4 = 10; } message TypedVarDecl { @@ -159,11 +160,22 @@ message UnaryOp { CALLDATALOAD = 4; EXTCODESIZE = 5; EXTCODEHASH = 6; + BALANCE = 7; + BLOCKHASH = 8; } required UOp op = 1; required Expression operand = 2; } +message UnaryOpData { + enum UOpData { + SIZE = 1; + OFFSET = 2; + } + required UOpData op = 1; + required ObjectId identifier = 2; +} + message TernaryOp { enum TOp { ADDM = 0; @@ -180,6 +192,7 @@ message CopyFunc { CALLDATA = 0; CODE = 1; RETURNDATA = 2; + DATA = 3; } required CopyType ct = 1; required Expression target = 2; @@ -194,6 +207,18 @@ message ExtCodeCopy { required Expression size = 4; } +message ObjectId { + required uint64 id = 1; +} + +message DataSize { + required ObjectId identifier = 1; +} + +message DataOffset { + required ObjectId identifier = 1; +} + message NullaryOp { enum NOp { PC = 1; @@ -202,6 +227,16 @@ message NullaryOp { CALLDATASIZE = 4; CODESIZE = 5; RETURNDATASIZE = 6; + ADDRESS = 7; + ORIGIN = 8; + CALLER = 9; + CALLVALUE = 10; + GASPRICE = 11; + COINBASE = 12; + TIMESTAMP = 13; + NUMBER = 14; + DIFFICULTY = 15; + GASLIMIT = 16; } required NOp op = 1; } @@ -242,7 +277,10 @@ message Expression { UnaryOp unop = 4; TernaryOp top = 5; NullaryOp nop = 6; - FunctionCallSingleReturnVal func_expr = 7; + FunctionCall func_expr = 7; + LowLevelCall lowcall = 8; + Create create = 9; + UnaryOpData unopdata = 10; } } @@ -311,10 +349,16 @@ message TerminatingStmt { } } -// Stub for a VarDecl without an Expression on the RHS -message EmptyVarDecl {} +message FunctionDef { + required uint32 num_input_params = 1; + required uint32 num_output_params = 2; + required Block block = 3; +} + +message PopStmt { + required Expression expr = 1; +} -// TODO: Make Function definition a Statement message Statement { oneof stmt_oneof { VarDecl decl = 1; @@ -332,6 +376,8 @@ message Statement { TerminatingStmt terminatestmt = 13; FunctionCall functioncall = 14; BoundedForStmt boundedforstmt = 15; + FunctionDef funcdef = 16; + PopStmt pop = 17; } } @@ -339,39 +385,25 @@ message Block { repeated Statement statements = 1; } -// Identical to Block with the addition of an empty var right at the top -// Used by FunctionDefinitionNoReturnVal only. -message SpecialBlock { - required EmptyVarDecl var = 1; - repeated Statement statements = 2; +message Object { + required Code code = 1; + optional Data data = 2; + optional Object sub_obj = 3; } -// This ensures that proto mutator generates at least one of each type if it creates at least 1 functiondef message. -message FunctionDefinition { - required FunctionDefinitionNoReturnVal fd_zero = 1; - required FunctionDefinitionSingleReturnVal fd_one = 2; - required FunctionDefinitionMultiReturnVal fd_multi = 3; +message Code { + required Block block = 1; } -// Since this function can have 0 parameters, we hoist an empty var decl at the top via SpecialBlock. -message FunctionDefinitionNoReturnVal { - required uint32 num_input_params = 1; - required SpecialBlock statements = 2; -} - -message FunctionDefinitionSingleReturnVal { - required uint32 num_input_params = 1; - required Block statements = 2; -} - -message FunctionDefinitionMultiReturnVal { - required uint32 num_input_params = 1; - required uint32 num_output_params = 2; - required Block statements = 3; +message Data { + required string hex = 1; } message Program { - repeated FunctionDefinition funcs = 1; + oneof program_oneof { + Block block = 1; + Object obj = 2; + } } package yul.test.yul_fuzzer; diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index 4102bb4b0..41cd6edab 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -25,7 +25,9 @@ #include #include #include + #include +#include #include @@ -37,6 +39,20 @@ using namespace langutil; using namespace dev; using namespace yul::test; +namespace +{ +void printErrors(ostream& _stream, ErrorList const& _errors) +{ + SourceReferenceFormatter formatter(_stream); + + for (auto const& error: _errors) + formatter.printExceptionInformation( + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error" + ); +} +} + DEFINE_PROTO_FUZZER(Program const& _input) { ProtoConverter converter; @@ -67,7 +83,10 @@ DEFINE_PROTO_FUZZER(Program const& _input) // 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&) { diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index d060f45b6..d6011d8c7 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -63,13 +63,11 @@ u256 readZeroExtended(bytes const& _data, u256 const& _offset) /// 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. -/// Asserts the target is large enough to hold the copied segment. void copyZeroExtended( - bytes& _target, bytes const& _source, + map& _target, bytes const& _source, size_t _targetOffset, size_t _sourceOffset, size_t _size ) { - yulAssert(_targetOffset + _size <= _target.size(), ""); for (size_t i = 0; i < _size; ++i) _target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0; } @@ -176,12 +174,14 @@ u256 EVMInstructionInterpreter::eval( return u256("0x1234cafe1234cafe1234cafe") + arg[0]; uint64_t offset = uint64_t(arg[0] & uint64_t(-1)); uint64_t size = uint64_t(arg[1] & uint64_t(-1)); - return u256(keccak256(bytesConstRef(m_state.memory.data() + offset, size))); + return u256(keccak256(readMemory(offset, size))); } case Instruction::ADDRESS: return m_state.address; case Instruction::BALANCE: return m_state.balance; + case Instruction::SELFBALANCE: + return m_state.selfbalance; case Instruction::ORIGIN: return m_state.origin; case Instruction::CALLER: @@ -210,6 +210,8 @@ u256 EVMInstructionInterpreter::eval( return 0; case Instruction::GASPRICE: return m_state.gasprice; + case Instruction::CHAINID: + return m_state.chainid; case Instruction::EXTCODESIZE: return u256(keccak256(h256(arg[0]))) & 0xffffff; case Instruction::EXTCODEHASH: @@ -250,17 +252,15 @@ u256 EVMInstructionInterpreter::eval( return m_state.gaslimit; // --------------- memory / storage / logs --------------- case Instruction::MLOAD: - if (accessMemory(arg[0], 0x20)) - return u256(*reinterpret_cast(m_state.memory.data() + size_t(arg[0]))); - else - return 0x1234 + arg[0]; + accessMemory(arg[0], 0x20); + return readMemoryWord(arg[0]); case Instruction::MSTORE: - if (accessMemory(arg[0], 0x20)) - *reinterpret_cast(m_state.memory.data() + size_t(arg[0])) = h256(arg[1]); + accessMemory(arg[0], 0x20); + writeMemoryWord(arg[0], arg[1]); return 0; case Instruction::MSTORE8: - if (accessMemory(arg[0], 1)) - m_state.memory[size_t(arg[0])] = uint8_t(arg[1] & 0xff); + accessMemory(arg[0], 1); + m_state.memory[arg[0]] = uint8_t(arg[1] & 0xff); return 0; case Instruction::SLOAD: return m_state.storage[h256(arg[0])]; @@ -319,7 +319,7 @@ u256 EVMInstructionInterpreter::eval( { bytes data; if (accessMemory(arg[0], arg[1])) - data = bytesConstRef(m_state.memory.data() + size_t(arg[0]), size_t(arg[1])).toBytes(); + data = readMemory(arg[0], arg[1]); logTrace(_instruction, arg, data); throw ExplicitlyTerminated(); } @@ -455,12 +455,7 @@ bool EVMInstructionInterpreter::accessMemory(u256 const& _offset, u256 const& _s { u256 newSize = (_offset + _size + 0x1f) & ~u256(0x1f); m_state.msize = max(m_state.msize, newSize); - if (newSize < m_state.maxMemSize) - { - if (m_state.memory.size() < newSize) - m_state.memory.resize(size_t(newSize)); - return true; - } + return _size <= 0xffff; } else m_state.msize = u256(-1); @@ -468,6 +463,27 @@ bool EVMInstructionInterpreter::accessMemory(u256 const& _offset, u256 const& _s return false; } +bytes EVMInstructionInterpreter::readMemory(u256 const& _offset, u256 const& _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; +} + +u256 EVMInstructionInterpreter::readMemoryWord(u256 const& _offset) +{ + return u256(h256(readMemory(_offset, 32))); +} + +void EVMInstructionInterpreter::writeMemoryWord(u256 const& _offset, u256 const& _value) +{ + for (size_t i = 0; i < 32; i++) + m_state.memory[_offset + i] = uint8_t((_value >> (8 * (31 - i))) & 0xff); +} + + void EVMInstructionInterpreter::logTrace(dev::eth::Instruction _instruction, std::vector const& _arguments, bytes const& _data) { logTrace(dev::eth::instructionInfo(_instruction).name, _arguments, _data); diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index 0c087410a..b2545e9fa 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -75,9 +75,19 @@ public: dev::u256 evalBuiltin(BuiltinFunctionForEVM const& _fun, std::vector const& _arguments); private: - /// Resizes the memory to accommodate the memory access. - /// @returns false if memory would have to be expanded beyond m_state.maxMemSize. + /// 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(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::u256 readMemoryWord(dev::u256 const& _offset); + /// @returns writes a word to memory + /// Does not adjust msize, use @a accessMemory for that + void writeMemoryWord(dev::u256 const& _offset, dev::u256 const& _value); 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, diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 7bcd4771e..8e0eebcaa 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -47,13 +47,12 @@ void InterpreterState::dumpTraceAndState(ostream& _out) const for (auto const& line: trace) _out << " " << line << endl; _out << "Memory dump:\n"; - for (size_t i = 0; i < memory.size(); i += 0x20) - { - bytesConstRef data(memory.data() + i, 0x20); - if (boost::algorithm::all_of_equal(data, 0)) - continue; - _out << " " << std::hex << std::setw(4) << i << ": " << toHex(data.toBytes()) << endl; - } + map words; + for (auto const& [offset, value]: memory) + words[(offset / 0x20) * 0x20] |= u256(uint32_t(value)) << (256 - 8 - 8 * size_t(offset % 0x20)); + for (auto const& [offset, value]: words) + if (value != 0) + _out << " " << std::hex << std::setw(4) << offset << ": " << h256(value).hex() << endl; _out << "Storage dump:" << endl; for (auto const& slot: storage) if (slot.second != h256(0)) @@ -90,7 +89,8 @@ void Interpreter::operator()(VariableDeclaration const& _declaration) YulString varName = _declaration.variables.at(i).name; solAssert(!m_variables.count(varName), ""); m_variables[varName] = values.at(i); - m_scopes.back().insert(varName); + solAssert(!m_scopes.back().count(varName), ""); + m_scopes.back().emplace(varName, nullptr); } } @@ -164,8 +164,8 @@ void Interpreter::operator()(Block const& _block) if (statement.type() == typeid(FunctionDefinition)) { FunctionDefinition const& funDef = boost::get(statement); - m_functions[funDef.name] = &funDef; - m_scopes.back().insert(funDef.name); + solAssert(!m_scopes.back().count(funDef.name), ""); + m_scopes.back().emplace(funDef.name, &funDef); } for (auto const& statement: _block.statements) @@ -180,25 +180,23 @@ void Interpreter::operator()(Block const& _block) u256 Interpreter::evaluate(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_functions); + ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes); ev.visit(_expression); return ev.value(); } vector Interpreter::evaluateMulti(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_functions); + ExpressionEvaluator ev(m_state, m_dialect, m_variables, m_scopes); ev.visit(_expression); return ev.values(); } void Interpreter::closeScope() { - for (auto const& var: m_scopes.back()) - { - size_t erased = m_variables.erase(var) + m_functions.erase(var); - solAssert(erased == 1, ""); - } + for (auto const& [var, funDeclaration]: m_scopes.back()) + if (!funDeclaration) + solAssert(m_variables.erase(var) == 1, ""); m_scopes.pop_back(); } @@ -237,22 +235,21 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) return; } - solAssert(m_functions.count(_funCall.functionName.name), ""); - FunctionDefinition const& fun = *m_functions.at(_funCall.functionName.name); - solAssert(m_values.size() == fun.parameters.size(), ""); - map variables; - for (size_t i = 0; i < fun.parameters.size(); ++i) - variables[fun.parameters.at(i).name] = m_values.at(i); - for (size_t i = 0; i < fun.returnVariables.size(); ++i) - variables[fun.returnVariables.at(i).name] = 0; + auto [functionScopes, fun] = findFunctionAndScope(_funCall.functionName.name); - // TODO function name lookup could be a little more efficient, - // we have to copy the list here. - Interpreter interpreter(m_state, m_dialect, variables, m_functions); - interpreter(fun.body); + solAssert(fun, "Function not found."); + solAssert(m_values.size() == fun->parameters.size(), ""); + map variables; + for (size_t i = 0; i < fun->parameters.size(); ++i) + variables[fun->parameters.at(i).name] = m_values.at(i); + for (size_t i = 0; i < fun->returnVariables.size(); ++i) + variables[fun->returnVariables.at(i).name] = 0; + + Interpreter interpreter(m_state, m_dialect, variables, functionScopes); + interpreter(fun->body); m_values.clear(); - for (auto const& retVar: fun.returnVariables) + for (auto const& retVar: fun->returnVariables) m_values.emplace_back(interpreter.valueOfVariable(retVar.name)); } @@ -280,3 +277,27 @@ void ExpressionEvaluator::evaluateArgs(vector const& _expr) m_values = std::move(values); std::reverse(m_values.begin(), m_values.end()); } + +pair< + vector>, + FunctionDefinition const* +> ExpressionEvaluator::findFunctionAndScope(YulString _functionName) const +{ + FunctionDefinition const* fun = nullptr; + std::vector> newScopes; + for (auto const& scope: m_scopes) + { + // Copy over all functions. + newScopes.push_back({}); + for (auto const& [name, funDef]: scope) + if (funDef) + newScopes.back().emplace(name, funDef); + // Stop at the called function. + if (scope.count(_functionName)) + { + fun = scope.at(_functionName); + break; + } + } + return {move(newScopes), fun}; +} diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 6fc20280a..04688a1ae 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -64,13 +64,13 @@ struct InterpreterState { dev::bytes calldata; dev::bytes returndata; - /// TODO turn this into "vector with holes" for the randomized testing - dev::bytes memory; + std::map memory; /// This is different than memory.size() because we ignore gas. dev::u256 msize; std::map storage; dev::u160 address = 0x11111111; dev::u256 balance = 0x22222222; + dev::u256 selfbalance = 0x22223333; dev::u160 origin = 0x33333333; dev::u160 caller = 0x44444444; dev::u256 callvalue = 0x55555555; @@ -82,13 +82,11 @@ struct InterpreterState dev::u256 blockNumber = 1024; dev::u256 difficulty = 0x9999999; dev::u256 gaslimit = 4000000; + dev::u256 chainid = 0x01; /// Log of changes / effects. Sholud be structured data in the future. std::vector trace; /// This is actually an input parameter that more or less limits the runtime. size_t maxTraceSize = 0; - /// Memory size limit. Anything beyond this will still work, but it has - /// deterministic yet not necessarily consistent behaviour. - size_t maxMemSize = 0x200; size_t maxSteps = 0; size_t numSteps = 0; LoopState loopState = LoopState::Default; @@ -106,12 +104,12 @@ public: InterpreterState& _state, Dialect const& _dialect, std::map _variables = {}, - std::map _functions = {} + std::vector> _scopes = {} ): m_dialect(_dialect), m_state(_state), m_variables(std::move(_variables)), - m_functions(std::move(_functions)) + m_scopes(std::move(_scopes)) {} void operator()(ExpressionStatement const& _statement) override; @@ -136,17 +134,17 @@ private: std::vector evaluateMulti(Expression const& _expression); void openScope() { m_scopes.push_back({}); } - /// Unregisters variables. + /// Unregisters variables and functions. void closeScope(); Dialect const& m_dialect; InterpreterState& m_state; /// Values of variables. std::map m_variables; - /// Meanings of functions. - std::map m_functions; - /// Scopes of variables and functions, used to clear them at end of blocks. - std::vector> m_scopes; + /// Scopes of variables and functions. Used for lookup, clearing at end of blocks + /// and passing over the visible functions across function calls. + /// The pointer is nullptr if and only if the key is a variable. + std::vector> m_scopes; }; /** @@ -159,12 +157,12 @@ public: InterpreterState& _state, Dialect const& _dialect, std::map const& _variables, - std::map const& _functions + std::vector> const& _scopes ): m_state(_state), m_dialect(_dialect), m_variables(_variables), - m_functions(_functions) + m_scopes(_scopes) {} void operator()(Literal const&) override; @@ -184,12 +182,19 @@ private: /// stores it in m_value. void evaluateArgs(std::vector const& _expr); + /// Finds the function called @a _functionName in the current scope stack and returns + /// the function's scope stack (with variables removed) and definition. + std::pair< + std::vector>, + FunctionDefinition const* + > findFunctionAndScope(YulString _functionName) const; + InterpreterState& m_state; Dialect const& m_dialect; /// Values of variables. std::map const& m_variables; - /// Meanings of functions. - std::map const& m_functions; + /// Stack of scopes in the current context. + std::vector> const& m_scopes; /// Current value of the expression std::vector m_values; }; diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 2729c4577..7d0b1d952 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -49,13 +51,16 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include +#include #include @@ -122,90 +127,99 @@ public: cout << source << endl; if (!parse(source)) return; + set reservedIdentifiers; if (!disambiguated) { *m_ast = boost::get(Disambiguator(m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); - m_nameDispenser = make_shared(m_dialect, *m_ast); + m_nameDispenser = make_shared(m_dialect, *m_ast, reservedIdentifiers); disambiguated = true; } cout << "(q)quit/(f)flatten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; 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/f(O)r-loop-condition-into-body/" << endl; - cout << " 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/? " << 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.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; + + OptimiserStepContext context{m_dialect, *m_nameDispenser, reservedIdentifiers}; switch (option) { case 'q': return; case 'f': - BlockFlattener{}(*m_ast); + BlockFlattener::run(context, *m_ast); break; case 'o': - ForLoopInitRewriter{}(*m_ast); + ForLoopInitRewriter::run(context, *m_ast); break; case 'O': - ForLoopConditionIntoBody{}(*m_ast); + ForLoopConditionOutOfBody::run(context, *m_ast); + break; + case 'I': + ForLoopConditionIntoBody::run(context, *m_ast); break; case 'c': - (CommonSubexpressionEliminator{m_dialect})(*m_ast); + CommonSubexpressionEliminator::run(context, *m_ast); break; case 'd': - (VarDeclInitializer{})(*m_ast); + VarDeclInitializer::run(context, *m_ast); break; case 'l': - VarNameCleaner{*m_ast, m_dialect}(*m_ast); + VarNameCleaner::run(context, *m_ast); break; case 'x': - ExpressionSplitter{m_dialect, *m_nameDispenser}(*m_ast); + ExpressionSplitter::run(context, *m_ast); break; case 'j': - ExpressionJoiner::run(*m_ast); + ExpressionJoiner::run(context, *m_ast); break; case 'g': - (FunctionGrouper{})(*m_ast); + FunctionGrouper::run(context, *m_ast); break; case 'h': - (FunctionHoister{})(*m_ast); + FunctionHoister::run(context, *m_ast); break; case 'e': - ExpressionInliner{m_dialect, *m_ast}.run(); + ExpressionInliner::run(context, *m_ast); break; case 'i': - FullInliner(*m_ast, *m_nameDispenser).run(); + FullInliner::run(context, *m_ast); break; case 's': - ExpressionSimplifier::run(m_dialect, *m_ast); + ExpressionSimplifier::run(context, *m_ast); break; case 't': - (StructuralSimplifier{m_dialect})(*m_ast); + StructuralSimplifier::run(context, *m_ast); + break; + case 'T': + LiteralRematerialiser::run(context, *m_ast); break; case 'n': - (ControlFlowSimplifier{m_dialect})(*m_ast); + ControlFlowSimplifier::run(context, *m_ast); break; case 'u': - UnusedPruner::runUntilStabilised(m_dialect, *m_ast); + UnusedPruner::run(context, *m_ast); break; case 'D': - DeadCodeEliminator{m_dialect}(*m_ast); + DeadCodeEliminator::run(context, *m_ast); break; case 'a': - SSATransform::run(*m_ast, *m_nameDispenser); + SSATransform::run(context, *m_ast); break; case 'r': - RedundantAssignEliminator::run(m_dialect, *m_ast); + RedundantAssignEliminator::run(context, *m_ast); break; case 'm': - Rematerialiser::run(m_dialect, *m_ast); + Rematerialiser::run(context, *m_ast); break; case 'v': - EquivalentFunctionCombiner::run(*m_ast); + EquivalentFunctionCombiner::run(context, *m_ast); break; case 'V': - SSAReverser::run(*m_ast); + SSAReverser::run(context, *m_ast); break; case 'p': { @@ -214,6 +228,9 @@ public: StackCompressor::run(m_dialect, obj, true, 16); break; } + case 'L': + LoadResolver::run(context, *m_ast); + break; default: cout << "Unknown option." << endl; } diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index fb544231f..1f213fbd5 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -87,7 +87,6 @@ void interpret(string const& _source) InterpreterState state; state.maxTraceSize = 10000; - state.maxMemSize = 0x20000000; Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); Interpreter interpreter(state, dialect); try