diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 000000000..8c411c7b1 --- /dev/null +++ b/.circleci/README.md @@ -0,0 +1,21 @@ +## CircleCI integration + +### Docker images + +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/solc-buildpack-deps:archlinux -f Dockerfile.archlinux . +docker push solidity/solc-buildpack-deps:archlinux +``` + +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. diff --git a/.circleci/config.yml b/.circleci/config.yml index 3ea21ff0d..d88cb0269 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,48 +1,72 @@ +# vim:ts=2:sw=2:et +# -------------------------------------------------------------------------- +# Prefixes used in order to keep CircleCI workflow overview more readable: +# - b: build +# - t: test +# - ubu: ubuntu +# - ems: Emscripten +version: 2 + defaults: - # The default for tags is to not run, so we have to explicitly match a filter. - - build_on_tags: &build_on_tags - filters: - tags: - only: /.*/ + + # -------------------------------------------------------------------------- + # Build Templates + - setup_prerelease_commit_hash: &setup_prerelease_commit_hash name: Store commit hash and prerelease command: | if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi echo -n "$CIRCLE_SHA1" > commit_hash.txt + - run_build: &run_build 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 + echo -n "$CIRCLE_SHA1" > commit_hash.txt mkdir -p build cd build [ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON" - cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS -G "Unix Makefiles" make -j4 + - run_build_ossfuzz: &run_build_ossfuzz name: Build_ossfuzz command: | mkdir -p build cd build - /src/LPM/external.protobuf/bin/protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz - cmake .. -DCMAKE_BUILD_TYPE=Release $CMAKE_OPTIONS + protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS make ossfuzz ossfuzz_proto -j4 - - run_tests: &run_tests - name: Tests - command: scripts/tests.sh --junit_report test_results - - run_regressions: &run_regressions - name: Regression tests - command: | - 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 - - solc_artifact: &solc_artifact + + - run_proofs: &run_proofs + name: Correctness proofs for optimization rules + command: scripts/run_proofs.sh + + # -------------------------------------------------------------------------- + # Artifacts Templates + + # the whole build directory + - artifacts_build_dir: &artifacts_build_dir + root: build + paths: + - "*" + + # compiled solc executable target + - artifacts_solc: &artifacts_solc path: build/solc/solc destination: solc - - all_artifacts: &all_artifacts + + # compiled executable targets + - artifacts_executables: &artifacts_executables root: build paths: - solc/solc - test/soltest - test/tools/solfuzzer - - ossfuzz_artifacts: &ossfuzz_artifacts + + # compiled OSSFUZZ targets + - artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz root: build paths: - test/tools/ossfuzz/const_opt_ossfuzz @@ -54,9 +78,280 @@ defaults: - test/tools/ossfuzz/yul_proto_diff_ossfuzz - test/tools/ossfuzz/yul_proto_ossfuzz -version: 2 + # test result output directory + - artifacts_test_results: &artifacts_test_results + path: test_results/ + destination: test_results/ + + # -------------------------------------------------------------------------- + # Tests Templates + + # store_test_results helper + - store_test_results: &store_test_results + path: test_results/ + + - run_soltest: &run_soltest + name: soltest + command: ./.circleci/soltest.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 + + - test_asan: &test_asan + <<: *test_ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: + <<: *run_soltest + no_output_timeout: 30m + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + # -------------------------------------------------------------------------- + # Workflow Templates + + - workflow_trigger_on_tags: &workflow_trigger_on_tags + filters: + tags: + only: /.*/ + + - workflow_ubuntu1904: &workflow_ubuntu1904 + <<: *workflow_trigger_on_tags + requires: + - b_ubu + + - workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov + <<: *workflow_trigger_on_tags + requires: + - b_ubu_codecov + + - workflow_osx: &workflow_osx + <<: *workflow_trigger_on_tags + requires: + - b_osx + + - workflow_ubuntu1904_asan: &workflow_ubuntu1904_asan + <<: *workflow_trigger_on_tags + requires: + - b_ubu_asan + + - workflow_emscripten: &workflow_emscripten + <<: *workflow_trigger_on_tags + requires: + - b_ems + + - workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz + <<: *workflow_trigger_on_tags + requires: + - b_ubu_ossfuzz + +# ----------------------------------------------------------------------------------------------- jobs: - build_emscripten: + + chk_spelling: + docker: + - image: circleci/python:3.6 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Install dependencies + command: | + pip install --user codespell + - run: + name: Check spelling + command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt + + chk_coding_style: + docker: + - image: buildpack-deps:disco + steps: + - checkout + - run: + name: Check for C++ coding style + command: ./scripts/check_style.sh + + chk_buglist: + docker: + - image: circleci/node + environment: + TERM: xterm + steps: + - checkout + - run: + name: JS deps + command: | + npm install download + npm install JSONPath + npm install mktemp + - run: + name: Test buglist + command: ./test/buglistTests.js + + chk_proofs: + docker: + - image: buildpack-deps:disco + environment: + TERM: xterm + steps: + - checkout + - run: + name: Z3 python deps + command: | + apt-get -qq update + apt-get -qy install python-pip + pip install --user z3-solver + - run: *run_proofs + + b_ubu: &build_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_ubu_codecov: + <<: *build_ubuntu1904 + environment: + COVERAGE: ON + CMAKE_BUILD_TYPE: Debug + steps: + - checkout + - run: *run_build + - persist_to_workspace: *artifacts_build_dir + + t_ubu_codecov: + <<: *test_ubuntu1904 + environment: + EVM: constantinople + OPTIMIZE: 1 + steps: + - checkout + - attach_workspace: + at: build + - run: + name: "soltest: Syntax Tests" + command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test + - run: + name: "Code Coverage: Syntax Tests" + command: codecov --flags syntax --gcov-root build + - run: *run_soltest + - run: + name: "Coverage: All" + 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. + # Do *NOT* store any artifacts or workspace as we don't run tests on this build. + b_ubu_cxx17: + <<: *build_ubuntu1904 + environment: + CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake -DUSE_CVC4=OFF + steps: + - checkout + - run: *run_build + + b_ubu_ossfuzz: + <<: *build_ubuntu1904 + environment: + TERM: xterm + CC: /usr/bin/clang-8 + CXX: /usr/bin/clang++-8 + CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake + steps: + - checkout + - run: *setup_prerelease_commit_hash + - run: *run_build_ossfuzz + - persist_to_workspace: *artifacts_executables_ossfuzz + + t_ubu_ossfuzz: &t_ubu_ossfuzz + <<: *test_ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Regression tests + command: | + 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 + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + b_archlinux: + docker: + - image: ethereum/solidity-buildpack-deps:archlinux + environment: + TERM: xterm + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_osx: + macos: + xcode: "10.0.0" + environment: + TERM: xterm + CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DLLL=ON + steps: + - checkout + - run: + name: Install build dependencies + command: | + brew unlink python + brew install z3 + brew install boost + brew install cmake + brew install wget + ./scripts/install_obsolete_jsoncpp_1_7_4.sh + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + t_osx_cli: + macos: + xcode: "10.0.0" + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: + name: Install dependencies + command: | + brew unlink python + brew install z3 + - run: *run_cmdline_tests + - store_artifacts: *artifacts_test_results + + b_ems: docker: - image: trzeci/emscripten:sdk-tag-1.38.22-64bit environment: @@ -78,7 +373,7 @@ jobs: name: Save Boost build key: *boost-cache-key paths: - - boost_1_68_0 + - boost_1_70_0_install - store_artifacts: path: emscripten_build/libsolc/soljson.js destination: soljson.js @@ -91,7 +386,123 @@ jobs: - soljson.js - version.txt - test_emscripten_solcjs: + # x64 ASAN build, for testing for memory related bugs + b_ubu_asan: &b_ubu_asan + <<: *build_ubuntu1904 + environment: + CMAKE_OPTIONS: -DSANITIZE=address + CMAKE_BUILD_TYPE: Release + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_docs: + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - run: *setup_prerelease_commit_hash + - run: + name: Build documentation + command: ./scripts/docs.sh + - store_artifacts: + path: docs/_build/html/ + destination: docs-html + + t_ubu_cli: &t_ubu_cli + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: build + - run: *run_cmdline_tests + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + t_ubu_asan_cli: + <<: *t_ubu_cli + environment: + TERM: xterm + 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 + steps: + - checkout + - attach_workspace: + at: build + - run: + <<: *run_cmdline_tests + no_output_timeout: 30m + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + t_ubu_asan_constantinople: + <<: *test_asan + environment: + EVM: constantinople + OPTIMIZE: 0 + SOLTEST_IPC: 0 + 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 environment: @@ -101,16 +512,13 @@ jobs: - attach_workspace: at: /tmp/workspace - run: - name: Install external tests deps + name: Test solcjs command: | node --version npm --version - - run: - name: Test solcjs - command: | test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) - test_emscripten_external_gnosis: + t_ems_external_gnosis: docker: - image: circleci/node:10 environment: @@ -124,7 +532,7 @@ jobs: command: | test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js - test_emscripten_external_zeppelin: + t_ems_external_zeppelin: docker: - image: circleci/node:10 environment: @@ -138,7 +546,7 @@ jobs: command: | test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js - test_emscripten_external_colony: + t_ems_external_colony: docker: - image: circleci/node:10 environment: @@ -156,370 +564,51 @@ jobs: command: | test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js - build_x86_linux: - docker: - - image: buildpack-deps:bionic - environment: - TERM: xterm - COVERAGE: "ON" - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - "*" - - build_x86_linux_cxx17: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - build_x86_archlinux: - docker: - - image: archlinux/base - environment: - TERM: xterm - steps: - - run: - name: Install build dependencies - command: | - pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar - - checkout - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - solc/solc - - test/soltest - - test/tools/solfuzzer - - build_x86_clang7_asan: - docker: - - image: buildpack-deps:cosmic - environment: - TERM: xterm - CC: /usr/bin/clang-7 - CXX: /usr/bin/clang++-7 - CMAKE_OPTIONS: -DSANITIZE=address -DCMAKE_BUILD_TYPE=Debug - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libjsoncpp-dev=1.7.4-\* - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - solc/solc - - test/soltest - - test/tools/solfuzzer - - build_x86_mac: - macos: - xcode: "10.0.0" - environment: - TERM: xterm - CMAKE_OPTIONS: -DLLL=ON - steps: - - checkout - - run: - name: Install build dependencies - command: | - brew unlink python - brew install z3 - brew install boost - brew install cmake - brew install wget - ./scripts/install_obsolete_jsoncpp_1_7_4.sh - - run: *setup_prerelease_commit_hash - - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: *all_artifacts - - test_check_spelling: - docker: - - image: circleci/python:3.6 - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - pip install --user codespell - - run: - name: Check spelling - command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt - - test_check_style: - docker: - - image: buildpack-deps:bionic - steps: - - checkout - - run: - name: Check for trailing whitespace - command: ./scripts/check_style.sh - - test_buglist: - docker: - - image: circleci/node - environment: - TERM: xterm - steps: - - checkout - - run: - name: JS deps - command: | - npm install download - npm install JSONPath - npm install mktemp - - run: - name: Test buglist - command: ./test/buglistTests.js - - test_x86_linux: - docker: - - image: buildpack-deps:bionic - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install libcvc4-dev libleveldb1v5 python-pip - pip install codecov - - run: mkdir -p test_results - - run: - name: Test type checker - command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test - - run: - name: Coverage of type checker - command: codecov --flags syntax --gcov-root build - - run: *run_tests - - run: - name: Coverage of all - command: codecov --flags all --gcov-root build - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_clang7_asan: - docker: - - image: buildpack-deps:cosmic - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install llvm-7-dev libcvc4-dev libleveldb1v5 python-pip - # This is needed to resolve the symbols. Since we're using clang7 in the build, we must use the appropriate symbolizer. - update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1 - - run: mkdir -p test_results - - run: - name: Run soltest with ASAN - command: | - ulimit -a - # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). - ulimit -s 16384 - 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" - build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test - - run: - name: Run commandline tests with ASAN - command: | - ulimit -a - # Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests). - ulimit -s 16384 - 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" - test/cmdlineTests.sh - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_archlinux: - docker: - - image: archlinux/base - environment: - TERM: xterm - steps: - - run: - name: Install dependencies - command: | - pacman --noconfirm -Syu --noprogressbar --needed boost z3 cvc4 git openssh tar - - checkout - - attach_workspace: - at: build - - run: mkdir -p test_results - - run: build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - test_x86_mac: - macos: - xcode: "10.0.0" - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - brew unlink python - brew install z3 - - run: mkdir -p test_results - - run: *run_tests - - store_test_results: - path: test_results/ - - store_artifacts: - path: test_results/ - destination: test_results/ - - docs: - docker: - - image: buildpack-deps:bionic - environment: - DEBIAN_FRONTEND: noninteractive - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install python-sphinx python-pip - - run: *setup_prerelease_commit_hash - - run: - name: Build documentation - command: ./scripts/docs.sh - - store_artifacts: - path: docs/_build/html/ - destination: docs-html - - build_x86_linux_ossfuzz: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - CC: /usr/bin/clang-8 - CXX: /usr/bin/clang++-8 - CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake - steps: - - checkout - - run: - name: Install build dependencies - command: | - apt-get -qq update - apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\* - ./scripts/install_lpm.sh - ./scripts/install_libfuzzer.sh - # Install evmone and dependencies (intx and ethash) - ./scripts/install_evmone.sh - - run: *setup_prerelease_commit_hash - - run: *run_build_ossfuzz - - persist_to_workspace: *ossfuzz_artifacts - - test_x86_ossfuzz_regression: - docker: - - image: buildpack-deps:disco - environment: - TERM: xterm - steps: - - checkout - - attach_workspace: - at: build - - run: - name: Install dependencies - command: | - apt-get -qq update - apt-get -qy install libcvc4-dev llvm-8-dev - ./scripts/download_ossfuzz_corpus.sh - update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1 - - run: mkdir -p test_results - - run: *run_regressions - - store_artifacts: - path: test_results/ - destination: test_results/ - workflows: version: 2 - build_all: - jobs: - - test_check_spelling: *build_on_tags - - test_check_style: *build_on_tags - - test_buglist: *build_on_tags - - build_emscripten: *build_on_tags - - test_emscripten_solcjs: - <<: *build_on_tags - requires: - - build_emscripten - - build_x86_linux: *build_on_tags - - build_x86_linux_cxx17: *build_on_tags - - build_x86_clang7_asan: *build_on_tags - - build_x86_mac: *build_on_tags - - test_x86_linux: - <<: *build_on_tags - requires: - - build_x86_linux - - test_x86_clang7_asan: - <<: *build_on_tags - requires: - - build_x86_clang7_asan - - test_x86_mac: - <<: *build_on_tags - requires: - - build_x86_mac - - docs: *build_on_tags - - build_x86_archlinux: *build_on_tags - - test_x86_archlinux: - <<: *build_on_tags - requires: - - build_x86_archlinux - - build_x86_linux_ossfuzz: *build_on_tags - test_nightly: + main: + jobs: + # basic checks + - chk_spelling: *workflow_trigger_on_tags + - chk_coding_style: *workflow_trigger_on_tags + - chk_buglist: *workflow_trigger_on_tags + - chk_proofs: *workflow_trigger_on_tags + + # build-only + - b_docs: *workflow_trigger_on_tags + - b_archlinux: *workflow_trigger_on_tags + - b_ubu_cxx17: *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 + - b_ubu: *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 + + # ASan build and tests + - b_ubu_asan: *workflow_trigger_on_tags + - t_ubu_asan_constantinople: *workflow_ubuntu1904_asan + - t_ubu_asan_cli: *workflow_ubuntu1904_asan + + # Emscripten build and selected tests + - b_ems: *workflow_trigger_on_tags + - t_ems_solcjs: *workflow_emscripten + + nightly: + triggers: - schedule: cron: "0 0 * * *" @@ -527,23 +616,19 @@ workflows: branches: only: - develop - jobs: - - build_emscripten: *build_on_tags - - test_emscripten_external_zeppelin: - <<: *build_on_tags - requires: - - build_emscripten - - test_emscripten_external_gnosis: - <<: *build_on_tags - requires: - - build_emscripten - - test_emscripten_external_colony: - <<: *build_on_tags - requires: - - build_emscripten - - build_x86_linux_ossfuzz: *build_on_tags - - test_x86_ossfuzz_regression: - <<: *build_on_tags - requires: - - build_x86_linux_ossfuzz + + jobs: + # Emscripten builds and external tests + - b_ems: *workflow_trigger_on_tags + - t_ems_external_zeppelin: *workflow_emscripten + - t_ems_external_gnosis: *workflow_emscripten + - t_ems_external_colony: *workflow_emscripten + + # OSSFUZZ builds and (regression) tests + - b_ubu_ossfuzz: *workflow_trigger_on_tags + - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz + + # Code Coverage enabled build and tests + - b_ubu_codecov: *workflow_trigger_on_tags + - t_ubu_codecov: *workflow_ubuntu1904_codecov diff --git a/.circleci/docker/Dockerfile.archlinux b/.circleci/docker/Dockerfile.archlinux new file mode 100644 index 000000000..cd956c9cd --- /dev/null +++ b/.circleci/docker/Dockerfile.archlinux @@ -0,0 +1,28 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Arch Linux +# 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 archlinux/base + +RUN pacman --noconfirm -Syu --noprogressbar --needed \ + base-devel boost cmake z3 cvc4 git openssh tar + diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 new file mode 100644 index 000000000..bad21f664 --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -0,0 +1,124 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) +# 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 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + 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 libcvc4-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; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +# Aleth for end-to-end tests +ARG ALETH_VERSION="1.6.0" +ARG ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" +ARG ALETH_URL="https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz" +RUN set -ex; \ + wget -q -O /tmp/aleth.tar.gz "${ALETH_URL}"; \ + test "$(shasum /tmp/aleth.tar.gz)" = "$ALETH_HASH /tmp/aleth.tar.gz"; \ + tar -xf /tmp/aleth.tar.gz -C /usr + +# 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: LPM package (do not remove build dirs as solidity compiles/links against that dir) +RUN set -ex; \ + mkdir /src; \ + cd /src; \ + git clone https://github.com/google/libprotobuf-mutator.git; \ + cd libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir ../LPM; \ + cd ../LPM; \ + cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release; \ + 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 + +# 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 +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.1.0" --recurse-submodules https://github.com/chfast/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh new file mode 100755 index 000000000..673540b3c --- /dev/null +++ b/.circleci/soltest.sh @@ -0,0 +1,65 @@ +#! /bin/bash +#------------------------------------------------------------------------------ +# Bash script to execute the Solidity tests by CircleCI. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# Configuration Environment Variables: +# +# EVM=version_string Specifies EVM version to compile for (such as homestead, etc) +# OPTIMIZE=1 Enables backend optimizer +# ABI_ENCODER_V2=1 Enables ABI encoder version 2 +# +# ------------------------------------------------------------------------------ +# 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 + +OPTIMIZE=${OPTIMIZE:-"0"} +EVM=${EVM:-"invalid"} +WORKDIR=${CIRCLE_WORKING_DIRECTORY:-.} +SOLTEST_IPC=${SOLTEST_IPC:-1} +REPODIR="$(realpath $(dirname $0)/..)" +ALETH_PATH="/usr/bin/aleth" + +source "${REPODIR}/scripts/common.sh" +# Test result output directory (CircleCI is reading test results from here) +mkdir -p test_results + +ALETH_PID=$(run_aleth) + +function cleanup() { + safe_kill $ALETH_PID $ALETH_PATH +} +trap cleanup INT TERM + +# 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" +SOLTEST_ARGS="--evm-version=$EVM --ipcpath "${WORKDIR}/geth.ipc" $flags" +test "${SOLTEST_IPC}" = "1" || SOLTEST_ARGS="$SOLTEST_ARGS --no-ipc" +test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize" +test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul" + +echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS}" + +${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS} diff --git a/.travis.yml b/.travis.yml index c91daada1..7e1114974 100644 --- a/.travis.yml +++ b/.travis.yml @@ -183,7 +183,7 @@ git: cache: ccache: true directories: - - boost_1_68_0 + - boost_1_70_0_install - $HOME/.local install: diff --git a/CMakeLists.txt b/CMakeLists.txt index bfe35fcf5..40a81c03a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.5.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,9 +10,20 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.9") +set(PROJECT_VERSION "0.5.10") project(solidity VERSION ${PROJECT_VERSION} LANGUAGES 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) + message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.") +endif() + option(LLL "Build LLL" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) diff --git a/Changelog.md b/Changelog.md index 4ccefe18d..63879d665 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,29 @@ +### 0.5.10 (2019-06-25) + +Important Bugfixes: + * ABIEncoderV2: Fix incorrect abi encoding of storage array of data type that occupy multiple storage slots + * Code Generator: Properly zero out higher order bits in elements of an array of negative numbers when assigning to storage and converting the type at the same time. + + +Compiler Features: + * Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch. + * Optimizer: Add rule to simplify ``SUB(~0, X)`` to ``NOT(X)``. + * Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm. + + +Bugfixes: + * Type Checker: Set state mutability of the function type members ``gas`` and ``value`` to pure (while their return type inherits state mutability from the function type). + * Yul / Inline Assembly Parser: Disallow trailing commas in function call arguments. + + +Build System: + * Attempt to use stock Z3 cmake files to find Z3 and only fall back to manual discovery. + * CMake: use imported targets for boost. + * Emscripten build: upgrade to boost 1.70. + * Generate a cmake error for gcc versions older than 5.0. + + + ### 0.5.9 (2019-05-28) Language Features: @@ -21,7 +47,6 @@ Compiler Features: * Yul Optimizer: Do not inline recursive functions. * Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used. - Bugfixes: * Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage. * Code Generator: Fix assertion failure when assigning structs containing array of mapping. @@ -61,6 +86,7 @@ Compiler Features: * Yul: Adds break and continue keywords to for-loop syntax. * Yul: Support ``.`` as part of identifiers. * Yul Optimizer: Adds steps for detecting and removing of dead code. + * Yul Code Generator: Directly jump over a series of function definitions (instead of jumping over each one) Bugfixes: diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 3fe98188d..7ffb45964 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -41,11 +41,11 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Additional GCC-specific compiler settings. if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - # Check that we've got GCC 4.7 or newer. + # Check that we've got GCC 5.0 or newer. execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.7 or greater.") + if (NOT (GCC_VERSION VERSION_GREATER 5.0 OR GCC_VERSION VERSION_EQUAL 5.0)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.") endif () # Additional Clang-specific compiler settings. diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 477a604d6..53319e6c8 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -1,21 +1,6 @@ # all dependencies that are not directly included in the cpp-ethereum distribution are defined here # for this to work, download the dependency via the cmake script in extdep or install them manually! -function(eth_show_dependency DEP NAME) - get_property(DISPLAYED GLOBAL PROPERTY ETH_${DEP}_DISPLAYED) - if (NOT DISPLAYED) - set_property(GLOBAL PROPERTY ETH_${DEP}_DISPLAYED TRUE) - if (NOT("${${DEP}_VERSION}" STREQUAL "")) - message(STATUS "${NAME} version: ${${DEP}_VERSION}") - endif() - message(STATUS "${NAME} headers: ${${DEP}_INCLUDE_DIRS}") - message(STATUS "${NAME} lib : ${${DEP}_LIBRARIES}") - if (NOT("${${DEP}_DLLS}" STREQUAL "")) - message(STATUS "${NAME} dll : ${${DEP}_DLLS}") - endif() - endif() -endfunction() - if (DEFINED MSVC) # by defining CMAKE_PREFIX_PATH variable, cmake will look for dependencies first in our own repository before looking in system paths like /usr/local/ ... # this must be set to point to the same directory as $ETH_DEPENDENCY_INSTALL_DIR in /extdep directory @@ -41,6 +26,29 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts) set(Boost_USE_MULTITHREADED ON) option(Boost_USE_STATIC_LIBS "Link Boost statically" ON) -find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) +set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;system") -eth_show_dependency(Boost boost) +find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS ${BOOST_COMPONENTS}) + +# If cmake is older than boost and boost is older than 1.70, +# find_package does not define imported targets, so we have to +# define them manually. + +if (NOT TARGET Boost::boost) # header only target + add_library(Boost::boost INTERFACE IMPORTED) + set_property(TARGET Boost::boost APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) +endif() +get_property(LOCATION TARGET Boost::boost PROPERTY INTERFACE_INCLUDE_DIRECTORIES) +message(STATUS "Found Boost headers in ${LOCATION}") + +foreach (BOOST_COMPONENT IN LISTS BOOST_COMPONENTS) + if (NOT TARGET Boost::${BOOST_COMPONENT}) + add_library(Boost::${BOOST_COMPONENT} UNKNOWN IMPORTED) + string(TOUPPER ${BOOST_COMPONENT} BOOST_COMPONENT_UPPER) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARY}) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_LINK_LIBRARIES ${Boost_${BOOST_COMPONENT_UPPER}_LIBRARIES}) + set_property(TARGET Boost::${BOOST_COMPONENT} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Boost_INCLUDE_DIRS}) + endif() + get_property(LOCATION TARGET Boost::${BOOST_COMPONENT} PROPERTY IMPORTED_LOCATION) + message(STATUS "Found Boost::${BOOST_COMPONENT} at ${LOCATION}") +endforeach() diff --git a/cmake/FindZ3.cmake b/cmake/FindZ3.cmake index bdd8ce72f..4b63ed4cb 100644 --- a/cmake/FindZ3.cmake +++ b/cmake/FindZ3.cmake @@ -1,29 +1,45 @@ if (USE_Z3) - find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES 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) - - string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" - Z3_VERSION_STRING "${libz3_version_str}") - unset(libz3_version_str) - endif() - mark_as_advanced(Z3_VERSION_STRING z3_DIR) + # Save and clear Z3_FIND_VERSION, since the + # Z3 config module cannot handle version requirements. + set(Z3_FIND_VERSION_ORIG ${Z3_FIND_VERSION}) + set(Z3_FIND_VERSION) + # Try to find Z3 using its stock cmake files. + find_package(Z3 QUIET CONFIG) + # Restore Z3_FIND_VERSION for find_package_handle_standard_args. + set(Z3_FIND_VERSION ${Z3_FIND_VERSION_ORIG}) + set(Z3_FIND_VERSION_ORIG) include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Z3 - REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR - VERSION_VAR Z3_VERSION_STRING) - if (NOT TARGET Z3::Z3) - add_library(Z3::Z3 UNKNOWN IMPORTED) - set_property(TARGET Z3::Z3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY}) - set_property(TARGET Z3::Z3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR}) + if (Z3_FOUND) + set(Z3_VERSION ${Z3_VERSION_STRING}) + find_package_handle_standard_args(Z3 CONFIG_MODE) + else() + find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES 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) + + string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" + Z3_VERSION_STRING "${libz3_version_str}") + unset(libz3_version_str) + endif() + mark_as_advanced(Z3_VERSION_STRING z3_DIR) + + find_package_handle_standard_args(Z3 + REQUIRED_VARS Z3_LIBRARY Z3_INCLUDE_DIR + VERSION_VAR Z3_VERSION_STRING) + + if (NOT TARGET z3::libz3) + add_library(z3::libz3 UNKNOWN IMPORTED) + set_property(TARGET z3::libz3 PROPERTY IMPORTED_LOCATION ${Z3_LIBRARY}) + set_property(TARGET z3::libz3 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${Z3_INCLUDE_DIR}) + endif() endif() else() set(Z3_FOUND FALSE) diff --git a/docs/assembly.rst b/docs/assembly.rst index 355286d49..5015a18bb 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -415,7 +415,8 @@ Local Solidity variables are available for assignments, for example: To be safe, always clear the data properly before you use it in a context where this is important: ``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }`` - To clean signed types, you can use the ``signextend`` opcode. + To clean signed types, you can use the ``signextend`` opcode: + ``assembly { signextend(, x) }`` Labels ------ @@ -595,21 +596,21 @@ of their block is reached. Conventions in Solidity ----------------------- -In contrast to EVM assembly, Solidity knows types which are narrower than 256 bits, -e.g. ``uint24``. In order to make them more efficient, most arithmetic operations just -treat them as 256-bit numbers and the higher-order bits are only cleaned at the -point where it is necessary, i.e. just shortly before they are written to memory -or before comparisons are performed. This means that if you access such a variable -from within inline assembly, you might have to manually clean the higher order bits +In contrast to EVM assembly, Solidity has types which are narrower than 256 bits, +e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256 +bits, and the higher-order bits are cleaned when necessary, +i.e., shortly before they are written to memory or before comparisons are performed. +This means that if you access such a variable +from within inline assembly, you might have to manually clean the higher-order bits first. -Solidity manages memory in a very simple way: There is a "free memory pointer" -at position ``0x40`` in memory. If you want to allocate memory, just use the memory -starting from where this pointer points at and update it accordingly. +Solidity manages memory in the following way. There is a "free memory pointer" +at position ``0x40`` in memory. If you want to allocate memory, use the memory +starting from where this pointer points at and update it. There is no guarantee that the memory has not been used before and thus you cannot assume that its contents are zero bytes. There is no built-in mechanism to release or free allocated memory. -Here is an assembly snippet that can be used for allocating memory:: +Here is an assembly snippet you can use for allocating memory that follows the process outlined above:: function allocate(length) -> pos { pos := mload(0x40) @@ -617,13 +618,13 @@ Here is an assembly snippet that can be used for allocating memory:: } The first 64 bytes of memory can be used as "scratch space" for short-term -allocation. The 32 bytes after the free memory pointer (i.e. starting at ``0x60``) -is meant to be zero permanently and is used as the initial value for +allocation. The 32 bytes after the free memory pointer (i.e., starting at ``0x60``) +are meant to be zero permanently and is used as the initial value for empty dynamic memory arrays. This means that the allocatable memory starts at ``0x80``, which is the initial value of the free memory pointer. -Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is +Elements in memory arrays in Solidity always occupy multiples of 32 bytes (this is even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory arrays are pointers to memory arrays. The length of a dynamic array is stored at the first slot of the array and followed by the array elements. @@ -631,7 +632,7 @@ first slot of the array and followed by the array elements. .. warning:: Statically-sized memory arrays do not have a length field, but it might be added later to allow better convertibility between statically- and dynamically-sized arrays, so - please do not rely on that. + do not rely on this. Standalone Assembly diff --git a/docs/bugs.json b/docs/bugs.json index 3eb5d1807..e4c3bbcc1 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,4 +1,23 @@ [ + { + "name": "SignedArrayStorageCopy", + "summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.", + "description": "In two's complement, negative integers have their higher order bits set. In order to fit into a shared storage slot, these have to be set to zero. When a conversion is done at the same time, the bits to set to zero were incorrectly determined from the source and not the target type. This means that such copy operations can lead to incorrect values being stored.", + "introduced": "0.4.7", + "fixed": "0.5.10", + "severity": "low/medium" + }, + { + "name": "ABIEncoderV2StorageArrayWithMultiSlotElement", + "summary": "Storage arrays containing structs or other statically-sized arrays are not read properly when directly encoded in external function calls or in abi.encode*.", + "description": "When storage arrays whose elements occupy more than a single storage slot are directly encoded in external function calls or using abi.encode*, their elements are read in an overlapping manner, i.e. the element pointer is not properly advanced between reads. This is not a problem when the storage data is first copied to a memory variable or if the storage array only contains value types or dynamically-sized arrays.", + "introduced": "0.4.16", + "fixed": "0.5.10", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, { "name": "DynamicConstructorArgumentsClippedABIV2", "summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.", diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 16c949f65..247f1230f 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -380,6 +380,7 @@ }, "0.4.10": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -394,6 +395,7 @@ }, "0.4.11": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -407,6 +409,7 @@ }, "0.4.12": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -419,6 +422,7 @@ }, "0.4.13": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -431,6 +435,7 @@ }, "0.4.14": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -442,6 +447,7 @@ }, "0.4.15": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -452,6 +458,8 @@ }, "0.4.16": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -463,6 +471,8 @@ }, "0.4.17": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -475,6 +485,8 @@ }, "0.4.18": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -486,6 +498,8 @@ }, "0.4.19": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -514,6 +528,8 @@ }, "0.4.20": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -526,6 +542,8 @@ }, "0.4.21": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -538,6 +556,8 @@ }, "0.4.22": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -550,6 +570,8 @@ }, "0.4.23": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -561,6 +583,8 @@ }, "0.4.24": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -572,6 +596,8 @@ }, "0.4.25": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", @@ -581,6 +607,8 @@ }, "0.4.26": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], "released": "2019-04-29" @@ -647,6 +675,7 @@ }, "0.4.7": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -661,6 +690,7 @@ }, "0.4.8": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -675,6 +705,7 @@ }, "0.4.9": { "bugs": [ + "SignedArrayStorageCopy", "UninitializedFunctionPointerInConstructor_0.4.x", "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", @@ -689,6 +720,8 @@ }, "0.5.0": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -698,6 +731,8 @@ }, "0.5.1": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -705,8 +740,14 @@ ], "released": "2018-12-03" }, + "0.5.10": { + "bugs": [], + "released": "2019-06-25" + }, "0.5.2": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -716,6 +757,8 @@ }, "0.5.3": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -725,6 +768,8 @@ }, "0.5.4": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -734,6 +779,8 @@ }, "0.5.5": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -745,6 +792,8 @@ }, "0.5.6": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries", @@ -755,6 +804,8 @@ }, "0.5.7": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2", "UninitializedFunctionPointerInConstructor", "IncorrectEventSignatureInLibraries" @@ -763,12 +814,17 @@ }, "0.5.8": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", "DynamicConstructorArgumentsClippedABIV2" ], "released": "2019-04-30" }, "0.5.9": { - "bugs": [], + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement" + ], "released": "2019-05-28" } } \ No newline at end of file diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 4e5a52c0f..e8d9961e7 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -252,10 +252,6 @@ will consume more gas than the 2300 gas stipend: Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. -.. note:: - Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve - any payload supplied with the call. - .. warning:: The fallback function is also executed if the caller meant to call a function that is not available. If you want to implement the fallback @@ -273,6 +269,10 @@ Like any function, the fallback function can execute complex operations as long A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`) or as a destination of a ``selfdestruct``. +.. note:: + Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve + any payload supplied with the call. + A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it. It also means that ``address(this).balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function). diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index 43c8637ee..91e7d5947 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -34,3 +34,8 @@ Contracts can inherit interfaces as they would inherit other contracts. Types defined inside interfaces and other contract-like structures can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. + +.. warning: + + Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make + sure the pragma version specifies this version as a minimum. \ No newline at end of file diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst index 12fc976d5..ed19dd046 100644 --- a/docs/contracts/using-for.rst +++ b/docs/contracts/using-for.rst @@ -33,39 +33,41 @@ Let us rewrite the set example from the pragma solidity >=0.4.16 <0.7.0; + // This is the same code as before, just without comments library Set { - struct Data { mapping(uint => bool) flags; } + struct Data { mapping(uint => bool) flags; } - function insert(Data storage self, uint value) - public - returns (bool) - { - if (self.flags[value]) - return false; // already there - self.flags[value] = true; - return true; - } + function insert(Data storage self, uint value) + public + returns (bool) + { + if (self.flags[value]) + return false; // already there + self.flags[value] = true; + return true; + } - function remove(Data storage self, uint value) - public - returns (bool) - { - if (!self.flags[value]) - return false; // not there - self.flags[value] = false; - return true; - } + function remove(Data storage self, uint value) + public + returns (bool) + { + if (!self.flags[value]) + return false; // not there + self.flags[value] = false; + return true; + } - function contains(Data storage self, uint value) - public - view - returns (bool) - { - return self.flags[value]; - } + function contains(Data storage self, uint value) + public + view + returns (bool) + { + return self.flags[value]; + } } + contract C { using Set for Set.Data; // this is the crucial change Set.Data knownValues; diff --git a/docs/contributing.rst b/docs/contributing.rst index 0e0a58905..fd488d56d 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -130,7 +130,7 @@ The CI runs additional tests (including ``solc-js`` and testing third party Soli Some versions of ``aleth`` can not be used for testing. We suggest using the same version that the Solidity continuous integration tests use. - Currently the CI uses version ``1.5.0-alpha.7`` of ``aleth``. + Currently the CI uses version ``1.6.0`` of ``aleth``. Writing and running syntax tests -------------------------------- diff --git a/docs/control-structures.rst b/docs/control-structures.rst index c8be772f4..bd04ce58b 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -298,8 +298,8 @@ Scoping and Declarations A variable which is declared will have an initial default value whose byte-representation is all zeros. The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool`` is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual -element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes`` -and ``string``, the default value is an empty array or string. +element will be initialized to the default value corresponding to its type. For dynamically-sized arrays, ``bytes`` +and ``string``, the default value is an empty array or string. For the ``enum`` type, the default value is its first member. Scoping in Solidity follows the widespread scoping rules of C99 (and many other languages): Variables are visible from the point right after their declaration @@ -373,33 +373,53 @@ In any case, you will get a warning about the outer variable being shadowed. Error handling: Assert, Require, Revert and Exceptions ====================================================== -Solidity uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the -state in the current call (and all its sub-calls) and also flag an error to the caller. -The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception -if the condition is not met. The ``assert`` function should only be used to test for internal errors, and to check invariants. -The ``require`` function should be used to ensure valid conditions, such as inputs, or contract state variables are met, or to validate return values from calls to external contracts. -If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix. +Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the +state in the current call (and all its sub-calls) and flags an error to the caller. -There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and -revert the current call. It is possible to provide a string message containing details about the error -that will be passed back to the caller. - -.. note:: - There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which - was deprecated in version 0.4.13 and removed in version 0.5.0. - -When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send`` -and the low-level functions ``call``, ``delegatecall`` and ``staticcall`` -- those return ``false`` as their first return value in case +When exceptions happen in a sub-call, they "bubble up" (i.e., exceptions are rethrown) automatically. Exceptions to this rule are ``send`` +and the low-level functions ``call``, ``delegatecall`` and ``staticcall``, they return ``false`` as their first return value in case of an exception instead of "bubbling up". .. warning:: - The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the called account is non-existent, as part of the design of EVM. Existence must be checked prior to calling if desired. + The low-level functions ``call``, ``delegatecall`` and ``staticcall`` return ``true`` as their first return value if the account called is non-existent, as part of the design of EVM. Existence must be checked prior to calling if needed. -Catching exceptions is not yet possible. +It is not yet possible to catch exceptions with Solidity. -In the following example, you can see how ``require`` can be used to easily check conditions on inputs -and how ``assert`` can be used for internal error checking. Note that you can optionally provide -a message string for ``require``, but not for ``assert``. +``assert`` and ``require`` +-------------------------- + +The convenience functions ``assert`` and ``require`` can be used to check for conditions and throw an exception +if the condition is not met. + +The ``assert`` function should only be used to test for internal errors, and to check invariants. Properly functioning code should never reach a failing ``assert`` statement; if this happens there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. + +An ``assert``-style exception is generated in the following situations: + +#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). +#. If you access a fixed-length ``bytesN`` at a too large or negative index. +#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). +#. If you shift by a negative amount. +#. If you convert a value too big or negative into an enum type. +#. If you call a zero-initialized variable of internal function type. +#. If you call ``assert`` with an argument that evaluates to false. + +The ``require`` function should be used to ensure valid conditions that cannot be detected until execution time. +These conditions include inputs, or contract state variables are met, or to validate return values from calls to external contracts. + +A ``require``-style exception is generated in the following situations: + +#. Calling ``require`` with an argument that evaluates to ``false``. +#. If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. +#. If you create a contract using the ``new`` keyword but the contract creation :ref:`does not finish properly`. +#. If you perform an external function call targeting a contract that contains no code. +#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +#. If your contract receives Ether via a public getter function. +#. If a ``.transfer()`` fails. + +You can optionally provide a message string for ``require``, but not for ``assert``. + +The following example shows how you can use ``require`` to check conditions on inputs +and ``assert`` for internal error checking. :: @@ -418,34 +438,23 @@ a message string for ``require``, but not for ``assert``. } } -An ``assert``-style exception is generated in the following situations: - -#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``). -#. If you access a fixed-length ``bytesN`` at a too large or negative index. -#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). -#. If you shift by a negative amount. -#. If you convert a value too big or negative into an enum type. -#. If you call a zero-initialized variable of internal function type. -#. If you call ``assert`` with an argument that evaluates to false. - -A ``require``-style exception is generated in the following situations: - -#. Calling ``require`` with an argument that evaluates to ``false``. -#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall``, ``callcode`` or ``staticcall`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. -#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). -#. If you perform an external function call targeting a contract that contains no code. -#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -#. If your contract receives Ether via a public getter function. -#. If a ``.transfer()`` fails. - Internally, Solidity performs a revert operation (instruction ``0xfd``) for a ``require``-style exception and executes an invalid operation (instruction ``0xfe``) to throw an ``assert``-style exception. In both cases, this causes the EVM to revert all changes made to the state. The reason for reverting is that there is no safe way to continue execution, because an expected effect -did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction -(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while -``require``-style exceptions will not consume any gas starting from the Metropolis release. +did not occur. Because we want to keep the atomicity of transactions, the safest action is to revert all changes and make the whole transaction +(or at least call) without effect. -The following example shows how an error string can be used together with revert and require: +.. note:: + + ``assert``-style exceptions consume all gas available to the call, while ``require``-style exceptions do not consume any gas starting from the Metropolis release. + +``revert`` +---------- + +The ``revert`` function is another way to trigger exceptions from within other code blocks to flag an error and +revert the current call. The function takes an optional string message containing details about the error that is passed back to the caller. + +The following example shows how to use an error string together with ``revert`` and the equivalent ``require``: :: @@ -464,9 +473,10 @@ The following example shows how an error string can be used together with revert } } -The provided string will be :ref:`abi-encoded ` as if it were a call to a function ``Error(string)``. -In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be -set as error return data: +The two syntax options are equivalent, it's developer preference which to use. + +The provided string is :ref:`abi-encoded ` as if it were a call to a function ``Error(string)``. +In the above example, ``revert("Not enough Ether provided.");`` returns the following hexadecimal as error return data: .. code:: @@ -474,3 +484,7 @@ set as error return data: 0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset 0x000000000000000000000000000000000000000000000000000000000000001a // String length 0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data + +.. note:: + There used to be a keyword called ``throw`` with the same semantics as ``revert()`` which + was deprecated in version 0.4.13 and removed in version 0.5.0. diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst index 765bdfc35..c4e684ce1 100644 --- a/docs/examples/safe-remote.rst +++ b/docs/examples/safe-remote.rst @@ -13,6 +13,7 @@ Safe Remote Purchase address payable public seller; address payable public buyer; enum State { Created, Locked, Inactive } + // The state variable has a default value of the first member, `State.created` State public state; // Ensure that `msg.value` is an even number. diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 71073e29c..b19532d39 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`_ | Cross-platform build file generator. | +| `CMake`_ (version 3.5+) | Cross-platform build file generator. | +-----------------------------------+-------------------------------------------------------+ | `Boost`_ (version 1.65+) | C++ libraries. | +-----------------------------------+-------------------------------------------------------+ @@ -201,6 +201,13 @@ The following are dependencies for all builds of Solidity: .. _CMake: https://cmake.org/download/ .. _z3: https://github.com/Z3Prover/z3 +.. note:: + Solidity versions prior to 0.5.10 can fail to correctly link against Boost versions 1.70+. + A possible workaround is to temporarily rename ``/lib/cmake/Boost-1.70.0`` + prior to running the cmake command to configure solidity. + + Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention. + Prerequisites - macOS --------------------- @@ -304,10 +311,6 @@ Building Solidity is quite similar on Linux, macOS and other Unices: cd build cmake .. && make -.. warning:: - - BSD builds should work, but are untested by the Solidity team. - or even easier on Linux and macOS, you can run: .. code-block:: bash @@ -315,6 +318,10 @@ or even easier on Linux and macOS, you can run: #note: this will install binaries solc and soltest at usr/local/bin ./scripts/build.sh +.. warning:: + + BSD builds should work, but are untested by the Solidity team. + And for Windows: .. code-block:: bash diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 5d5649837..f59200b24 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -42,8 +42,7 @@ data (its *state*) that resides at a specific address on the Ethereum blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot in a database that you can query and alter by calling functions of the -code that manages the database. In the case of Ethereum, this is always the owning -contract. In this case, the functions ``set`` and ``get`` can be used to modify +code that manages the database. In this example, the contract defines the functions ``set`` and ``get`` that can be used to modify or retrieve the value of the variable. To access a state variable, you do not need the prefix ``this.`` as is common in @@ -57,53 +56,54 @@ and overwrite your number, but the number is still stored in the history of the blockchain. Later, you will see how you can impose access restrictions so that only you can alter the number. -.. note:: - All identifiers (contract names, function names and variable names) are restricted to - the ASCII character set. It is possible to store UTF-8 encoded data in string variables. - .. warning:: Be careful with using Unicode text, as similar looking (or even identical) characters can have different code points and as such are encoded as a different byte array. +.. note:: + All identifiers (contract names, function names and variable names) are restricted to + the ASCII character set. It is possible to store UTF-8 encoded data in string variables. + .. index:: ! subcurrency Subcurrency Example =================== -The following contract will implement the simplest form of a -cryptocurrency. It is possible to generate coins out of thin air, but -only the person that created the contract will be able to do that (it is easy -to implement a different issuance scheme). -Furthermore, anyone can send coins to each other without a need for -registering with username and password — all you need is an Ethereum keypair. - +The following contract implements the simplest form of a +cryptocurrency. The contract allows only its creator to create new coins (different issuance scheme are possible). +Anyone can send coins to each other without a need for +registering with a username and password, all you need is an Ethereum keypair. :: pragma solidity >=0.5.0 <0.7.0; contract Coin { - // The keyword "public" makes those variables - // easily readable from outside. + // The keyword "public" makes variables + // accessible from other contracts address public minter; mapping (address => uint) public balances; - // Events allow light clients to react to - // changes efficiently. + // Events allow clients to react to specific + // contract changes you declare event Sent(address from, address to, uint amount); - // This is the constructor whose code is - // run only when the contract is created. + // Constructor code is only run when the contract + // is created constructor() public { minter = msg.sender; } + // Sends an amount of newly created coins to an address + // Can only be called by the contract creator function mint(address receiver, uint amount) public { require(msg.sender == minter); require(amount < 1e60); balances[receiver] += amount; } + // Sends an amount of existing coins + // from any caller to an address function send(address receiver, uint amount) public { require(amount <= balances[msg.sender], "Insufficient balance."); balances[msg.sender] -= amount; @@ -114,58 +114,56 @@ registering with username and password — all you need is an Ethereum keypair. This contract introduces some new concepts, let us go through them one by one. -The line ``address public minter;`` declares a state variable of type address -that is publicly accessible. The ``address`` type is a 160-bit value -that does not allow any arithmetic operations. It is suitable for -storing addresses of contracts or of keypairs belonging to external -persons. The keyword ``public`` automatically generates a function that -allows you to access the current value of the state variable -from outside of the contract. -Without this keyword, other contracts have no way to access the variable. -The code of the function generated by the compiler is roughly equivalent +The line ``address public minter;`` declares a state variable of type :ref:`address
`. +The ``address`` type is a 160-bit value that does not allow any arithmetic operations. +It is suitable for storing addresses of contracts, or a hash of the public half of a keypair belonging to :ref:`external accounts`. + +The keyword ``public`` automatically generates a function that allows you to access the current value of the state +variable from outside of the contract. Without this keyword, other contracts have no way to access the variable. +The code of the function generated by the compiler is equivalent to the following (ignore ``external`` and ``view`` for now):: function minter() external view returns (address) { return minter; } -Of course, adding a function exactly like that will not work -because we would have a -function and a state variable with the same name, but hopefully, you -get the idea - the compiler figures that out for you. +You could add a function like the above yourself, but you would have a function and state variable with the same name. +You do not need to do this, the compiler figures it out for you. .. index:: mapping The next line, ``mapping (address => uint) public balances;`` also creates a public state variable, but it is a more complex datatype. -The type maps addresses to unsigned integers. +The :ref:`mapping ` type maps addresses to :ref:`unsigned integers `. + Mappings can be seen as `hash tables `_ which are -virtually initialized such that every possible key exists from the start and is mapped to a -value whose byte-representation is all zeros. This analogy does not go -too far, though, as it is neither possible to obtain a list of all keys of -a mapping, nor a list of all values. So either keep in mind (or -better, keep a list or use a more advanced data type) what you -added to the mapping or use it in a context where this is not needed. +virtually initialised such that every possible key exists from the start and is mapped to a +value whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys of +a mapping, nor a list of all values. Record what you +added to the mapping, or use it in a context where this is not needed. Or +even better, keep a list, or use a more suitable data type. + The :ref:`getter function` created by the ``public`` keyword -is a bit more complex in this case. It roughly looks like the +is more complex in the case of a mapping. It looks like the following:: function balances(address _account) external view returns (uint) { return balances[_account]; } -As you see, you can use this function to easily query the balance of a -single account. +You can use this function to query the balance of a single account. .. index:: event The line ``event Sent(address from, address to, uint amount);`` declares -a so-called "event" which is emitted in the last line of the function -``send``. User interfaces (as well as server applications of course) can -listen for those events being emitted on the blockchain without much -cost. As soon as it is emitted, the listener will also receive the -arguments ``from``, ``to`` and ``amount``, which makes it easy to track -transactions. In order to listen for this event, you would use the following -JavaScript code (which assumes that ``Coin`` is a contract object created via -web3.js or a similar module):: +an :ref:`"event" `, which is emitted in the last line of the function +``send``. Ethereum clients such as web applications can +listen for these events emitted on the blockchain without much +cost. As soon as it is emitted, the listener receives the +arguments ``from``, ``to`` and ``amount``, which makes it possible to track +transactions. + +To listen for this event, you could use the following +JavaScript code, which uses `web3.js `_ to create the ``Coin`` contract object, +and any user interface calls the automatically generated ``balances`` function from above:: Coin.Sent().watch({}, '', function(error, result) { if (!error) { @@ -178,36 +176,33 @@ web3.js or a similar module):: } }) -Note how the automatically generated function ``balances`` is called from -the user interface. - .. index:: coin -The constructor is a special function which is run during creation of the contract and -cannot be called afterwards. It permanently stores the address of the person creating the -contract: ``msg`` (together with ``tx`` and ``block``) is a special global variable that -contains some properties which allow access to the blockchain. ``msg.sender`` is +The :ref:`constructor` is a special function run during the creation of the contract and +cannot be called afterwards. In this case, it permanently stores the address of the person creating the +contract. The ``msg`` variable (together with ``tx`` and ``block``) is a +:ref:`special global variable ` that +contains properties which allow access to the blockchain. ``msg.sender`` is always the address where the current (external) function call came from. -Finally, the functions that will actually end up with the contract and can be called -by users and contracts alike are ``mint`` and ``send``. -If ``mint`` is called by anyone except the account that created the contract, -nothing will happen. This is ensured by the special function ``require`` which -causes all changes to be reverted if its argument evaluates to false. -The second call to ``require`` ensures that there will not be too many coins, -which could cause overflow errors later. +The functions that make up the contract, and that users and contracts can call are ``mint`` and ``send``. -On the other hand, ``send`` can be used by anyone (who already -has some of these coins) to send coins to anyone else. If you do not have -enough coins to send, the ``require`` call will fail and also provide the -user with an appropriate error message string. +The ``mint`` function sends an amount of newly created coins to another address. +The :ref:`require ` function call defines conditions that reverts all changes if not met. +In this example, ``require(msg.sender == minter);`` ensures that only the creator of the contract can call ``mint``, +and ``require(amount < 1e60);`` ensures a maximum amount of tokens, without which could cause overflow errors in the future. + +The ``send`` function can be used by anyone (who already +has some of these coins) to send coins to anyone else. If the sender does not have +enough coins to send, the ``require`` call fails and provides the +sender with an appropriate error message string. .. note:: If you use this contract to send coins to an address, you will not see anything when you - look at that address on a blockchain explorer, because the fact that you sent + look at that address on a blockchain explorer, because the record that you sent coins and the changed balances are only stored in the data storage of this - particular coin contract. By the use of events it is relatively easy to create + particular coin contract. By using events, you can create a "blockchain explorer" that tracks transactions and balances of your new coin, but you have to inspect the coin contract address and not the addresses of the coin owners. @@ -301,6 +296,8 @@ Smart contracts even have limited access to other smart contracts. .. index:: ! account, address, storage, balance +.. _accounts: + Accounts ======== @@ -513,10 +510,10 @@ Deactivate and Self-destruct The only way to remove code from the blockchain is when a contract at that address performs the ``selfdestruct`` operation. The remaining Ether stored at that address is sent to a designated target and then the storage and code is removed from the state. Removing the contract in theory sounds like a good idea, but it is potentially dangerous, as if someone sends Ether to removed contracts, the Ether is forever lost. +.. warning:: + Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk. + .. note:: Even if a contract's code does not contain a call to ``selfdestruct``, it can still perform that operation using ``delegatecall`` or ``callcode``. If you want to deactivate your contracts, you should instead **disable** them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately. - -.. warning:: - Even if a contract is removed by "selfdestruct", it is still part of the history of the blockchain and probably retained by most Ethereum nodes. So using "selfdestruct" is not the same as deleting data from a hard disk. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index ff80b01ae..137513b83 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -11,7 +11,7 @@ Layout of State Variables in Storage Statically-sized variables (everything except mapping and dynamically-sized array types) are laid out contiguously in storage starting from position ``0``. Multiple, contiguous items that need less than 32 bytes are packed into a single storage slot if possible, according to the following rules: - The first item in a storage slot is stored lower-order aligned. -- Elementary types use only that many bytes that are necessary to store them. +- Elementary types use only as many bytes as are necessary to store them. - If an elementary type does not fit the remaining part of a storage slot, it is moved to the next storage slot. - Structs and array data always start a new slot and occupy whole slots (but items inside a struct or array are packed tightly according to these rules). @@ -64,10 +64,11 @@ So for the following contract snippet:: pragma solidity >=0.4.0 <0.7.0; + contract C { - struct s { uint a; uint b; } - uint x; - mapping(uint => mapping(uint => s)) data; + struct S { uint a; uint b; } + uint x; + mapping(uint => mapping(uint => S)) data; } The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint256(4) . uint256(1))) + 1``. @@ -268,7 +269,7 @@ Tips and Tricks * Use ``delete`` on arrays to delete all its elements. * Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check! -* Make your state variables public - the compiler will create :ref:`getters ` for you automatically. +* Make your state variables public - the compiler creates :ref:`getters ` for you automatically. * If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`. * Initialize storage structs with a single assignment: ``x = MyStruct({a: 1, b: 2});`` diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst index d55547050..2ba6d2188 100644 --- a/docs/natspec-format.rst +++ b/docs/natspec-format.rst @@ -36,12 +36,16 @@ Documentation is inserted above each ``class``, ``interface`` and documentation `__. The following example shows a contract and a function using all available tags. -Note: NatSpec currently does NOT apply to public state variables (see -`solidity#3418 `__), -even if they are declared public and therefore do affect the ABI. Note: -The Solidity compiler only interprets tags if they are external or -public. You are welcome to use similar comments for your internal and -private functions, but those will not be parsed. + +.. note:: + + NatSpec currently does NOT apply to public state variables (see + `solidity#3418 `__), + even if they are declared public and therefore do affect the ABI. + + The Solidity compiler only interprets tags if they are external or + public. You are welcome to use similar comments for your internal and + private functions, but those will not be parsed. .. code:: solidity @@ -108,7 +112,7 @@ to the end-user as: This function will multiply 10 by 7 -if a function is being called and the input ``a`` is assigned a value of 7. +if a function is being called and the input ``a`` is assigned a value of 10. Specifying these dynamic expressions is outside the scope of the Solidity documentation and you may read more at @@ -196,4 +200,3 @@ file should also be produced and should look like this: }, "title" : "A simulator for trees" } - diff --git a/docs/style-guide.rst b/docs/style-guide.rst index 7bc75a306..fb38f2d93 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -757,20 +757,26 @@ No:: pragma solidity >=0.4.0 <0.7.0; + // Base contracts just to make this compile contract B { constructor(uint) public { } } + + contract C { constructor(uint, uint) public { } } + + contract D { constructor(uint) public { } } + contract A is B, C, D { uint x; @@ -778,12 +784,12 @@ No:: B(param1) C(param2, param3) D(param4) - public - { + public { x = param5; } } + contract X is B, C, D { uint x; @@ -792,10 +798,11 @@ No:: C(param2, param3) D(param4) public { - x = param5; - } + x = param5; + } } + When declaring short functions with a single statement, it is permissible to do it on a single line. Permissible:: @@ -973,27 +980,32 @@ Yes:: pragma solidity >=0.4.0 <0.7.0; + // Owned.sol contract Owned { - address public owner; + address public owner; - constructor() public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - modifier onlyOwner { - require(msg.sender == owner); - _; - } + modifier onlyOwner { + require(msg.sender == owner); + _; + } - function transferOwnership(address newOwner) public onlyOwner { - owner = newOwner; - } + function transferOwnership(address newOwner) public onlyOwner { + owner = newOwner; + } } - // Congress.sol +and in ``Congress.sol``:: + + pragma solidity >=0.4.0 <0.7.0; + import "./Owned.sol"; + contract Congress is Owned, TokenRecipient { //... } @@ -1002,32 +1014,34 @@ No:: pragma solidity >=0.4.0 <0.7.0; + // owned.sol contract owned { - address public owner; + address public owner; - constructor() public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - modifier onlyOwner { - require(msg.sender == owner); - _; - } + modifier onlyOwner { + require(msg.sender == owner); + _; + } - function transferOwnership(address newOwner) public onlyOwner { - owner = newOwner; - } + function transferOwnership(address newOwner) public onlyOwner { + owner = newOwner; + } } - // Congress.sol +and in ``Congress.sol``:: + import "./owned.sol"; + contract Congress is owned, tokenRecipient { //... } - Struct Names ========================== @@ -1104,6 +1118,7 @@ added looks like the one below:: pragma solidity >=0.4.0 <0.7.0; + /// @author The Solidity Team /// @title A simple storage example contract SimpleStorage { @@ -1126,4 +1141,4 @@ 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). -Please see the sectian about `NatSpec `_ for a detailed explanation. \ No newline at end of file +Please see the section about `NatSpec `_ for a detailed explanation. \ No newline at end of file diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst index 49a3f0100..51e373405 100644 --- a/docs/types/conversion.rst +++ b/docs/types/conversion.rst @@ -8,25 +8,28 @@ Conversions between Elementary Types Implicit Conversions -------------------- -If an operator is applied to different types, the compiler tries to -implicitly convert one of the operands to the type of the other (the same is -true for assignments). In general, an implicit conversion between value-types -is possible if it -makes sense semantically and no information is lost: ``uint8`` is convertible to -``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256`` -(because ``uint256`` cannot hold e.g. ``-1``). +If an operator is applied to different types, the compiler tries to implicitly +convert one of the operands to the type of the other (the same is true for assignments). +This means that operations are always performed in the type of one of the operands. +In general, an implicit conversion between value-types is possible if it makes +sense semantically and no information is lost. + +For example, ``uint8`` is convertible to +``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256``, +because ``uint256`` cannot hold values such as ``-1``. For more details, please consult the sections about the types themselves. Explicit Conversions -------------------- -If the compiler does not allow implicit conversion but you know what you are -doing, an explicit type conversion is sometimes possible. Note that this may -give you some unexpected behaviour and allows you to bypass some security +If the compiler does not allow implicit conversion but you are confident a conversion will work, +an explicit type conversion is sometimes possible. This may +result in unexpected behaviour and allows you to bypass some security features of the compiler, so be sure to test that the -result is what you want! Take the following example where you are converting -a negative ``int`` to a ``uint``: +result is what you want and expect! + +Take the following example that converts a negative ``int`` to a ``uint``: :: @@ -42,7 +45,7 @@ cut off:: uint32 a = 0x12345678; uint16 b = uint16(a); // b will be 0x5678 now -If an integer is explicitly converted to a larger type, it is padded on the left (i.e. at the higher order end). +If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end). The result of the conversion will compare equal to the original integer:: uint16 a = 0x1234; diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index 2414687c5..afee61202 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -25,7 +25,7 @@ in functions, or as parameters for library functions. They cannot be used as parameters or return parameters of contract functions that are publicly visible. -You can mark variables of mapping type as ``public`` and Solidity creates a +You can mark state variables of mapping type as ``public`` and Solidity creates a :ref:`getter ` for you. The ``_KeyType`` becomes a parameter for the getter. If ``_ValueType`` is a value type or a struct, the getter returns ``_ValueType``. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 9c082f9b4..f970a2e2c 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -26,6 +26,7 @@ Operators: The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. .. index:: ! uint, ! int, ! integer +.. _integers: Integers -------- @@ -332,7 +333,7 @@ type and this type is also used in the :ref:`ABI`. Contracts do not support any operators. The members of contract types are the external functions of the contract -including public state variables. +including any state variables marked as ``public``. For a contract ``C`` you can use ``type(C)`` to access :ref:`type information` about the contract. @@ -427,6 +428,9 @@ long as the operands are integers. If any of the two is fractional, bit operatio and exponentiation is disallowed if the exponent is fractional (because that might result in a non-rational number). +.. warning:: + Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. + .. note:: Solidity has a number literal type for each rational number. Integer literals and rational number literals belong to number literal types. @@ -435,8 +439,6 @@ a non-rational number). types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both belong to the same number literal type for the rational number three. -.. warning:: - Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. .. note:: Number literal expressions are converted into a non-literal type as soon as they are used with non-literal @@ -510,7 +512,7 @@ Enums Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversion from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise. -Enums needs at least one member. +Enums require at least one member, and its default value when declared is the first member. The data representation is the same as for enums in C: The options are represented by subsequent unsigned integer values starting from ``0``. @@ -624,100 +626,116 @@ Example that shows how to use the members:: pragma solidity >=0.4.16 <0.7.0; + contract Example { - function f() public payable returns (bytes4) { - return this.f.selector; - } - function g() public { - this.f.gas(10).value(800)(); - } + function f() public payable returns (bytes4) { + return this.f.selector; + } + + function g() public { + this.f.gas(10).value(800)(); + } } Example that shows how to use internal function types:: pragma solidity >=0.4.16 <0.7.0; + library ArrayUtils { - // internal functions can be used in internal library functions because - // they will be part of the same code context - function map(uint[] memory self, function (uint) pure returns (uint) f) - internal - pure - returns (uint[] memory r) - { - r = new uint[](self.length); - for (uint i = 0; i < self.length; i++) { - r[i] = f(self[i]); + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) pure returns (uint) f) + internal + pure + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } } - } - function reduce( - uint[] memory self, - function (uint, uint) pure returns (uint) f - ) - internal - pure - returns (uint r) - { - r = self[0]; - for (uint i = 1; i < self.length; i++) { - r = f(r, self[i]); + + function reduce( + uint[] memory self, + function (uint, uint) pure returns (uint) f + ) + internal + pure + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } } - } - function range(uint length) internal pure returns (uint[] memory r) { - r = new uint[](length); - for (uint i = 0; i < r.length; i++) { - r[i] = i; + + function range(uint length) internal pure returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } } - } } + contract Pyramid { - using ArrayUtils for *; - function pyramid(uint l) public pure returns (uint) { - return ArrayUtils.range(l).map(square).reduce(sum); - } - function square(uint x) internal pure returns (uint) { - return x * x; - } - function sum(uint x, uint y) internal pure returns (uint) { - return x + y; - } + using ArrayUtils for *; + + function pyramid(uint l) public pure returns (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + + function square(uint x) internal pure returns (uint) { + return x * x; + } + + function sum(uint x, uint y) internal pure returns (uint) { + return x + y; + } } Another example that uses external function types:: pragma solidity >=0.4.22 <0.7.0; + contract Oracle { - struct Request { - bytes data; - function(uint) external callback; - } - Request[] requests; - event NewRequest(uint); - function query(bytes memory data, function(uint) external callback) public { - requests.push(Request(data, callback)); - emit NewRequest(requests.length - 1); - } - function reply(uint requestID, uint response) public { - // Here goes the check that the reply comes from a trusted source - requests[requestID].callback(response); - } + struct Request { + bytes data; + function(uint) external callback; + } + + Request[] private requests; + event NewRequest(uint); + + function query(bytes memory data, function(uint) external callback) public { + requests.push(Request(data, callback)); + emit NewRequest(requests.length - 1); + } + + function reply(uint requestID, uint response) public { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } } + contract OracleUser { - Oracle constant oracle = Oracle(0x1234567); // known contract - uint exchangeRate; - function buySomething() public { - oracle.query("USD", this.oracleResponse); - } - function oracleResponse(uint response) public { - require( - msg.sender == address(oracle), - "Only oracle can call this." - ); - exchangeRate = response; - } + Oracle constant private ORACLE_CONST = Oracle(0x1234567); // known contract + uint private exchangeRate; + + function buySomething() public { + ORACLE_CONST.query("USD", this.oracleResponse); + } + + function oracleResponse(uint response) public { + require( + msg.sender == address(ORACLE_CONST), + "Only oracle can call this." + ); + exchangeRate = response; + } } .. note:: diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 604386c47..8b8c06735 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -52,6 +52,8 @@ interpret a function parameter in days, you can in the following way:: } } +.. _special-variables-functions: + Special Variables and Functions =============================== diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index b7bf917f8..a092e3140 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -12,6 +12,7 @@ set(sources FixedHash.h IndentedWriter.cpp IndentedWriter.h + InvertibleMap.h IpfsHash.cpp IpfsHash.h JSON.cpp @@ -33,7 +34,6 @@ set(sources ) add_library(devcore ${sources}) -target_link_libraries(devcore PUBLIC jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} Threads::Threads) +target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system Threads::Threads) target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") -target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) add_dependencies(devcore solidity_BuildInfo.h) diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 0a13e7940..2fca07171 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -81,6 +81,22 @@ inline std::vector operator+(std::vector&& _a, std::vector&& _b) ret += std::move(_b); return ret; } +/// Concatenate something to a sets of elements. +template +inline std::set operator+(std::set const& _a, U&& _b) +{ + std::set ret(_a); + ret += std::forward(_b); + return ret; +} +/// Concatenate something to a sets of elements, move variant. +template +inline std::set operator+(std::set&& _a, U&& _b) +{ + std::set ret(std::move(_a)); + ret += std::forward(_b); + return ret; +} namespace dev { diff --git a/libdevcore/InvertibleMap.h b/libdevcore/InvertibleMap.h new file mode 100644 index 000000000..4448d03d7 --- /dev/null +++ b/libdevcore/InvertibleMap.h @@ -0,0 +1,93 @@ +/* + 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 + +/** + * Data structure that keeps track of values and keys of a mapping. + */ +template +struct InvertibleMap +{ + std::map values; + // references[x] == {y | values[y] == x} + std::map> references; + + void set(K _key, V _value) + { + if (values.count(_key)) + references[values[_key]].erase(_key); + values[_key] = _value; + references[_value].insert(_key); + } + + void eraseKey(K _key) + { + if (values.count(_key)) + references[values[_key]].erase(_key); + values.erase(_key); + } + + void eraseValue(V _value) + { + if (references.count(_value)) + { + for (V v: references[_value]) + values.erase(v); + references.erase(_value); + } + } + + void clear() + { + values.clear(); + references.clear(); + } +}; + +template +struct InvertibleRelation +{ + /// forward[x] contains y <=> backward[y] contains x + std::map> forward; + std::map> backward; + + void insert(T _key, T _value) + { + forward[_key].insert(_value); + backward[_value].insert(_key); + } + + void set(T _key, std::set _values) + { + for (T v: forward[_key]) + backward[v].erase(_key); + for (T v: _values) + backward[v].insert(_key); + forward[_key] = std::move(_values); + } + + void eraseKey(T _key) + { + for (auto const& v: forward[_key]) + backward[v].erase(_key); + forward.erase(_key); + } +}; diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp index 8a20f2cd7..c48c8d6f1 100644 --- a/libdevcore/Whiskers.cpp +++ b/libdevcore/Whiskers.cpp @@ -37,6 +37,7 @@ Whiskers::Whiskers(string _template): Whiskers& Whiskers::operator()(string _parameter, string _value) { + checkParameterValid(_parameter); checkParameterUnknown(_parameter); m_parameters[move(_parameter)] = move(_value); return *this; @@ -44,6 +45,7 @@ Whiskers& Whiskers::operator()(string _parameter, string _value) Whiskers& Whiskers::operator()(string _parameter, bool _value) { + checkParameterValid(_parameter); checkParameterUnknown(_parameter); m_conditions[move(_parameter)] = _value; return *this; @@ -54,7 +56,11 @@ Whiskers& Whiskers::operator()( vector> _values ) { + checkParameterValid(_listParameter); checkParameterUnknown(_listParameter); + for (auto const& element: _values) + for (auto const& val: element) + checkParameterValid(val.first); m_listParameters[move(_listParameter)] = move(_values); return *this; } @@ -64,7 +70,17 @@ string Whiskers::render() const return replace(m_template, m_parameters, m_conditions, m_listParameters); } -void Whiskers::checkParameterUnknown(string const& _parameter) +void Whiskers::checkParameterValid(string const& _parameter) const +{ + static boost::regex validParam("^" + paramRegex() + "$"); + assertThrow( + boost::regex_match(_parameter, validParam), + WhiskersError, + "Parameter" + _parameter + " contains invalid characters." + ); +} + +void Whiskers::checkParameterUnknown(string const& _parameter) const { assertThrow( !m_parameters.count(_parameter), @@ -91,7 +107,7 @@ string Whiskers::replace( ) { using namespace boost; - static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)|<\\?([^>]+)>(.*?)((.*?))?"); + static regex listOrTag("<(" + paramRegex() + ")>|<#(" + paramRegex() + ")>(.*?)|<\\?(" + paramRegex() + ")>(.*?)((.*?))?"); return regex_replace(_template, listOrTag, [&](match_results _match) -> string { string tagName(_match[1]); diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h index 7944358a0..ebe17b51c 100644 --- a/libdevcore/Whiskers.h +++ b/libdevcore/Whiskers.h @@ -85,7 +85,8 @@ public: std::string render() const; private: - void checkParameterUnknown(std::string const& _parameter); + void checkParameterValid(std::string const& _parameter) const; + void checkParameterUnknown(std::string const& _parameter) const; static std::string replace( std::string const& _template, @@ -94,6 +95,8 @@ private: StringListMap const& _listParameters = StringListMap() ); + static std::string paramRegex() { return "[a-zA-Z0-9_$-]+"; } + /// Joins the two maps throwing an exception if two keys are equal. static StringMap joinMaps(StringMap const& _a, StringMap const& _b); diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 7c95883b2..70f136d9d 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -89,7 +89,6 @@ std::vector> simplificationRuleListPart1( {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }, false}, {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, - {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }, false}, {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { if (A.d() >= 31) return B.d(); @@ -124,6 +123,7 @@ std::vector> simplificationRuleListPart2( {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, {{Instruction::ADD, {0, X}}, [=]{ return X; }, false}, {{Instruction::SUB, {X, 0}}, [=]{ return X; }, false}, + {{Instruction::SUB, {~u256(0), X}}, [=]() -> Pattern { return {Instruction::NOT, {X}}; }, false}, {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::MUL, {X, 1}}, [=]{ return X; }, false}, diff --git a/liblangutil/CharStream.cpp b/liblangutil/CharStream.cpp index 2c10bd608..58edced3b 100644 --- a/liblangutil/CharStream.cpp +++ b/liblangutil/CharStream.cpp @@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount) return get(); } +char CharStream::setPosition(size_t _location) +{ + solAssert(_location <= m_source.size(), "Attempting to set position past end of source."); + m_position = _location; + return get(); +} + string CharStream::lineAtPosition(int _position) const { // if _position points to \n, it returns the line before the \n @@ -106,5 +113,3 @@ tuple CharStream::translatePositionToLineColumn(int _position) const } return tuple(lineNumber, searchPosition - lineStart); } - - diff --git a/liblangutil/CharStream.h b/liblangutil/CharStream.h index c11340b21..504c39da5 100644 --- a/liblangutil/CharStream.h +++ b/liblangutil/CharStream.h @@ -76,7 +76,13 @@ public: char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } char advanceAndGet(size_t _chars = 1); + /// Sets scanner position to @ _amount characters backwards in source text. + /// @returns The character of the current location after update is returned. char rollback(size_t _amount); + /// Sets scanner position to @ _location if it refers a valid offset in m_source. + /// If not, nothing is done. + /// @returns The character of the current location after update is returned. + char setPosition(size_t _location); void reset() { m_position = 0; } diff --git a/liblangutil/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp index 0bf3a4167..7f8a77f1a 100644 --- a/liblangutil/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se m_errorList.push_back(err); } +bool ErrorReporter::hasExcessiveErrors() const +{ + return m_errorCount > c_maxErrorsAllowed; +} + bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) { if (_type == Error::Type::Warning) diff --git a/liblangutil/ErrorReporter.h b/liblangutil/ErrorReporter.h index 30b494a0f..6d6b4ef41 100644 --- a/liblangutil/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -118,6 +118,9 @@ public: return m_errorCount > 0; } + // @returns true if the maximum error count has been reached. + bool hasExcessiveErrors() const; + private: void error( Error::Type _type, @@ -149,4 +152,3 @@ private: }; } - diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index edd23fbaf..4ceb9358f 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const return m_scanner->peekNextToken(); } -std::string ParserBase::currentLiteral() const +string ParserBase::currentLiteral() const { return m_scanner->currentLiteral(); } @@ -57,38 +57,87 @@ Token ParserBase::advance() return m_scanner->next(); } +string ParserBase::tokenName(Token _token) +{ + if (_token == Token::Identifier) + return "identifier"; + else if (_token == Token::EOS) + return "end of source"; + else if (TokenTraits::isReservedKeyword(_token)) + return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'"; + else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting + { + ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); + return "'" + elemTypeName.toString() + "'"; + } + else + return "'" + TokenTraits::friendlyName(_token) + "'"; +} + void ParserBase::expectToken(Token _value, bool _advance) { Token tok = m_scanner->currentToken(); if (tok != _value) { - auto tokenName = [this](Token _token) - { - if (_token == Token::Identifier) - return string("identifier"); - else if (_token == Token::EOS) - return string("end of source"); - else if (TokenTraits::isReservedKeyword(_token)) - return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'"; - else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting - { - ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); - return string("'") + elemTypeName.toString() + "'"; - } - else - return string("'") + TokenTraits::friendlyName(_token) + "'"; - }; - - fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok)); + string const expectedToken = ParserBase::tokenName(_value); + if (m_parserErrorRecovery) + parserError("Expected " + expectedToken + " but got " + tokenName(tok)); + else + fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok)); + // Do not advance so that recovery can sync or make use of the current token. + // This is especially useful if the expected token + // is the only one that is missing and is at the end of a construct. + // "{ ... ; }" is such an example. + // ^ + _advance = false; } if (_advance) m_scanner->next(); } +void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance) +{ + Token tok = m_scanner->currentToken(); + if (tok != _value) + { + int startPosition = position(); + SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()}; + while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) + m_scanner->next(); + + string const expectedToken = ParserBase::tokenName(_value); + string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + " instead."; + if (m_scanner->currentToken() == Token::EOS) + { + // rollback to where the token started, and raise exception to be caught at a higher level. + m_scanner->setPosition(startPosition); + m_inParserRecovery = true; + fatalParserError(errorLoc, msg); + } + else + { + if (m_inParserRecovery) + parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); + else + parserError(errorLoc, msg + "Recovered at next " + expectedToken); + m_inParserRecovery = false; + } + } + else if (m_inParserRecovery) + { + string expectedToken = ParserBase::tokenName(_value); + parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + "."); + m_inParserRecovery = false; + } + + if (_advance) + m_scanner->next(); +} + void ParserBase::increaseRecursionDepth() { m_recursionDepth++; - if (m_recursionDepth >= 2560) + if (m_recursionDepth >= 1200) fatalParserError("Maximum recursion depth reached during parsing."); } @@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth() m_recursionDepth--; } +void ParserBase::parserWarning(string const& _description) +{ + m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description); +} + +void ParserBase::parserError(SourceLocation const& _location, string const& _description) +{ + m_errorReporter.parserError(_location, _description); +} + void ParserBase::parserError(string const& _description) { - m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description); + parserError(SourceLocation{position(), endPosition(), source()}, _description); } void ParserBase::fatalParserError(string const& _description) { - m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); + fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); +} + +void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description) +{ + m_errorReporter.fatalParserError(_location, _description); } diff --git a/liblangutil/ParserBase.h b/liblangutil/ParserBase.h index 1c6f298c1..98123b813 100644 --- a/liblangutil/ParserBase.h +++ b/liblangutil/ParserBase.h @@ -36,7 +36,14 @@ class Scanner; class ParserBase { public: - explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} + /// Set @a _parserErrorRecovery to true for additional error + /// recovery. This is experimental and intended for use + /// by front-end tools that need partial AST information even + /// when errors occur. + explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter) + { + m_parserErrorRecovery = _parserErrorRecovery; + } std::shared_ptr source() const { return m_scanner->charStream(); } @@ -62,10 +69,17 @@ protected: ///@{ ///@name Helper functions - /// If current token value is not _value, throw exception otherwise advance token. + /// If current token value is not @a _value, throw exception otherwise advance token + // @a if _advance is true and error recovery is in effect. void expectToken(Token _value, bool _advance = true); + + /// Like expectToken but if there is an error ignores tokens until + /// the expected token or EOS is seen. If EOS is encountered, back up to the error point, + /// and throw an exception so that a higher grammar rule has an opportunity to recover. + void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true); Token currentToken() const; Token peekNextToken() const; + std::string tokenName(Token _token); std::string currentLiteral() const; Token advance(); ///@} @@ -77,16 +91,26 @@ protected: /// Creates a @ref ParserError and annotates it with the current position and the /// given @a _description. void parserError(std::string const& _description); + void parserError(SourceLocation const& _location, std::string const& _description); + + /// Creates a @ref ParserWarning and annotates it with the current position and the + /// given @a _description. + void parserWarning(std::string const& _description); /// Creates a @ref ParserError and annotates it with the current position and the /// given @a _description. Throws the FatalError. void fatalParserError(std::string const& _description); + void fatalParserError(SourceLocation const& _location, std::string const& _description); std::shared_ptr m_scanner; /// The reference to the list of errors and warning to add errors/warnings during parsing ErrorReporter& m_errorReporter; /// Current recursion depth during parsing. size_t m_recursionDepth = 0; + /// True if we are in parser error recovery. Usually this means we are scanning for + /// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages. + bool m_inParserRecovery = false; + bool m_parserErrorRecovery = false; }; } diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 852a92e9b..a317c79b6 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -156,6 +156,13 @@ void Scanner::reset() next(); } +void Scanner::setPosition(size_t _offset) +{ + m_char = m_source->setPosition(_offset); + scanToken(); + next(); +} + void Scanner::supportPeriodInIdentifier(bool _value) { m_supportPeriodInIdentifier = _value; diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 9a4a170a6..555575129 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -110,6 +110,9 @@ public: /// @returns the next token and advances input Token next(); + /// Set scanner to a specific offset. This is used in error recovery. + void setPosition(size_t _offset); + ///@{ ///@name Information about the current token diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 572038d22..0360dba29 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -137,10 +137,10 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND})) endif() add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) -target_link_libraries(solidity PUBLIC yul evmasm langutil devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) +target_link_libraries(solidity PUBLIC yul evmasm langutil devcore Boost::boost Boost::filesystem Boost::system) if (${Z3_FOUND}) - target_link_libraries(solidity PUBLIC Z3::Z3) + target_link_libraries(solidity PUBLIC z3::libz3) endif() if (${CVC4_FOUND}) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index dc05ce18f..8069bd13b 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -2065,6 +2065,11 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) { if (funType->kind() == FunctionType::Kind::Creation) errorMsg = "Constructor for " + t.front()->toString() + " must be payable for member \"value\" to be available."; + else if ( + funType->kind() == FunctionType::Kind::DelegateCall || + funType->kind() == FunctionType::Kind::BareDelegateCall + ) + errorMsg = "Member \"value\" is not allowed in delegated calls due to \"msg.value\" persisting."; else errorMsg = "Member \"value\" is only available for payable functions."; } diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 36540bc08..d791a8a06 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -456,15 +456,13 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node) { Json::Value externalReferences(Json::arrayValue); - for (auto const& it : _node.annotation().externalReferences) - { + for (auto const& it: _node.annotation().externalReferences) if (it.first) { Json::Value tuple(Json::objectValue); tuple[it.first->name.str()] = inlineAssemblyIdentifierToJson(it); externalReferences.append(tuple); } - } setJsonNode(_node, "InlineAssembly", { make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))), make_pair("externalReferences", std::move(externalReferences)) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6877ecbd6..65d9abf06 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2912,7 +2912,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(1, ""), Kind::SetValue, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet @@ -2929,7 +2929,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con strings(1, ""), Kind::SetGas, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index d0c197b1f..1583a8d63 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -414,7 +414,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( fromArrayType.isByteArray() || *fromArrayType.baseType() == *TypeProvider::uint256() || *fromArrayType.baseType() == FixedBytesType(32), - ""); + "" + ); solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); solAssert( diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index a54b403d9..eba01d765 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -154,7 +154,7 @@ private: EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given memory array or - /// a given storage array with one item per slot. + /// a given storage array with every item occupies one or multiple full slots. std::string abiEncodingFunctionSimpleArray( ArrayType const& _givenType, ArrayType const& _targetType, diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 08d30f537..7c01a8c52 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -1055,28 +1055,27 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b switch (location) { case DataLocation::Memory: + // stack: + if (!_arrayType.isByteArray()) + m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL; + if (_arrayType.isDynamicallySized()) + m_context << u256(32) << Instruction::ADD; + if (_keepReference) + m_context << Instruction::DUP2; + m_context << Instruction::ADD; + break; case DataLocation::CallData: if (!_arrayType.isByteArray()) { - if (location == DataLocation::CallData) - { - if (_arrayType.baseType()->isDynamicallyEncoded()) - m_context << u256(0x20); - else - m_context << _arrayType.baseType()->calldataEncodedSize(); - } + if (_arrayType.baseType()->isDynamicallyEncoded()) + m_context << u256(0x20); else - m_context << u256(_arrayType.memoryHeadSize()); + m_context << _arrayType.baseType()->calldataEncodedSize(); m_context << Instruction::MUL; } // stack: - - if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - if (_keepReference) m_context << Instruction::DUP2; - m_context << Instruction::ADD; break; case DataLocation::Storage: @@ -1132,6 +1131,50 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b } } +void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const +{ + solAssert(_arrayType.location() == DataLocation::CallData, ""); + if (_arrayType.baseType()->isDynamicallyEncoded()) + { + // stack layout: + ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true); + // stack layout: + + CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType()); + // stack layout: [length] + } + else + { + ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck); + if (_arrayType.baseType()->isValueType()) + { + solAssert(_arrayType.baseType()->storageBytes() <= 32, ""); + if ( + !_arrayType.isByteArray() && + _arrayType.baseType()->storageBytes() < 32 && + m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) + ) + { + m_context << u256(32); + CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false); + } + else + CompilerUtils(m_context).loadFromMemoryDynamic( + *_arrayType.baseType(), + true, + !_arrayType.isByteArray(), + false + ); + } + else + solAssert( + _arrayType.baseType()->category() == Type::Category::Struct || + _arrayType.baseType()->category() == Type::Category::Array, + "Invalid statically sized non-value base type on array access." + ); + } +} + void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const { solAssert(_byteSize < 32, ""); diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h index 4c382a5ca..9cb5338ae 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -104,6 +104,10 @@ public: /// Stack post (storage): [reference] storage_slot byte_offset /// Stack post: [reference] memory/calldata_offset void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; + /// Access calldata array's element and put it on stack. + /// Stack pre: reference [length] index + /// Stack post: value + void accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; private: /// Adds the given number of bytes to a storage byte offset counter and also increments diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index e208a8c3c..8c65b8a19 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -32,8 +32,8 @@ #include #include #include +#include #include -#include #include #include @@ -422,9 +422,10 @@ void CompilerContext::appendInlineAssembly( if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { bool const isCreation = m_runtimeContext != nullptr; + yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); yul::OptimiserSuite::run( dialect, - yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment), + &meter, *parserResult, analysisInfo, _optimiserSettings.optimizeStackAllocation, diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index d6be20328..700b8db16 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -816,7 +816,12 @@ void CompilerUtils::convertType( } else { - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract || targetTypeCategory == Type::Category::Address, ""); + solAssert( + targetTypeCategory == Type::Category::Integer || + targetTypeCategory == Type::Category::Contract || + targetTypeCategory == Type::Category::Address, + "" + ); IntegerType addressType(160); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer ? dynamic_cast(_targetType) : addressType; @@ -841,9 +846,9 @@ void CompilerUtils::convertType( cleanHigherOrderBits(targetType); if (chopSignBitsPending) { - if (typeOnStack.numBits() < 256) + if (targetType.numBits() < 256) m_context - << ((u256(1) << typeOnStack.numBits()) - 1) + << ((u256(1) << targetType.numBits()) - 1) << Instruction::AND; chopSignBitsPending = false; } @@ -923,7 +928,6 @@ void CompilerUtils::convertType( // stack: (variably sized) if (targetType.baseType()->isValueType()) { - solAssert(typeOnStack.baseType()->isValueType(), ""); copyToStackTop(2 + stackSize, stackSize); ArrayUtils(m_context).copyArrayToMemory(typeOnStack); } @@ -957,10 +961,11 @@ void CompilerUtils::convertType( } case DataLocation::CallData: solAssert( - targetType.isByteArray() && - typeOnStack.isByteArray() && - typeOnStack.location() == DataLocation::CallData, - "Invalid conversion to calldata type."); + targetType.isByteArray() && + typeOnStack.isByteArray() && + typeOnStack.location() == DataLocation::CallData, + "Invalid conversion to calldata type." + ); break; } break; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index d50e4f4d5..6e2e74e45 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -54,7 +54,13 @@ class StackHeightChecker public: explicit StackHeightChecker(CompilerContext const& _context): m_context(_context), stackHeight(m_context.stackHeight()) {} - void check() { solAssert(m_context.stackHeight() == stackHeight, std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight)); } + void check() + { + solAssert( + m_context.stackHeight() == stackHeight, + std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight) + ); + } private: CompilerContext const& m_context; unsigned stackHeight; @@ -893,9 +899,9 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar // Local variable slots are reserved when their declaration is visited, // and freed in the end of their scope. - for (auto _decl: _variableDeclarationStatement.declarations()) - if (_decl) - appendStackVariableInitialisation(*_decl); + for (auto decl: _variableDeclarationStatement.declarations()) + if (decl) + appendStackVariableInitialisation(*decl); StackHeightChecker checker(m_context); if (Expression const* expression = _variableDeclarationStatement.initialValue()) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index bde4a167b..f393c6a6a 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -1588,45 +1588,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); break; case DataLocation::CallData: - if (arrayType.baseType()->isDynamicallyEncoded()) - { - // stack layout: - ArrayUtils(m_context).accessIndex(arrayType, true, true); - // stack layout: - - CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType()); - // stack layout: [length] - } - else - { - ArrayUtils(m_context).accessIndex(arrayType, true); - if (arrayType.baseType()->isValueType()) - { - solAssert(arrayType.baseType()->storageBytes() <= 32, ""); - if ( - !arrayType.isByteArray() && - arrayType.baseType()->storageBytes() < 32 && - m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) - ) - { - m_context << u256(32); - CompilerUtils(m_context).abiDecodeV2({arrayType.baseType()}, false); - } - else - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); - } - else - solAssert( - arrayType.baseType()->category() == Type::Category::Struct || - arrayType.baseType()->category() == Type::Category::Array, - "Invalid statically sized non-value base type on array access." - ); - } + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); break; } } diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index 2fe363e1b..13e410a74 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -224,34 +224,45 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits) solAssert(_numBits < 256, ""); string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return m_functionCollector->createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := + + shl(, value) + + mul(value, ) + + } + )") + ("functionName", functionName) + ("numBits", to_string(_numBits)) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); +} + +string YulUtilFunctions::shiftLeftFunctionDynamic() +{ + string functionName = "shift_left_dynamic"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shl(bits, value) + + mul(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); } string YulUtilFunctions::shiftRightFunction(size_t _numBits) @@ -261,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) // Note that if this is extended with signed shifts, // the opcodes SAR and SDIV behave differently with regards to rounding! - string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); + string functionName = "shift_right_" + to_string(_numBits) + "_unsigned"; return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( @@ -282,6 +293,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) }); } +string YulUtilFunctions::shiftRightFunctionDynamic() +{ + // Note that if this is extended with signed shifts, + // the opcodes SAR and SDIV behave differently with regards to rounding! + + string const functionName = "shift_right_unsigned_dynamic"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (bits, value) -> newValue { + newValue := + + shr(bits, value) + + div(value, exp(2, bits)) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + .render(); + }); +} + string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) { solAssert(_numBytes <= 32, ""); @@ -306,6 +341,29 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift }); } +string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes) +{ + solAssert(_numBytes <= 32, ""); + size_t numBits = _numBytes * 8; + string functionName = "update_byte_slice_dynamic" + to_string(_numBytes); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value, shiftBytes, toInsert) -> result { + let shiftBits := mul(shiftBytes, 8) + let mask := (shiftBits, ) + toInsert := (shiftBits, toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + )") + ("functionName", functionName) + ("mask", formatNumber((bigint(1) << numBits) - 1)) + ("shl", shiftLeftFunctionDynamic()) + .render(); + }); +} + string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; @@ -321,63 +379,70 @@ string YulUtilFunctions::roundUpFunction() }); } -string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits) +string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) { - solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); - string functionName = "checked_add_uint_" + to_string(_bits); + string functionName = "checked_add_" + _type.identifier(); + // TODO: Consider to add a special case for unsigned 256-bit integers + // and use the following instead: + // sum := add(x, y) if lt(sum, x) { revert(0, 0) } return m_functionCollector->createFunction(functionName, [&]() { return Whiskers(R"( function (x, y) -> sum { - - let mask := - sum := add(and(x, mask), and(y, mask)) - if and(sum, not(mask)) { revert(0, 0) } - - sum := add(x, y) - if lt(sum, x) { revert(0, 0) } - + + // overflow, if x >= 0 and y > (maxValue - x) + if and(iszero(slt(x, 0)), sgt(y, sub(, x))) { revert(0, 0) } + // underflow, if x < 0 and y < (minValue - x) + if and(slt(x, 0), slt(y, sub(, x))) { revert(0, 0) } + + // overflow, if x > (maxValue - y) + if gt(x, sub(, y)) { revert(0, 0) } + + sum := add(x, y) } )") - ("shortType", _bits < 256) ("functionName", functionName) - ("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } -string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits) +string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) { - solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, ""); - string functionName = "checked_mul_uint_" + to_string(_bits); + string functionName = "checked_mul_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return - // - The current overflow check *before* the multiplication could - // be replaced by the following check *after* the multiplication: - // if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) } - // - The case the x equals 0 could be treated separately and directly return zero. + // Multiplication by zero could be treated separately and directly return zero. Whiskers(R"( function (x, y) -> product { - if and(iszero(iszero(x)), lt(div(, x), y)) { revert(0, 0) } - - product := mulmod(x, y, ) - - product := mul(x, y) - + + // overflow, if x > 0, y > 0 and x > (maxValue / y) + if and(and(sgt(x, 0), sgt(y, 0)), gt(x, div(, y))) { revert(0, 0) } + // underflow, if x > 0, y < 0 and y < (minValue / x) + if and(and(sgt(x, 0), slt(y, 0)), slt(y, sdiv(, x))) { revert(0, 0) } + // underflow, if x < 0, y > 0 and x < (minValue / y) + if and(and(slt(x, 0), sgt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } + // overflow, if x < 0, y < 0 and x < (maxValue / y) + if and(and(slt(x, 0), slt(y, 0)), slt(x, sdiv(, y))) { revert(0, 0) } + + // overflow, if x != 0 and y > (maxValue / x) + if and(iszero(iszero(x)), gt(y, div(, x))) { revert(0, 0) } + + product := mul(x, y) } )") - ("shortType", _bits < 256) - ("functionName", functionName) - ("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits)) - ("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1)) - .render(); + ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); }); } string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) { - unsigned bits = _type.numBits(); - solAssert(0 < bits && bits <= 256 && bits % 8 == 0, ""); string functionName = "checked_div_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&]() { return @@ -385,7 +450,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) function (x, y) -> r { if iszero(y) { revert(0, 0) } - // x / -1 == x + // overflow for minVal / -1 if and( eq(x, ), eq(y, sub(0, 1)) @@ -394,25 +459,52 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) r := sdiv(x, y) } )") - ("functionName", functionName) - ("signed", _type.isSigned()) - ("minVal", (0 - (u256(1) << (bits - 1))).str()) - .render(); + ("functionName", functionName) + ("signed", _type.isSigned()) + ("minVal", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); }); } -string YulUtilFunctions::overflowCheckedUIntSubFunction() +string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) { - string functionName = "checked_sub_uint"; + string functionName = "checked_mod_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (x, y) -> r { + if iszero(y) { revert(0, 0) } + r := smod(x, y) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + .render(); + }); +} + +string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type) +{ + string functionName = "checked_sub_" + _type.identifier(); return m_functionCollector->createFunction(functionName, [&] { return Whiskers(R"( function (x, y) -> diff { - if lt(x, y) { revert(0, 0) } + + // underflow, if y >= 0 and x < (minValue + y) + if and(iszero(slt(y, 0)), slt(x, add(, y))) { revert(0, 0) } + // overflow, if y < 0 and x > (maxValue + y) + if and(slt(y, 0), sgt(x, add(, y))) { revert(0, 0) } + + if lt(x, y) { revert(0, 0) } + diff := sub(x, y) } )") ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) .render(); }); } @@ -459,6 +551,120 @@ string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) }); } +std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + solAssert(_type.isDynamicallySized(), ""); + solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!"); + solUnimplementedAssert(_type.baseType()->isValueType(), "..."); + solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "..."); + solUnimplementedAssert(_type.baseType()->storageSize() == 1, ""); + + string functionName = "resize_array_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, newLen) { + if gt(newLen, ) { + invalid() + } + + let oldLen := (array) + + // Store new length + sstore(array, newLen) + + // Size was reduced, clear end of array + if lt(newLen, oldLen) { + let oldSlotCount := (oldLen) + let newSlotCount := (newLen) + let arrayDataStart := (array) + let deleteStart := add(arrayDataStart, newSlotCount) + let deleteEnd := add(arrayDataStart, oldSlotCount) + (deleteStart, deleteEnd) + } + })") + ("functionName", functionName) + ("fetchLength", arrayLengthFunction(_type)) + ("convertToSize", arrayConvertLengthToSize(_type)) + ("dataPosition", arrayDataAreaFunction(_type)) + ("clearStorageRange", clearStorageRangeFunction(*_type.baseType())) + ("maxArrayLength", (u256(1) << 64).str()) + .render(); + }); +} + +string YulUtilFunctions::clearStorageRangeFunction(Type const& _type) +{ + string functionName = "clear_storage_range_" + _type.identifier(); + + solUnimplementedAssert(_type.isValueType(), "..."); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (start, end) { + for {} lt(start, end) { start := add(start, 1) } + { + sstore(start, 0) + } + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type) +{ + string functionName = "array_convert_length_to_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Type const& baseType = *_type.baseType(); + + switch (_type.location()) + { + case DataLocation::Storage: + { + unsigned const baseStorageBytes = baseType.storageBytes(); + solAssert(baseStorageBytes > 0, ""); + solAssert(32 / baseStorageBytes > 0, ""); + + return Whiskers(R"( + function (length) -> size { + size := length + + size := (, length) + + // Number of slots rounded up + size := div(add(length, sub(, 1)), ) + + })") + ("functionName", functionName) + ("multiSlot", baseType.storageSize() > 1) + ("itemsPerSlot", to_string(32 / baseStorageBytes)) + ("storageSize", baseType.storageSize().str()) + ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) + .render(); + } + case DataLocation::CallData: // fallthrough + case DataLocation::Memory: + return Whiskers(R"( + function (length) -> size { + + size := length + + size := (length, ) + + })") + ("functionName", functionName) + ("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize()) + ("byteArray", _type.isByteArray()) + ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) + .render(); + default: + solAssert(false, ""); + } + + }); +} string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) { solAssert(_type.dataStoredIn(DataLocation::Memory), ""); @@ -518,6 +724,36 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) }); } +string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type) +{ + solUnimplementedAssert(_type.baseType()->storageBytes() > 16, ""); + + string functionName = "storage_array_index_access_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (array, index) -> slot, offset { + if iszero(lt(index, (array))) { + invalid() + } + + let data := (array) + + + + slot := add(data, mul(index, )) + offset := 0 + + } + )") + ("functionName", functionName) + ("arrayLen", arrayLengthFunction(_type)) + ("dataAreaFunc", arrayDataAreaFunction(_type)) + ("multipleItemsPerSlot", _type.baseType()->storageBytes() <= 16) + ("storageSize", _type.baseType()->storageSize().str()) + .render(); + }); +} + string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) { solAssert(!_type.isByteArray(), ""); @@ -531,18 +767,29 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) } )"); templ("functionName", functionName); - if (_type.location() == DataLocation::Memory) + switch (_type.location()) + { + case DataLocation::Memory: templ("advance", "0x20"); - else if (_type.location() == DataLocation::Storage) - templ("advance", "1"); - else if (_type.location() == DataLocation::CallData) - templ("advance", toCompactHexWithPrefix( + break; + case DataLocation::Storage: + { + u256 size = _type.baseType()->storageSize(); + solAssert(size >= 1, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + case DataLocation::CallData: + { + u256 size = _type.baseType()->isDynamicallyEncoded() ? 32 : - _type.baseType()->calldataEncodedSize() - )); - else - solAssert(false, ""); + _type.baseType()->calldataEncodedSize(); + solAssert(size >= 32 && size % 32 == 0, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + } return templ.render(); }); } @@ -613,6 +860,89 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool }); } +string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + string functionName = + "read_from_storage_dynamic" + + string(_splitFunctionTypes ? "split_" : "") + + "_" + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + solAssert(_type.sizeOnStack() == 1, ""); + return Whiskers(R"( + function (slot, offset) -> value { + value := (sload(slot), offset) + } + )") + ("functionName", functionName) + ("extract", extractFromStorageValueDynamic(_type, _splitFunctionTypes)) + .render(); + }); +} + +string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional const _offset) +{ + string const functionName = + "update_storage_value_" + + (_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") + + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&] { + if (_type.isValueType()) + { + solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(_type.storageBytes() > 0, "Invalid storage bytes size."); + + return Whiskers(R"( + function (slot, value) { + sstore(slot, (sload(slot), (value))) + } + + )") + ("functionName", functionName) + ("update", + _offset.is_initialized() ? + updateByteSliceFunction(_type.storageBytes(), *_offset) : + updateByteSliceFunctionDynamic(_type.storageBytes()) + ) + ("offset", _offset.is_initialized() ? "" : "offset, ") + ("prepare", prepareStoreFunction(_type)) + .render(); + } + else + { + if (_type.category() == Type::Category::Array) + solUnimplementedAssert(false, ""); + else if (_type.category() == Type::Category::Struct) + solUnimplementedAssert(false, ""); + else + solAssert(false, "Invalid non-value type for assignment."); + } + }); +} + +string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + + string functionName = + "extract_from_storage_value_dynamic" + + string(_splitFunctionTypes ? "split_" : "") + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + return Whiskers(R"( + function (slot_value, offset) -> value { + value := ((mul(offset, 8), slot_value)) + } + )") + ("functionName", functionName) + ("shr", shiftRightFunctionDynamic()) + ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) + .render(); + }); +} + string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) { solUnimplementedAssert(!_splitFunctionTypes, ""); @@ -631,7 +961,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs )") ("functionName", functionName) ("shr", shiftRightFunction(_offset * 8)) - ("cleanupStorage", cleanupFromStorageFunction(_type, false)) + ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) .render(); }); } diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index 245d29c12..d9a1e4a7f 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -74,31 +74,64 @@ public: std::string leftAlignFunction(Type const& _type); std::string shiftLeftFunction(size_t _numBits); + std::string shiftLeftFunctionDynamic(); std::string shiftRightFunction(size_t _numBits); + std::string shiftRightFunctionDynamic(); - /// @returns the name of a function f(value, toInsert) -> newValue which replaces the + /// @returns the name of a function which replaces the /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// byte) by the _numBytes least significant bytes of `toInsert`. + /// signature: (value, toInsert) -> result std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); + /// signature: (value, shiftBytes, toInsert) -> result + std::string updateByteSliceFunctionDynamic(size_t _numBytes); + /// @returns the name of a function that rounds its input to the next multiple /// of 32 or the input if it is a multiple of 32. + /// signature: (value) -> result std::string roundUpFunction(); - std::string overflowCheckedUIntAddFunction(size_t _bits); + /// signature: (x, y) -> sum + std::string overflowCheckedIntAddFunction(IntegerType const& _type); - std::string overflowCheckedUIntMulFunction(size_t _bits); + /// signature: (x, y) -> product + std::string overflowCheckedIntMulFunction(IntegerType const& _type); /// @returns name of function to perform division on integers. /// Checks for division by zero and the special case of /// signed division of the smallest number by -1. std::string overflowCheckedIntDivFunction(IntegerType const& _type); + /// @returns name of function to perform modulo on integers. + /// Reverts for modulo by zero. + std::string checkedIntModFunction(IntegerType const& _type); + /// @returns computes the difference between two values. /// Assumes the input to be in range for the type. - std::string overflowCheckedUIntSubFunction(); + /// signature: (x, y) -> diff + std::string overflowCheckedIntSubFunction(IntegerType const& _type); + /// @returns the name of a function that fetches the length of the given + /// array + /// signature: (array) -> length std::string arrayLengthFunction(ArrayType const& _type); + + /// @returns the name of a function that resizes a storage array + /// signature: (array, newLen) + std::string resizeDynamicArrayFunction(ArrayType const& _type); + + /// @returns the name of a function that will clear the storage area given + /// by the start and end (exclusive) parameters (slots). Only works for value types. + /// signature: (start, end) + std::string clearStorageRangeFunction(Type const& _type); + + /// Returns the name of a function that will convert a given length to the + /// size in memory (number of storage slots or calldata/memory bytes) it + /// will require. + /// signature: (length) -> size + std::string arrayConvertLengthToSize(ArrayType const& _type); + /// @returns the name of a function that computes the number of bytes required /// to store an array in memory given its length (internally encoded, not ABI encoded). /// The function reverts for too large lengths. @@ -107,8 +140,14 @@ public: /// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer /// for the data position of an array which is stored in that slot / memory area / calldata area. std::string arrayDataAreaFunction(ArrayType const& _type); + + /// @returns the name of a function that returns the slot and offset for the + /// given array and index + /// signature: (array, index) -> slot, offset + std::string storageArrayIndexAccessFunction(ArrayType const& _type); + /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot. + /// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots. std::string nextArrayElementFunction(ArrayType const& _type); /// @returns the name of a function that performs index access for mappings. @@ -121,6 +160,7 @@ public: /// @param _splitFunctionTypes if false, returns the address and function signature in a /// single variable. std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes); + std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes); /// @returns a function that extracts a value type from storage slot that has been /// retrieved already. @@ -128,6 +168,13 @@ public: /// @param _splitFunctionTypes if false, returns the address and function signature in a /// single variable. std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); + std::string extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes); + + /// Returns the name of a function will write the given value to + /// the specified slot and offset. If offset is not given, it is expected as + /// runtime parameter. + /// signature: (slot, [offset,] value) + std::string updateStorageValueFunction(Type const& _type, boost::optional const _offset = boost::optional()); /// Performs cleanup after reading from a potentially compressed storage slot. /// The function does not perform any validation, it just masks or sign-extends diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index acf74805a..34c2bf3b3 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -45,8 +46,7 @@ using namespace dev::solidity; pair IRGenerator::run(ContractDefinition const& _contract) { - // TODO Would be nice to pretty-print this while retaining comments. - string ir = generate(_contract); + string const ir = yul::reindent(generate(_contract)); yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); if (!asmStack.parseAndAnalyze("", ir)) @@ -54,7 +54,7 @@ pair IRGenerator::run(ContractDefinition const& _contract) string errorMessage; for (auto const& error: asmStack.errors()) errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); - solAssert(false, "Invalid IR generated:\n" + errorMessage + "\n" + ir); + solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n"); } asmStack.optimize(); diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index d5975b717..2bd593171 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -56,6 +56,35 @@ struct CopyTranslate: public yul::ASTCopier using ASTCopier::operator(); + yul::Expression operator()(yul::Identifier const& _identifier) override + { + if (m_references.count(&_identifier)) + { + auto const& reference = m_references.at(&_identifier); + auto const varDecl = dynamic_cast(reference.declaration); + solUnimplementedAssert(varDecl, ""); + + if (reference.isOffset || reference.isSlot) + { + solAssert(reference.isOffset != reference.isSlot, ""); + + pair slot_offset = m_context.storageLocationOfVariable(*varDecl); + + string const value = reference.isSlot ? + slot_offset.first.str() : + to_string(slot_offset.second); + + return yul::Literal{ + _identifier.location, + yul::LiteralKind::Number, + yul::YulString{value}, + yul::YulString{"uint256"} + }; + } + } + return ASTCopier::operator()(_identifier); + } + yul::YulString translateIdentifier(yul::YulString _name) override { // Strictly, the dialect used by inline assembly (m_dialect) could be different @@ -76,9 +105,10 @@ struct CopyTranslate: public yul::ASTCopier auto const& reference = m_references.at(&_identifier); auto const varDecl = dynamic_cast(reference.declaration); solUnimplementedAssert(varDecl, ""); - solUnimplementedAssert( + + solAssert( reference.isOffset == false && reference.isSlot == false, - "" + "Should not be called for offset/slot" ); return yul::Identifier{ @@ -550,13 +580,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert"); solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert"); + Type const* messageArgumentType = arguments.size() > 1 ? arguments[1]->annotation().type : nullptr; string requireOrAssertFunction = m_utils.requireOrAssertFunction( functionType->kind() == FunctionType::Kind::Assert, - arguments.size() > 1 ? arguments[1]->annotation().type : nullptr + messageArgumentType ); m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]); - if (arguments.size() > 1) + if (messageArgumentType && messageArgumentType->sizeOnStack() > 0) m_code << ", " << m_context.variable(*arguments[1]); m_code << ")\n"; @@ -702,7 +733,35 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) } case Type::Category::Array: { - solUnimplementedAssert(false, ""); + auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + + solAssert(member == "length", ""); + + if (!type.isDynamicallySized()) + defineExpression(_memberAccess) << type.length() << "\n"; + else + switch (type.location()) + { + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + //m_context << Instruction::SWAP1 << Instruction::POP; + break; + case DataLocation::Storage: + setLValue(_memberAccess, make_unique( + m_context, + m_context.variable(_memberAccess.expression()), + *_memberAccess.annotation().type, + type + )); + + break; + case DataLocation::Memory: + solUnimplementedAssert(false, ""); + //m_context << Instruction::MLOAD; + break; + } + + break; } case Type::Category::FixedBytes: { @@ -761,7 +820,45 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) )); } else if (baseType.category() == Type::Category::Array) - solUnimplementedAssert(false, ""); + { + ArrayType const& arrayType = dynamic_cast(baseType); + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + switch (arrayType.location()) + { + case DataLocation::Storage: + { + string slot = m_context.newYulVariable(); + string offset = m_context.newYulVariable(); + + m_code << Whiskers(R"( + let , := (, ) + )") + ("slot", slot) + ("offset", offset) + ("indexFunc", m_utils.storageArrayIndexAccessFunction(arrayType)) + ("array", m_context.variable(_indexAccess.baseExpression())) + ("index", m_context.variable(*_indexAccess.indexExpression())) + .render(); + + setLValue(_indexAccess, make_unique( + m_context, + slot, + offset, + *_indexAccess.annotation().type + )); + + break; + } + case DataLocation::Memory: + solUnimplementedAssert(false, ""); + break; + case DataLocation::CallData: + solUnimplementedAssert(false, ""); + break; + } + + } else if (baseType.category() == Type::Category::FixedBytes) solUnimplementedAssert(false, ""); else if (baseType.category() == Type::Category::TypeType) @@ -1097,18 +1194,28 @@ string IRGeneratorForStatements::binaryOperation( if (IntegerType const* type = dynamic_cast(&_type)) { string fun; - // TODO: Only division is implemented for signed integers for now. - if (!type->isSigned()) + // TODO: Implement all operations for signed and unsigned types. + switch (_operator) { - if (_operator == Token::Add) - fun = m_utils.overflowCheckedUIntAddFunction(type->numBits()); - else if (_operator == Token::Sub) - fun = m_utils.overflowCheckedUIntSubFunction(); - else if (_operator == Token::Mul) - fun = m_utils.overflowCheckedUIntMulFunction(type->numBits()); + case Token::Add: + fun = m_utils.overflowCheckedIntAddFunction(*type); + break; + case Token::Sub: + fun = m_utils.overflowCheckedIntSubFunction(*type); + break; + case Token::Mul: + fun = m_utils.overflowCheckedIntMulFunction(*type); + break; + case Token::Div: + fun = m_utils.overflowCheckedIntDivFunction(*type); + break; + case Token::Mod: + fun = m_utils.checkedIntModFunction(*type); + break; + default: + break; } - if (_operator == Token::Div) - fun = m_utils.overflowCheckedIntDivFunction(*type); + solUnimplementedAssert(!fun.empty(), ""); return fun + "(" + _left + ", " + _right + ")\n"; } diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp index 4c3fe1acf..f1e5cbc7c 100644 --- a/libsolidity/codegen/ir/IRLValue.cpp +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -55,25 +55,36 @@ IRStorageItem::IRStorageItem( IRGenerationContext& _context, VariableDeclaration const& _varDecl ): - IRLValue(_context, _varDecl.annotation().type) + IRStorageItem( + _context, + *_varDecl.annotation().type, + _context.storageLocationOfVariable(_varDecl) +) +{ } + +IRStorageItem::IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset +): + IRLValue(_context, &_type), + m_slot(toCompactHexWithPrefix(slot_offset.first)), + m_offset(slot_offset.second) { - u256 slot; - unsigned offset; - std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl); - m_slot = toCompactHexWithPrefix(slot); - m_offset = offset; } IRStorageItem::IRStorageItem( IRGenerationContext& _context, string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ): IRLValue(_context, &_type), m_slot(move(_slot)), - m_offset(_offset) + m_offset(std::move(_offset)) { + solAssert(!m_offset.empty(), ""); + solAssert(!m_slot.empty(), ""); } string IRStorageItem::retrieveValue() const @@ -81,42 +92,77 @@ string IRStorageItem::retrieveValue() const if (!m_type->isValueType()) return m_slot; solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); - return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; + if (m_offset.type() == typeid(string)) + return + m_context.utils().readFromStorageDynamic(*m_type, false) + + "(" + + m_slot + + ", " + + boost::get(m_offset) + + ")"; + else if (m_offset.type() == typeid(unsigned)) + return + m_context.utils().readFromStorage(*m_type, boost::get(m_offset), false) + + "(" + + m_slot + + ")"; + + solAssert(false, ""); } string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const { if (m_type->isValueType()) - { - solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size."); - solAssert(m_type->storageBytes() + m_offset <= 32, ""); - solAssert(_sourceType == *m_type, "Different type, but might not be an error."); - return Whiskers("sstore(, (sload(), ()))\n") - ("slot", m_slot) - ("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) - ("prepare", m_context.utils().prepareStoreFunction(*m_type)) - ("value", _value) - .render(); - } - else - { - solAssert( - _sourceType.category() == m_type->category(), - "Wrong type conversation for assignment." - ); - if (m_type->category() == Type::Category::Array) - solUnimplementedAssert(false, ""); - else if (m_type->category() == Type::Category::Struct) - solUnimplementedAssert(false, ""); - else - solAssert(false, "Invalid non-value type for assignment."); - } + boost::optional offset; + + if (m_offset.type() == typeid(unsigned)) + offset = get(m_offset); + + return + m_context.utils().updateStorageValueFunction(*m_type, offset) + + "(" + + m_slot + + (m_offset.type() == typeid(string) ? + (", " + get(m_offset)) : + "" + ) + + ", " + + _value + + ")\n"; } string IRStorageItem::setToZero() const { - solUnimplemented("Delete for storage location not yet implemented"); + solUnimplementedAssert(m_type->isValueType(), ""); + return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type); +} + +IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType): + IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot)) +{ + solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!"); +} + +string IRStorageArrayLength::retrieveValue() const +{ + return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n"; +} + +string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const +{ + solAssert(_type == *m_type, "Different type, but might not be an error."); + + return m_context.utils().resizeDynamicArrayFunction(m_arrayType) + + "(" + + m_slot + + ", " + + _value + + ")\n"; +} + +string IRStorageArrayLength::setToZero() const +{ + return storeValue("0", *TypeProvider::uint256()); } diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 98ad987f1..f8ac5b6be 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -20,8 +20,11 @@ #pragma once +#include + #include #include +#include namespace dev { @@ -31,6 +34,7 @@ namespace solidity class VariableDeclaration; class IRGenerationContext; class Type; +class ArrayType; /** * Abstract class used to retrieve, delete and store data in LValues. @@ -83,7 +87,7 @@ public: IRStorageItem( IRGenerationContext& _context, std::string _slot, - unsigned _offset, + boost::variant _offset, Type const& _type ); std::string retrieveValue() const override; @@ -91,10 +95,37 @@ public: std::string setToZero() const override; private: - std::string m_slot; - unsigned m_offset; + IRStorageItem( + IRGenerationContext& _context, + Type const& _type, + std::pair slot_offset + ); + + std::string const m_slot; + /// unsigned: Used when the offset is known at compile time, uses optimized + /// functions + /// string: Used when the offset is determined at run time + boost::variant const m_offset; }; +/** + * Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special + * semantics since assignments to it might reduce its length and thus the array's members have to be + * deleted. + */ +class IRStorageArrayLength: public IRLValue +{ +public: + IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType); + + std::string retrieveValue() const override; + std::string storeValue(std::string const& _value, Type const& _type) const override; + std::string setToZero() const override; + +private: + ArrayType const& m_arrayType; + std::string const m_slot; +}; } } diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 71f657471..909202094 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -60,17 +60,21 @@ void CVC4Interface::addAssertion(Expression const& _expr) { m_solver.assertFormula(toCVC4Expr(_expr)); } - catch (CVC4::TypeCheckingException const&) + catch (CVC4::TypeCheckingException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); } - catch (CVC4::LogicException const&) + catch (CVC4::LogicException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); } - catch (CVC4::UnsafeInterruptException const&) + catch (CVC4::UnsafeInterruptException const& _e) { - solAssert(false, ""); + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); } } @@ -120,58 +124,82 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) for (auto const& arg: _expr.arguments) arguments.push_back(toCVC4Expr(arg)); - string const& n = _expr.name; - // Function application - if (!arguments.empty() && m_variables.count(_expr.name)) - return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments); - // Literal - else if (arguments.empty()) + try { - if (n == "true") - return m_context.mkConst(true); - else if (n == "false") - return m_context.mkConst(false); - else - // We assume it is an integer... - return m_context.mkConst(CVC4::Rational(n)); + string const& n = _expr.name; + // Function application + if (!arguments.empty() && m_variables.count(_expr.name)) + return m_context.mkExpr(CVC4::kind::APPLY_UF, m_variables.at(n), arguments); + // Literal + else if (arguments.empty()) + { + if (n == "true") + return m_context.mkConst(true); + else if (n == "false") + return m_context.mkConst(false); + else + try + { + return m_context.mkConst(CVC4::Rational(n)); + } + catch (CVC4::TypeCheckingException const& _e) + { + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); + } + } + + solAssert(_expr.hasCorrectArity(), ""); + if (n == "ite") + return arguments[0].iteExpr(arguments[1], arguments[2]); + else if (n == "not") + return arguments[0].notExpr(); + else if (n == "and") + return arguments[0].andExpr(arguments[1]); + else if (n == "or") + return arguments[0].orExpr(arguments[1]); + else if (n == "implies") + return m_context.mkExpr(CVC4::kind::IMPLIES, arguments[0], arguments[1]); + else if (n == "=") + return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]); + else if (n == "<") + return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]); + else if (n == "<=") + return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]); + else if (n == ">") + return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]); + else if (n == ">=") + return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]); + else if (n == "+") + return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]); + else if (n == "-") + return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]); + else if (n == "*") + return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]); + else if (n == "/") + return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]); + else if (n == "mod") + return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]); + else if (n == "select") + 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]); + + solAssert(false, ""); + } + catch (CVC4::TypeCheckingException const& _e) + { + solAssert(false, _e.what()); + } + catch (CVC4::Exception const& _e) + { + solAssert(false, _e.what()); } - solAssert(_expr.hasCorrectArity(), ""); - if (n == "ite") - return arguments[0].iteExpr(arguments[1], arguments[2]); - else if (n == "not") - return arguments[0].notExpr(); - else if (n == "and") - return arguments[0].andExpr(arguments[1]); - else if (n == "or") - return arguments[0].orExpr(arguments[1]); - else if (n == "=") - return m_context.mkExpr(CVC4::kind::EQUAL, arguments[0], arguments[1]); - else if (n == "<") - return m_context.mkExpr(CVC4::kind::LT, arguments[0], arguments[1]); - else if (n == "<=") - return m_context.mkExpr(CVC4::kind::LEQ, arguments[0], arguments[1]); - else if (n == ">") - return m_context.mkExpr(CVC4::kind::GT, arguments[0], arguments[1]); - else if (n == ">=") - return m_context.mkExpr(CVC4::kind::GEQ, arguments[0], arguments[1]); - else if (n == "+") - return m_context.mkExpr(CVC4::kind::PLUS, arguments[0], arguments[1]); - else if (n == "-") - return m_context.mkExpr(CVC4::kind::MINUS, arguments[0], arguments[1]); - else if (n == "*") - return m_context.mkExpr(CVC4::kind::MULT, arguments[0], arguments[1]); - else if (n == "/") - return m_context.mkExpr(CVC4::kind::INTS_DIVISION_TOTAL, arguments[0], arguments[1]); - else if (n == "mod") - return m_context.mkExpr(CVC4::kind::INTS_MODULUS, arguments[0], arguments[1]); - else if (n == "select") - 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]); - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort) diff --git a/libsolidity/formal/EncodingContext.cpp b/libsolidity/formal/EncodingContext.cpp index 150b5813d..2442c7df7 100644 --- a/libsolidity/formal/EncodingContext.cpp +++ b/libsolidity/formal/EncodingContext.cpp @@ -23,15 +23,15 @@ using namespace std; using namespace dev; using namespace dev::solidity::smt; -EncodingContext::EncodingContext(SolverInterface& _solver): - m_solver(_solver), - m_thisAddress(make_unique("this", m_solver)) +EncodingContext::EncodingContext(std::shared_ptr _solver): + m_thisAddress(make_unique("this", *_solver)), + m_solver(_solver) { auto sort = make_shared( make_shared(Kind::Int), make_shared(Kind::Int) ); - m_balances = make_unique(sort, "balances", m_solver); + m_balances = make_unique(sort, "balances", *m_solver); } void EncodingContext::reset() @@ -41,6 +41,7 @@ void EncodingContext::reset() m_globalContext.clear(); m_thisAddress->increaseIndex(); m_balances->increaseIndex(); + m_assertions.clear(); } /// Variables. @@ -55,7 +56,7 @@ bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDe { solAssert(!knownVariable(_varDecl), ""); auto const& type = _varDecl.type(); - auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver); + auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_solver); m_variables.emplace(&_varDecl, result.second); return result.first; } @@ -105,7 +106,7 @@ void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl) void EncodingContext::setZeroValue(SymbolicVariable& _variable) { - setSymbolicZeroValue(_variable, m_solver); + setSymbolicZeroValue(_variable, *m_solver); } void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl) @@ -116,7 +117,7 @@ void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl void EncodingContext::setUnknownValue(SymbolicVariable& _variable) { - setSymbolicUnknownValue(_variable, m_solver); + setSymbolicUnknownValue(_variable, *m_solver); } /// Expressions @@ -143,7 +144,7 @@ bool EncodingContext::createExpression(solidity::Expression const& _e, shared_pt } else { - auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver); + auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_solver); m_expressions.emplace(&_e, result.second); return result.first; } @@ -165,7 +166,7 @@ shared_ptr EncodingContext::globalSymbol(string const& _name) bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr) { solAssert(!knownGlobalSymbol(_name), ""); - auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver); + auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_solver); m_globalContext.emplace(_name, result.second); setUnknownValue(*result.second); return result.first; @@ -207,9 +208,40 @@ void EncodingContext::transfer(Expression _from, Expression _to, Expression _val m_balances->valueAtIndex(indexBefore), m_balances->valueAtIndex(indexAfter) ); - m_solver.addAssertion(m_balances->currentValue() == newBalances); + m_solver->addAssertion(m_balances->currentValue() == newBalances); } +/// Solver. + +Expression EncodingContext::assertions() +{ + if (m_assertions.empty()) + return Expression(true); + + return m_assertions.back(); +} + +void EncodingContext::pushSolver() +{ + m_assertions.push_back(assertions()); +} + +void EncodingContext::popSolver() +{ + solAssert(!m_assertions.empty(), ""); + m_assertions.pop_back(); +} + +void EncodingContext::addAssertion(Expression const& _expr) +{ + if (m_assertions.empty()) + m_assertions.push_back(_expr); + else + m_assertions.back() = _expr && move(m_assertions.back()); +} + +/// Private helpers. + void EncodingContext::addBalance(Expression _address, Expression _value) { auto newBalances = Expression::store( @@ -218,5 +250,5 @@ void EncodingContext::addBalance(Expression _address, Expression _value) balance(_address) + move(_value) ); m_balances->increaseIndex(); - m_solver.addAssertion(newBalances == m_balances->currentValue()); + m_solver->addAssertion(newBalances == m_balances->currentValue()); } diff --git a/libsolidity/formal/EncodingContext.h b/libsolidity/formal/EncodingContext.h index 4a7d05863..4532d6fb4 100644 --- a/libsolidity/formal/EncodingContext.h +++ b/libsolidity/formal/EncodingContext.h @@ -36,12 +36,12 @@ namespace smt class EncodingContext { public: - EncodingContext(SolverInterface& _solver); + EncodingContext(std::shared_ptr _solver); /// Resets the entire context. void reset(); - /// Methods related to variables. + /// Variables. //@{ /// @returns the symbolic representation of a program variable. std::shared_ptr variable(solidity::VariableDeclaration const& _varDecl); @@ -74,7 +74,7 @@ public: void setUnknownValue(SymbolicVariable& _variable); //@} - /// Methods related to expressions. + /// Expressions. ////@{ /// @returns the symbolic representation of an AST node expression. std::shared_ptr expression(solidity::Expression const& _e); @@ -88,12 +88,13 @@ public: bool knownExpression(solidity::Expression const& _e) const; //@} - /// Methods related to global variables and functions. + /// Global variables and functions. //@{ /// Global variables and functions. std::shared_ptr globalSymbol(std::string const& _name); - /// @returns all symbolic variables. + /// @returns all symbolic globals. std::unordered_map> const& globalSymbols() const { return m_globalContext; } + /// Defines a new global variable or function /// and @returns true if type was abstracted. bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr); @@ -101,7 +102,7 @@ public: bool knownGlobalSymbol(std::string const& _var) const; //@} - /// Blockchain related methods. + /// Blockchain. //@{ /// Value of `this` address. Expression thisAddress(); @@ -113,12 +114,22 @@ public: void transfer(Expression _from, Expression _to, Expression _value); //@} + /// Solver. + //@{ + /// @returns conjunction of all added assertions. + Expression assertions(); + void pushSolver(); + void popSolver(); + void addAssertion(Expression const& _e); + std::shared_ptr solver() { return m_solver; } + //@} + private: /// Adds _value to _account's balance. void addBalance(Expression _account, Expression _value); - SolverInterface& m_solver; - + /// Symbolic expressions. + //{@ /// Symbolic variables. std::unordered_map> m_variables; @@ -134,6 +145,16 @@ private: /// Symbolic balances. std::unique_ptr m_balances; + //@} + + /// Solver related. + //@{ + /// Solver can be SMT solver or Horn solver in the future. + std::shared_ptr m_solver; + + /// Assertion stack. + std::vector m_assertions; + //@} }; } diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index dbd287876..484a3afaf 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -34,10 +34,10 @@ using namespace langutil; using namespace dev::solidity; SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): - m_interface(make_unique(_smtlib2Responses)), + m_interface(make_shared(_smtlib2Responses)), m_errorReporterReference(_errorReporter), m_errorReporter(m_smtErrors), - m_context(*m_interface) + m_context(m_interface) { #if defined (HAVE_Z3) || defined (HAVE_CVC4) if (!_smtlib2Responses.empty()) @@ -52,9 +52,12 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, map const& _ void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) { + if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) + return; + m_scanner = _scanner; - if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) - _source.accept(*this); + + _source.accept(*this); solAssert(m_interface->solvers() > 0, ""); // If this check is true, Z3 and CVC4 are not available diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 54254c3a6..c3e973528 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -19,7 +19,7 @@ #include -#include +#include #include #include @@ -270,7 +270,7 @@ private: /// @returns the VariableDeclaration referenced by an Identifier or nullptr. VariableDeclaration const* identifierToVariable(Expression const& _expr); - std::unique_ptr m_interface; + std::shared_ptr m_interface; smt::VariableUsage m_variableUsage; bool m_loopExecutionHappened = false; bool m_arrayAssignmentHappened = false; diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index ebe98261f..3eb0a793b 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -52,6 +52,7 @@ public: void declareVariable(std::string const&, Sort const&) override; void addAssertion(Expression const& _expr) override; + std::pair> check(std::vector const& _expressionsToEvaluate) override; std::vector unhandledQueries() override; @@ -60,6 +61,8 @@ private: static bool solverAnswered(CheckResult result); std::vector> m_solvers; + + std::vector m_assertions; }; } diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 0c7f1dbdf..5610a5fd6 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -133,6 +133,7 @@ public: {"not", 1}, {"and", 2}, {"or", 2}, + {"implies", 2}, {"=", 2}, {"<", 2}, {"<=", 2}, @@ -160,7 +161,12 @@ public: static Expression implies(Expression _a, Expression _b) { - return !std::move(_a) || std::move(_b); + return Expression( + "implies", + std::move(_a), + std::move(_b), + Kind::Bool + ); } /// select is the SMT representation of an array index access. diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 4d7ea8347..1039dd1f8 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -116,61 +116,77 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr) for (auto const& arg: _expr.arguments) arguments.push_back(toZ3Expr(arg)); - string const& n = _expr.name; - if (m_functions.count(n)) - return m_functions.at(n)(arguments); - else if (m_constants.count(n)) + try { - solAssert(arguments.empty(), ""); - return m_constants.at(n); + string const& n = _expr.name; + if (m_functions.count(n)) + return m_functions.at(n)(arguments); + else if (m_constants.count(n)) + { + solAssert(arguments.empty(), ""); + return m_constants.at(n); + } + else if (arguments.empty()) + { + if (n == "true") + return m_context.bool_val(true); + else if (n == "false") + return m_context.bool_val(false); + else + try + { + return m_context.int_val(n.c_str()); + } + catch (z3::exception const& _e) + { + solAssert(false, _e.msg()); + } + } + + solAssert(_expr.hasCorrectArity(), ""); + if (n == "ite") + return z3::ite(arguments[0], arguments[1], arguments[2]); + else if (n == "not") + return !arguments[0]; + else if (n == "and") + return arguments[0] && arguments[1]; + else if (n == "or") + return arguments[0] || arguments[1]; + else if (n == "implies") + return z3::implies(arguments[0], arguments[1]); + else if (n == "=") + return arguments[0] == arguments[1]; + else if (n == "<") + return arguments[0] < arguments[1]; + else if (n == "<=") + return arguments[0] <= arguments[1]; + else if (n == ">") + return arguments[0] > arguments[1]; + else if (n == ">=") + return arguments[0] >= arguments[1]; + else if (n == "+") + return arguments[0] + arguments[1]; + else if (n == "-") + return arguments[0] - arguments[1]; + else if (n == "*") + return arguments[0] * arguments[1]; + else if (n == "/") + return arguments[0] / arguments[1]; + else if (n == "mod") + return z3::mod(arguments[0], arguments[1]); + else if (n == "select") + return z3::select(arguments[0], arguments[1]); + else if (n == "store") + return z3::store(arguments[0], arguments[1], arguments[2]); + + solAssert(false, ""); } - else if (arguments.empty()) + catch (z3::exception const& _e) { - if (n == "true") - return m_context.bool_val(true); - else if (n == "false") - return m_context.bool_val(false); - else - // We assume it is an integer... - return m_context.int_val(n.c_str()); + solAssert(false, _e.msg()); } - solAssert(_expr.hasCorrectArity(), ""); - if (n == "ite") - return z3::ite(arguments[0], arguments[1], arguments[2]); - else if (n == "not") - return !arguments[0]; - else if (n == "and") - return arguments[0] && arguments[1]; - else if (n == "or") - return arguments[0] || arguments[1]; - else if (n == "=") - return arguments[0] == arguments[1]; - else if (n == "<") - return arguments[0] < arguments[1]; - else if (n == "<=") - return arguments[0] <= arguments[1]; - else if (n == ">") - return arguments[0] > arguments[1]; - else if (n == ">=") - return arguments[0] >= arguments[1]; - else if (n == "+") - return arguments[0] + arguments[1]; - else if (n == "-") - return arguments[0] - arguments[1]; - else if (n == "*") - return arguments[0] * arguments[1]; - else if (n == "/") - return arguments[0] / arguments[1]; - else if (n == "mod") - return z3::mod(arguments[0], arguments[1]); - else if (n == "select") - return z3::select(arguments[0], arguments[1]); - else if (n == "store") - return z3::store(arguments[0], arguments[1], arguments[2]); - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } z3::sort Z3Interface::z3Sort(Sort const& _sort) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 2910a9d95..c7846c8e2 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -217,7 +217,7 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errorReporter, m_evmVersion).parse(source.scanner); + source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner); if (!source.ast) solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error."); else diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index de90553cd..25718e698 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -132,6 +132,14 @@ public: /// Must be set before parsing. void setOptimiserSettings(OptimiserSettings _settings); + /// Set whether or not parser error is desired. + /// When called without an argument it will revert to the default. + /// Must be set before parsing. + void setParserErrorRecovery(bool _wantErrorRecovery = false) + { + m_parserErrorRecovery = _wantErrorRecovery; + } + /// Set the EVM version used before running compile. /// When called without an argument it will revert to the default version. /// Must be set before parsing. @@ -386,6 +394,7 @@ private: langutil::ErrorList m_errorList; langutil::ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; + bool m_parserErrorRecovery = false; State m_stackState = Empty; bool m_release = VersionIsRelease; }; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 8cd8dea8a..5e7d7e118 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -301,7 +301,7 @@ boost::optional checkAuxiliaryInputKeys(Json::Value const& _input) boost::optional checkSettingsKeys(Json::Value const& _input) { - static set keys{"evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; + static set keys{"parserErrorRecovery", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; return checkKeys(_input, keys, "settings"); } @@ -576,6 +576,13 @@ boost::variant StandardCompile if (auto result = checkSettingsKeys(settings)) return *result; + if (settings.isMember("parserErrorRecovery")) + { + if (!settings["parserErrorRecovery"].isBool()) + return formatFatalError("JSONError", "\"settings.parserErrorRecovery\" must be a Boolean."); + ret.parserErrorRecovery = settings["parserErrorRecovery"].asBool(); + } + if (settings.isMember("evmVersion")) { if (!settings["evmVersion"].isString()) @@ -675,6 +682,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses) compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second); compilerStack.setEVMVersion(_inputsAndSettings.evmVersion); + compilerStack.setParserErrorRecovery(_inputsAndSettings.parserErrorRecovery); compilerStack.setRemappings(_inputsAndSettings.remappings); compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings)); compilerStack.setLibraries(_inputsAndSettings.libraries); @@ -769,6 +777,15 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting "Exception during compilation: " + boost::diagnostic_information(_exception) )); } + catch (std::exception const& _e) + { + errors.append(formatError( + false, + "Exception", + "general", + "Unknown exception during compilation" + (_e.what() ? ": " + string(_e.what()) : ".") + )); + } catch (...) { errors.append(formatError( diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index daae7797c..9d0320abb 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -60,6 +60,7 @@ private: { std::string language; Json::Value errors; + bool parserErrorRecovery = false; std::map sources; std::map smtLib2Responses; langutil::EVMVersion evmVersion; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 5b5c56523..d8e0fa4bc 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -258,57 +258,75 @@ ASTPointer Parser::parseContractDefinition() { RecursionGuard recursionGuard(*this); ASTNodeFactory nodeFactory(*this); + ASTPointer name = nullptr; ASTPointer docString; - if (m_scanner->currentCommentLiteral() != "") - docString = make_shared(m_scanner->currentCommentLiteral()); - ContractDefinition::ContractKind contractKind = parseContractKind(); - ASTPointer name = expectIdentifierToken(); vector> baseContracts; - if (m_scanner->currentToken() == Token::Is) - do - { - m_scanner->next(); - baseContracts.push_back(parseInheritanceSpecifier()); - } - while (m_scanner->currentToken() == Token::Comma); vector> subNodes; - expectToken(Token::LBrace); - while (true) + ContractDefinition::ContractKind contractKind = ContractDefinition::ContractKind::Contract; + try { - Token currentTokenValue = m_scanner->currentToken(); - if (currentTokenValue == Token::RBrace) - break; - else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) - // This can be a function or a state variable of function type (especially - // complicated to distinguish fallback function from function type state variable) - subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); - else if (currentTokenValue == Token::Struct) - subNodes.push_back(parseStructDefinition()); - else if (currentTokenValue == Token::Enum) - subNodes.push_back(parseEnumDefinition()); - else if ( - currentTokenValue == Token::Identifier || - currentTokenValue == Token::Mapping || - TokenTraits::isElementaryTypeName(currentTokenValue) - ) + if (m_scanner->currentCommentLiteral() != "") + docString = make_shared(m_scanner->currentCommentLiteral()); + contractKind = parseContractKind(); + name = expectIdentifierToken(); + if (m_scanner->currentToken() == Token::Is) + do + { + m_scanner->next(); + baseContracts.push_back(parseInheritanceSpecifier()); + } + while (m_scanner->currentToken() == Token::Comma); + expectToken(Token::LBrace); + while (true) { - VarDeclParserOptions options; - options.isStateVariable = true; - options.allowInitialValue = true; - subNodes.push_back(parseVariableDeclaration(options)); - expectToken(Token::Semicolon); + Token currentTokenValue = m_scanner->currentToken(); + if (currentTokenValue == Token::RBrace) + break; + else if (currentTokenValue == Token::Function || currentTokenValue == Token::Constructor) + // This can be a function or a state variable of function type (especially + // complicated to distinguish fallback function from function type state variable) + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable()); + else if (currentTokenValue == Token::Struct) + subNodes.push_back(parseStructDefinition()); + else if (currentTokenValue == Token::Enum) + subNodes.push_back(parseEnumDefinition()); + else if ( + currentTokenValue == Token::Identifier || + currentTokenValue == Token::Mapping || + TokenTraits::isElementaryTypeName(currentTokenValue) + ) + { + VarDeclParserOptions options; + options.isStateVariable = true; + options.allowInitialValue = true; + subNodes.push_back(parseVariableDeclaration(options)); + expectToken(Token::Semicolon); + } + else if (currentTokenValue == Token::Modifier) + subNodes.push_back(parseModifierDefinition()); + else if (currentTokenValue == Token::Event) + subNodes.push_back(parseEventDefinition()); + else if (currentTokenValue == Token::Using) + subNodes.push_back(parseUsingDirective()); + else + fatalParserError(string("Function, variable, struct or modifier declaration expected.")); } - else if (currentTokenValue == Token::Modifier) - subNodes.push_back(parseModifierDefinition()); - else if (currentTokenValue == Token::Event) - subNodes.push_back(parseEventDefinition()); - else if (currentTokenValue == Token::Using) - subNodes.push_back(parseUsingDirective()); - else - fatalParserError(string("Function, variable, struct or modifier declaration expected.")); + } + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; } nodeFactory.markEndPosition(); - expectToken(Token::RBrace); + if (m_inParserRecovery) + expectTokenOrConsumeUntil(Token::RBrace, "ContractDefinition"); + else + expectToken(Token::RBrace); return nodeFactory.createNode( name, docString, @@ -959,10 +977,26 @@ ASTPointer Parser::parseBlock(ASTPointer const& _docString) ASTNodeFactory nodeFactory(*this); expectToken(Token::LBrace); vector> statements; - while (m_scanner->currentToken() != Token::RBrace) - statements.push_back(parseStatement()); - nodeFactory.markEndPosition(); - expectToken(Token::RBrace); + try + { + while (m_scanner->currentToken() != Token::RBrace) + statements.push_back(parseStatement()); + nodeFactory.markEndPosition(); + } + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; + } + if (m_parserErrorRecovery) + expectTokenOrConsumeUntil(Token::RBrace, "Block"); + else + expectToken(Token::RBrace); return nodeFactory.createNode(_docString, statements); } @@ -970,67 +1004,83 @@ ASTPointer Parser::parseStatement() { RecursionGuard recursionGuard(*this); ASTPointer docString; - if (m_scanner->currentCommentLiteral() != "") - docString = make_shared(m_scanner->currentCommentLiteral()); ASTPointer statement; - switch (m_scanner->currentToken()) + try { - case Token::If: - return parseIfStatement(docString); - case Token::While: - return parseWhileStatement(docString); - case Token::Do: - return parseDoWhileStatement(docString); - case Token::For: - return parseForStatement(docString); - case Token::LBrace: - return parseBlock(docString); - // starting from here, all statements must be terminated by a semicolon - case Token::Continue: - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - case Token::Break: - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - case Token::Return: - { - ASTNodeFactory nodeFactory(*this); - ASTPointer expression; - if (m_scanner->next() != Token::Semicolon) + if (m_scanner->currentCommentLiteral() != "") + docString = make_shared(m_scanner->currentCommentLiteral()); + switch (m_scanner->currentToken()) { - expression = parseExpression(); - nodeFactory.setEndPositionFromNode(expression); - } - statement = nodeFactory.createNode(docString, expression); - break; - } - case Token::Throw: - { - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - break; - } - case Token::Assembly: - return parseInlineAssembly(docString); - case Token::Emit: - statement = parseEmitStatement(docString); - break; - case Token::Identifier: - if (m_insideModifier && m_scanner->currentLiteral() == "_") - { - statement = ASTNodeFactory(*this).createNode(docString); + case Token::If: + return parseIfStatement(docString); + case Token::While: + return parseWhileStatement(docString); + case Token::Do: + return parseDoWhileStatement(docString); + case Token::For: + return parseForStatement(docString); + case Token::LBrace: + return parseBlock(docString); + // starting from here, all statements must be terminated by a semicolon + case Token::Continue: + statement = ASTNodeFactory(*this).createNode(docString); m_scanner->next(); - } - else + break; + case Token::Break: + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + break; + case Token::Return: + { + ASTNodeFactory nodeFactory(*this); + ASTPointer expression; + if (m_scanner->next() != Token::Semicolon) + { + expression = parseExpression(); + nodeFactory.setEndPositionFromNode(expression); + } + statement = nodeFactory.createNode(docString, expression); + break; + } + case Token::Throw: + { + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + break; + } + case Token::Assembly: + return parseInlineAssembly(docString); + case Token::Emit: + statement = parseEmitStatement(docString); + break; + case Token::Identifier: + if (m_insideModifier && m_scanner->currentLiteral() == "_") + { + statement = ASTNodeFactory(*this).createNode(docString); + m_scanner->next(); + } + else + statement = parseSimpleStatement(docString); + break; + default: statement = parseSimpleStatement(docString); - break; - default: - statement = parseSimpleStatement(docString); - break; + break; + } } - expectToken(Token::Semicolon); + catch (FatalError const&) + { + if ( + !m_errorReporter.hasErrors() || + !m_parserErrorRecovery || + m_errorReporter.hasExcessiveErrors() + ) + BOOST_THROW_EXCEPTION(FatalError()); /* Don't try to recover here. */ + m_inParserRecovery = true; + } + if (m_inParserRecovery) + expectTokenOrConsumeUntil(Token::Semicolon, "Statement"); + else + expectToken(Token::Semicolon); return statement; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 51b3b5be4..cb3d75f51 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -41,9 +41,10 @@ class Parser: public langutil::ParserBase public: explicit Parser( langutil::ErrorReporter& _errorReporter, - langutil::EVMVersion _evmVersion + langutil::EVMVersion _evmVersion, + bool _errorRecovery = false ): - ParserBase(_errorReporter), + ParserBase(_errorReporter, _errorRecovery), m_evmVersion(_evmVersion) {} diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index 3f29f6fa5..ae3056f21 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -384,17 +384,10 @@ Parser::ElementaryOperation Parser::parseElementaryOperation() case Token::Identifier: case Token::Return: case Token::Byte: + case Token::Bool: case Token::Address: { - YulString literal; - if (currentToken() == Token::Return) - literal = "return"_yulstring; - else if (currentToken() == Token::Byte) - literal = "byte"_yulstring; - else if (currentToken() == Token::Address) - literal = "address"_yulstring; - else - literal = YulString{currentLiteral()}; + YulString literal{currentLiteral()}; // first search the set of builtins, then the instructions. if (m_dialect.builtin(literal)) { @@ -609,12 +602,14 @@ Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp) else ret = std::move(boost::get(_initialOp)); expectToken(Token::LParen); - while (currentToken() != Token::RParen) + if (currentToken() != Token::RParen) { ret.arguments.emplace_back(parseExpression()); - if (currentToken() == Token::RParen) - break; - expectToken(Token::Comma); + while (currentToken() != Token::RParen) + { + expectToken(Token::Comma); + ret.arguments.emplace_back(parseExpression()); + } } ret.location.end = endPosition(); expectToken(Token::RParen); @@ -646,26 +641,25 @@ TypedName Parser::parseTypedName() YulString Parser::expectAsmIdentifier() { - YulString name = YulString{currentLiteral()}; - if (m_dialect.flavour == AsmFlavour::Yul) + YulString name{currentLiteral()}; + switch (currentToken()) { - switch (currentToken()) - { - case Token::Return: - case Token::Byte: - case Token::Address: - case Token::Bool: - advance(); - return name; - default: - break; - } + case Token::Return: + case Token::Byte: + case Token::Address: + case Token::Bool: + case Token::Identifier: + break; + default: + expectToken(Token::Identifier); + break; } - else if (m_dialect.builtin(name)) + + if (m_dialect.builtin(name)) fatalParserError("Cannot use builtin function name \"" + name.str() + "\" as identifier name."); - else if (instructions().count(name.str())) - fatalParserError("Cannot use instruction names for identifier names."); - expectToken(Token::Identifier); + else if (m_dialect.flavour == AsmFlavour::Loose && instructions().count(name.str())) + fatalParserError("Cannot use instruction name \"" + name.str() + "\" as identifier name."); + advance(); return name; } diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp index a3d9eafc3..71151a0cf 100644 --- a/libyul/AsmPrinter.cpp +++ b/libyul/AsmPrinter.cpp @@ -266,7 +266,7 @@ string AsmPrinter::formatTypedName(TypedName _variable) const string AsmPrinter::appendTypeName(YulString _type) const { - if (m_yul) + if (m_yul && !_type.empty()) return ":" + _type.str(); return ""; } diff --git a/libyul/AssemblyStack.cpp b/libyul/AssemblyStack.cpp index dec7d21e8..549a599fe 100644 --- a/libyul/AssemblyStack.cpp +++ b/libyul/AssemblyStack.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -93,8 +94,6 @@ void AssemblyStack::optimize() if (!m_optimiserSettings.runYulOptimiser) return; - if (m_language != Language::StrictAssembly) - solUnimplemented("Optimizer for both loose assembly and Yul is not yet implemented"); solAssert(m_analysisSuccessful, "Analysis was not successful."); m_analysisSuccessful = false; @@ -146,11 +145,14 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast(subNode.get())) optimize(*subObject, false); - EVMDialect const& dialect = dynamic_cast(languageToDialect(m_language, m_evmVersion)); - GasMeter meter(dialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); + + Dialect const& dialect = languageToDialect(m_language, m_evmVersion); + unique_ptr meter; + if (EVMDialect const* evmDialect = dynamic_cast(&dialect)) + meter = make_unique(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment); OptimiserSuite::run( dialect, - meter, + meter.get(), *_object.code, *_object.analysisInfo, m_optimiserSettings.optimizeStackAllocation diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 12a31c2bb..b0c226f45 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -28,6 +28,8 @@ add_library(yul backends/evm/AbstractAssembly.h backends/evm/AsmCodeGen.h backends/evm/AsmCodeGen.cpp + backends/evm/ConstantOptimiser.cpp + backends/evm/ConstantOptimiser.h backends/evm/EVMAssembly.cpp backends/evm/EVMAssembly.h backends/evm/EVMCodeTransform.cpp @@ -36,6 +38,8 @@ add_library(yul backends/evm/EVMDialect.h backends/evm/EVMObjectCompiler.cpp backends/evm/EVMObjectCompiler.h + backends/evm/EVMMetrics.cpp + backends/evm/EVMMetrics.h backends/evm/NoOutputAssembly.h backends/evm/NoOutputAssembly.cpp backends/wasm/EWasmCodeTransform.cpp @@ -58,8 +62,6 @@ add_library(yul optimiser/BlockHasher.h optimiser/CommonSubexpressionEliminator.cpp optimiser/CommonSubexpressionEliminator.h - optimiser/ConstantOptimiser.cpp - optimiser/ConstantOptimiser.h optimiser/ControlFlowSimplifier.cpp optimiser/ControlFlowSimplifier.h optimiser/DataFlowAnalyzer.cpp @@ -92,6 +94,10 @@ add_library(yul optimiser/FunctionHoister.h optimiser/InlinableExpressionFunctionFinder.cpp optimiser/InlinableExpressionFunctionFinder.h + optimiser/KnowledgeBase.cpp + optimiser/KnowledgeBase.h + optimiser/LoadResolver.cpp + optimiser/LoadResolver.h optimiser/MainFunction.cpp optimiser/MainFunction.h optimiser/Metrics.cpp @@ -100,6 +106,8 @@ add_library(yul optimiser/NameCollector.h optimiser/NameDispenser.cpp optimiser/NameDispenser.h + optimiser/NameDisplacer.cpp + optimiser/NameDisplacer.h optimiser/OptimizerUtilities.cpp optimiser/OptimizerUtilities.h optimiser/RedundantAssignEliminator.cpp diff --git a/libyul/CompilabilityChecker.cpp b/libyul/CompilabilityChecker.cpp index 7f065e884..c25b33c98 100644 --- a/libyul/CompilabilityChecker.cpp +++ b/libyul/CompilabilityChecker.cpp @@ -43,27 +43,31 @@ map CompilabilityChecker::run( solAssert(_dialect.flavour == AsmFlavour::Strict, ""); - solAssert(dynamic_cast(&_dialect), ""); - NoOutputEVMDialect noOutputDialect(dynamic_cast(_dialect)); - BuiltinContext builtinContext; - - yul::AsmAnalysisInfo analysisInfo = - yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast); - - NoOutputAssembly assembly; - CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation); - try + if (EVMDialect const* evmDialect = dynamic_cast(&_dialect)) { - transform(_ast); - } - catch (StackTooDeepError const&) - { - solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); - } + NoOutputEVMDialect noOutputDialect(*evmDialect); + BuiltinContext builtinContext; - std::map functions; - for (StackTooDeepError const& error: transform.stackErrors()) - functions[error.functionName] = max(error.depth, functions[error.functionName]); + yul::AsmAnalysisInfo analysisInfo = + yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast); - return functions; + NoOutputAssembly assembly; + CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation); + try + { + transform(_ast); + } + catch (StackTooDeepError const&) + { + solAssert(!transform.stackErrors().empty(), "Got stack too deep exception that was not stored."); + } + + std::map functions; + for (StackTooDeepError const& error: transform.stackErrors()) + functions[error.functionName] = max(error.depth, functions[error.functionName]); + + return functions; + } + else + return {}; } diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 4fc33edbc..bf1a352e1 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -25,6 +25,7 @@ #include #include +#include namespace yul { @@ -56,6 +57,12 @@ struct BuiltinFunction bool sideEffectFreeIfNoMSize = false; /// 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; }; @@ -66,6 +73,11 @@ struct Dialect: boost::noncopyable /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; } + virtual BuiltinFunction const* discardFunction() const { return nullptr; } + virtual BuiltinFunction const* equalityFunction() const { return nullptr; } + + virtual std::set fixedFunctionNames() const { return {}; } + Dialect(AsmFlavour _flavour): flavour(_flavour) {} virtual ~Dialect() = default; diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp index b69df0e4f..6894c2319 100644 --- a/libyul/Utilities.cpp +++ b/libyul/Utilities.cpp @@ -26,10 +26,57 @@ #include #include +#include + +#include +#include +#include +#include + using namespace std; using namespace dev; using namespace yul; +using boost::split; +using boost::is_any_of; + +string yul::reindent(string const& _code) +{ + auto const static countBraces = [](string const& _s) noexcept -> int + { + auto const i = _s.find("//"); + auto const e = i == _s.npos ? end(_s) : next(begin(_s), i); + auto const opening = count_if(begin(_s), e, [](auto ch) { return ch == '{' || ch == '('; }); + auto const closing = count_if(begin(_s), e, [](auto ch) { return ch == '}' || ch == ')'; }); + return opening - closing; + }; + + vector lines; + split(lines, _code, is_any_of("\n")); + for (string& line: lines) + boost::trim(line); + + stringstream out; + int depth = 0; + + for (string const& line: lines) + { + int const diff = countBraces(line); + if (diff < 0) + depth += diff; + + for (int i = 0; i < depth; ++i) + out << '\t'; + + out << line << '\n'; + + if (diff > 0) + depth += diff; + } + + return out.str(); +} + u256 yul::valueOfNumberLiteral(Literal const& _literal) { yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!"); diff --git a/libyul/Utilities.h b/libyul/Utilities.h index 0103d74ec..092dc90c3 100644 --- a/libyul/Utilities.h +++ b/libyul/Utilities.h @@ -26,6 +26,8 @@ namespace yul { +std::string reindent(std::string const& _code); + dev::u256 valueOfNumberLiteral(Literal const& _literal); dev::u256 valueOfStringLiteral(Literal const& _literal); dev::u256 valueOfBoolLiteral(Literal const& _literal); diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index a531c0624..b87710522 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -62,6 +62,7 @@ public: /// Retrieve the current height of the stack. This does not have to be zero /// at the beginning. virtual int stackHeight() const = 0; + virtual void setStackHeight(int height) = 0; /// Append an EVM instruction. virtual void appendInstruction(dev::eth::Instruction _instruction) = 0; /// Append a constant. diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index e96481dba..999d215a3 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -57,6 +57,11 @@ int EthAssemblyAdapter::stackHeight() const return m_assembly.deposit(); } +void EthAssemblyAdapter::setStackHeight(int height) +{ + m_assembly.setDeposit(height); +} + void EthAssemblyAdapter::appendInstruction(dev::eth::Instruction _instruction) { m_assembly.append(_instruction); diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 3d2e9e191..ffaea6221 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -44,6 +44,7 @@ public: explicit EthAssemblyAdapter(dev::eth::Assembly& _assembly); void setSourceLocation(langutil::SourceLocation const& _location) override; int stackHeight() const override; + void setStackHeight(int height) override; void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; diff --git a/libyul/optimiser/ConstantOptimiser.cpp b/libyul/backends/evm/ConstantOptimiser.cpp similarity index 97% rename from libyul/optimiser/ConstantOptimiser.cpp rename to libyul/backends/evm/ConstantOptimiser.cpp index 2fa200e73..f8b4854d8 100644 --- a/libyul/optimiser/ConstantOptimiser.cpp +++ b/libyul/backends/evm/ConstantOptimiser.cpp @@ -18,14 +18,12 @@ * Optimisation stage that replaces constants by expressions that compute them. */ -#include +#include #include -#include +#include #include -#include #include -#include #include diff --git a/libyul/optimiser/ConstantOptimiser.h b/libyul/backends/evm/ConstantOptimiser.h similarity index 100% rename from libyul/optimiser/ConstantOptimiser.h rename to libyul/backends/evm/ConstantOptimiser.h diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index a2318aea7..9b4242a2c 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -45,6 +45,7 @@ public: /// Retrieve the current height of the stack. This does not have to be zero /// at the beginning. int stackHeight() const override { return m_stackHeight; } + void setStackHeight(int height) override { m_stackHeight = height; } /// Append an EVM instruction. void appendInstruction(dev::eth::Instruction _instruction) override; /// Append a constant. diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index e8f5ec613..9ca079f07 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -490,19 +490,15 @@ void CodeTransform::operator()(FunctionDefinition const& _function) } m_assembly.setSourceLocation(_function.location); - int stackHeightBefore = m_assembly.stackHeight(); - AbstractAssembly::LabelID afterFunction = m_assembly.newLabelId(); + int const stackHeightBefore = m_assembly.stackHeight(); if (m_evm15) - { - m_assembly.appendJumpTo(afterFunction, -stackHeightBefore); m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size()); - } else - { - m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height); m_assembly.appendLabel(functionEntryID(_function.name, function)); - } + + m_assembly.setStackHeight(height); + m_stackAdjustment += localStackAdjustment; for (auto const& v: _function.returnVariables) @@ -592,8 +588,8 @@ void CodeTransform::operator()(FunctionDefinition const& _function) else m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size()); m_stackAdjustment -= localStackAdjustment; - m_assembly.appendLabel(afterFunction); checkStackHeight(&_function); + m_assembly.setStackHeight(stackHeightBefore); } void CodeTransform::operator()(ForLoop const& _forLoop) @@ -727,11 +723,32 @@ void CodeTransform::visitExpression(Expression const& _expression) void CodeTransform::visitStatements(vector const& _statements) { + // Workaround boost bug: + // https://www.boost.org/doc/libs/1_63_0/libs/optional/doc/html/boost_optional/tutorial/gotchas/false_positive_with__wmaybe_uninitialized.html + boost::optional jumpTarget = boost::make_optional(false, AbstractAssembly::LabelID()); + for (auto const& statement: _statements) { freeUnusedVariables(); + auto const* functionDefinition = boost::get(&statement); + if (functionDefinition && !jumpTarget) + { + m_assembly.setSourceLocation(locationOf(statement)); + jumpTarget = m_assembly.newLabelId(); + m_assembly.appendJumpTo(*jumpTarget, 0); + } + else if (!functionDefinition && jumpTarget) + { + m_assembly.appendLabel(*jumpTarget); + jumpTarget = boost::none; + } + boost::apply_visitor(*this, statement); } + // we may have a leftover jumpTarget + if (jumpTarget) + m_assembly.appendLabel(*jumpTarget); + freeUnusedVariables(); } diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 323b7d7bd..b4a7cbd49 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -54,6 +54,8 @@ pair createEVMFunction( f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_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]( @@ -76,6 +78,8 @@ pair createFunction( bool _movable, bool _sideEffectFree, bool _sideEffectFreeIfNoMSize, + bool _invalidatesStorage, + bool _invalidatesMemory, bool _literalArguments, std::function)> _generateCode ) @@ -90,6 +94,8 @@ pair createFunction( 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}; @@ -110,7 +116,7 @@ map createBuiltins(langutil::EVMVersion _evmVe if (_objectAccess) { - builtins.emplace(createFunction("datasize", 1, 1, true, true, true, true, []( + builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -131,7 +137,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataSize(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, true, []( + builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -152,7 +158,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataOffset(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, []( + builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, []( FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 56dc99f87..bcea185f4 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -68,6 +68,9 @@ struct EVMDialect: public Dialect /// @returns the builtin function of the given name or a nullptr if it is not a builtin function. BuiltinFunctionForEVM const* builtin(YulString _name) const override; + BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); } + BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); } + static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version); diff --git a/libyul/backends/evm/EVMMetrics.cpp b/libyul/backends/evm/EVMMetrics.cpp new file mode 100644 index 000000000..5211e5132 --- /dev/null +++ b/libyul/backends/evm/EVMMetrics.cpp @@ -0,0 +1,123 @@ +/* + 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 . +*/ +/** +* Module providing metrics for the EVM optimizer. +*/ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +size_t GasMeter::costs(Expression const& _expression) const +{ + return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation)); +} + +size_t GasMeter::instructionCosts(eth::Instruction _instruction) const +{ + return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation)); +} + +size_t GasMeter::combineCosts(std::pair _costs) const +{ + return _costs.first * m_runs + _costs.second; +} + + +pair GasMeterVisitor::costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.visit(_expression); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +pair GasMeterVisitor::instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation +) +{ + GasMeterVisitor gmv(_dialect, _isCreation); + gmv.instructionCostsInternal(_instruction); + return {gmv.m_runGas, gmv.m_dataGas}; +} + +void GasMeterVisitor::operator()(FunctionCall const& _funCall) +{ + ASTWalker::operator()(_funCall); + if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name)) + if (f->instruction) + { + instructionCostsInternal(*f->instruction); + return; + } + yulAssert(false, "Functions not implemented."); +} + +void GasMeterVisitor::operator()(FunctionalInstruction const& _fun) +{ + ASTWalker::operator()(_fun); + instructionCostsInternal(_fun.instruction); +} + +void GasMeterVisitor::operator()(Literal const& _lit) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); + m_dataGas += + singleByteDataGas() + + size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); +} + +void GasMeterVisitor::operator()(Identifier const&) +{ + m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1); + m_dataGas += singleByteDataGas(); +} + +size_t GasMeterVisitor::singleByteDataGas() const +{ + if (m_isCreation) + return dev::eth::GasCosts::txDataNonZeroGas; + else + return dev::eth::GasCosts::createDataGas; +} + +void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction) +{ + if (_instruction == eth::Instruction::EXP) + m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion()); + else + m_runGas += dev::eth::GasMeter::runGas(_instruction); + m_dataGas += singleByteDataGas(); +} diff --git a/libyul/backends/evm/EVMMetrics.h b/libyul/backends/evm/EVMMetrics.h new file mode 100644 index 000000000..8a8987b94 --- /dev/null +++ b/libyul/backends/evm/EVMMetrics.h @@ -0,0 +1,101 @@ +/* + 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 . +*/ +/** + * Module providing metrics for the optimizer. + */ + +#pragma once + +#include +#include +#include + +namespace yul +{ + +struct EVMDialect; + +/** + * Gas meter for expressions only involving literals, identifiers and + * EVM instructions. + * + * Assumes that EXP is not used with exponents larger than a single byte. + * Is not particularly exact for anything apart from arithmetic. + */ +class GasMeter +{ +public: + GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs): + m_dialect(_dialect), + m_isCreation{_isCreation}, + m_runs(_runs) + {} + + /// @returns the full combined costs of deploying and evaluating the expression. + size_t costs(Expression const& _expression) const; + /// @returns the combined costs of deploying and running the instruction, not including + /// the costs for its arguments. + size_t instructionCosts(dev::eth::Instruction _instruction) const; + +private: + size_t combineCosts(std::pair _costs) const; + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runs; +}; + +class GasMeterVisitor: public ASTWalker +{ +public: + static std::pair costs( + Expression const& _expression, + EVMDialect const& _dialect, + bool _isCreation + ); + + static std::pair instructionCosts( + dev::eth::Instruction _instruction, + EVMDialect const& _dialect, + bool _isCreation = false + ); + +public: + GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation): + m_dialect(_dialect), + m_isCreation{_isCreation} + {} + + void operator()(FunctionCall const& _funCall) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(Literal const& _literal) override; + void operator()(Identifier const& _identifier) override; + +private: + size_t singleByteDataGas() const; + /// Computes the cost of storing and executing the single instruction (excluding its arguments). + /// For EXP, it assumes that the exponent is at most 255. + /// Does not work particularly exact for anything apart from arithmetic. + void instructionCostsInternal(dev::eth::Instruction _instruction); + + EVMDialect const& m_dialect; + bool m_isCreation = false; + size_t m_runGas = 0; + size_t m_dataGas = 0; +}; + +} diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index 7700599e7..d938a4e45 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -49,6 +49,7 @@ public: void setSourceLocation(langutil::SourceLocation const&) override {} int stackHeight() const override { return m_stackHeight; } + void setStackHeight(int height) override { m_stackHeight = height; } void appendInstruction(dev::eth::Instruction _instruction) override; void appendConstant(dev::u256 const& _constant) override; void appendLabel(LabelID _labelId) override; diff --git a/libyul/backends/wasm/EWasmCodeTransform.cpp b/libyul/backends/wasm/EWasmCodeTransform.cpp index c25e5a1c9..90ca28d1c 100644 --- a/libyul/backends/wasm/EWasmCodeTransform.cpp +++ b/libyul/backends/wasm/EWasmCodeTransform.cpp @@ -115,9 +115,9 @@ wasm::Expression EWasmCodeTransform::operator()(Label const&) return {}; } -wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const&) +wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const& _f) { - yulAssert(false, ""); + yulAssert(false, "EVM instruction in ewasm code: " + eth::instructionInfo(_f.instruction).name); return {}; } diff --git a/libyul/backends/wasm/EWasmToText.cpp b/libyul/backends/wasm/EWasmToText.cpp index a0d552d11..4e2bf5c2b 100644 --- a/libyul/backends/wasm/EWasmToText.cpp +++ b/libyul/backends/wasm/EWasmToText.cpp @@ -34,9 +34,18 @@ string EWasmToText::run( vector const& _functions ) { - string ret = "(module\n\n"; + string ret = "(module\n"; + // TODO Add all the interface functions: + // ret += " (import \"ethereum\" \"getBalance\" (func $getBalance (param i32 i32)))\n"; + + // allocate one 64k page of memory and make it available to the Ethereum client + ret += " (memory $memory (export \"memory\") 1)\n"; + // export the main function + ret += " (export \"main\" (func $main))\n"; + for (auto const& g: _globals) ret += " (global $" + g.variableName + " (mut i64) (i64.const 0))\n"; + ret += "\n"; for (auto const& f: _functions) ret += transform(f) + "\n"; return move(ret) + ")\n"; diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 6d4eef27d..5b76a4af9 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -83,5 +83,7 @@ void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) f.sideEffectFree = false; f.sideEffectFreeIfNoMSize = false; f.isMSize = false; + f.invalidatesStorage = true; + f.invalidatesMemory = true; f.literalArguments = false; } diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 0948a8049..71f7c52ad 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -45,6 +45,10 @@ struct WasmDialect: public Dialect WasmDialect(); 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); } + + std::set fixedFunctionNames() const override { return {"main"_yulstring}; } static WasmDialect const& instance(); diff --git a/libyul/backends/wasm/WordSizeTransform.cpp b/libyul/backends/wasm/WordSizeTransform.cpp index 4388eebd1..08d55e296 100644 --- a/libyul/backends/wasm/WordSizeTransform.cpp +++ b/libyul/backends/wasm/WordSizeTransform.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include @@ -41,6 +43,10 @@ void WordSizeTransform::operator()(FunctionalInstruction& _ins) void WordSizeTransform::operator()(FunctionCall& _fc) { + if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name)) + if (fun->literalArguments) + return; + rewriteFunctionCallArguments(_fc.arguments); } @@ -48,7 +54,7 @@ void WordSizeTransform::operator()(If& _if) { _if.condition = make_unique(FunctionCall{ locationOf(*_if.condition), - Identifier{locationOf(*_if.condition), "or_bool"_yulstring}, // TODO make sure this is not used + Identifier{locationOf(*_if.condition), "or_bool"_yulstring}, expandValueToVector(*_if.condition) }); (*this)(_if.body); @@ -59,6 +65,18 @@ void WordSizeTransform::operator()(Switch&) yulAssert(false, "Switch statement not implemented."); } +void WordSizeTransform::operator()(ForLoop& _for) +{ + (*this)(_for.pre); + _for.condition = make_unique(FunctionCall{ + locationOf(*_for.condition), + Identifier{locationOf(*_for.condition), "or_bool"_yulstring}, + expandValueToVector(*_for.condition) + }); + (*this)(_for.post); + (*this)(_for.body); +} + void WordSizeTransform::operator()(Block& _block) { iterateReplacing( @@ -142,9 +160,11 @@ void WordSizeTransform::operator()(Block& _block) ); } -void WordSizeTransform::run(Block& _ast, NameDispenser& _nameDispenser) +void WordSizeTransform::run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser) { - WordSizeTransform{_nameDispenser}(_ast); + // Free the name `or_bool`. + NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast); + WordSizeTransform{_inputDialect, _nameDispenser}(_ast); } void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList) diff --git a/libyul/backends/wasm/WordSizeTransform.h b/libyul/backends/wasm/WordSizeTransform.h index 8c71b9817..42bf58940 100644 --- a/libyul/backends/wasm/WordSizeTransform.h +++ b/libyul/backends/wasm/WordSizeTransform.h @@ -51,7 +51,7 @@ namespace yul * take four times the parameters and each of type u64. * In addition, it uses a single other builtin function called `or_bool` that * takes four u64 parameters and is supposed to return the logical disjunction - * of them es a u64 value. + * of them as a u64 value. If this name is already used somewhere, it is renamed. * * Prerequisite: Disambiguator, ExpressionSplitter */ @@ -63,12 +63,14 @@ public: void operator()(FunctionCall&) override; void operator()(If&) override; void operator()(Switch&) override; + void operator()(ForLoop&) override; void operator()(Block& _block) override; - static void run(Block& _ast, NameDispenser& _nameDispenser); + static void run(Dialect const& _inputDialect, Block& _ast, NameDispenser& _nameDispenser); private: - explicit WordSizeTransform(NameDispenser& _nameDispenser): + explicit WordSizeTransform(Dialect const& _inputDialect, NameDispenser& _nameDispenser): + m_inputDialect(_inputDialect), m_nameDispenser(_nameDispenser) { } @@ -80,6 +82,7 @@ private: std::array, 4> expandValue(Expression const& _e); std::vector expandValueToVector(Expression const& _e); + Dialect const& m_inputDialect; NameDispenser& m_nameDispenser; /// maps original u256 variable's name to corresponding u64 variables' names std::map> m_variableMapping; diff --git a/libyul/optimiser/ASTWalker.cpp b/libyul/optimiser/ASTWalker.cpp index 85234c4c7..523cc8a92 100644 --- a/libyul/optimiser/ASTWalker.cpp +++ b/libyul/optimiser/ASTWalker.cpp @@ -35,6 +35,7 @@ void ASTWalker::operator()(FunctionalInstruction const& _instr) void ASTWalker::operator()(FunctionCall const& _funCall) { + // Does not visit _funCall.functionName on purpose walkVector(_funCall.arguments | boost::adaptors::reversed); } @@ -108,6 +109,7 @@ void ASTModifier::operator()(FunctionalInstruction& _instr) void ASTModifier::operator()(FunctionCall& _funCall) { + // Does not visit _funCall.functionName on purpose walkVector(_funCall.arguments | boost::adaptors::reversed); } diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp index ccd88051f..7c826aa28 100644 --- a/libyul/optimiser/ControlFlowSimplifier.cpp +++ b/libyul/optimiser/ControlFlowSimplifier.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -33,11 +34,16 @@ using OptionalStatements = boost::optional>; namespace { -ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression) +ExpressionStatement makeDiscardCall( + langutil::SourceLocation const& _location, + Dialect const& _dialect, + Expression&& _expression +) { + yulAssert(_dialect.discardFunction(), "No discard function available."); return {_location, FunctionCall{ _location, - Identifier{_location, "pop"_yulstring}, + Identifier{_location, _dialect.discardFunction()->name}, {std::move(_expression)} }}; } @@ -66,36 +72,55 @@ void removeEmptyCasesFromSwitch(Switch& _switchStmt) ); } -OptionalStatements reduceNoCaseSwitch(Switch& _switchStmt) +OptionalStatements reduceNoCaseSwitch(Dialect const& _dialect, Switch& _switchStmt) { yulAssert(_switchStmt.cases.empty(), "Expected no case!"); + if (!_dialect.discardFunction()) + return {}; auto loc = locationOf(*_switchStmt.expression); - return make_vector(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); + return make_vector(makeDiscardCall( + loc, + _dialect, + std::move(*_switchStmt.expression) + )); } -OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt) +OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _switchStmt) { yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!"); auto& switchCase = _switchStmt.cases.front(); auto loc = locationOf(*_switchStmt.expression); if (switchCase.value) + { + if (!_dialect.equalityFunction()) + return {}; return make_vector(If{ std::move(_switchStmt.location), make_unique(FunctionCall{ loc, - Identifier{loc, "eq"_yulstring}, + Identifier{loc, _dialect.equalityFunction()->name}, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} }), std::move(switchCase.body) }); + } else + { + if (!_dialect.discardFunction()) + return {}; + return make_vector( - makePopExpressionStatement(loc, std::move(*_switchStmt.expression)), + makeDiscardCall( + loc, + _dialect, + std::move(*_switchStmt.expression) + ), std::move(switchCase.body) ); + } } } @@ -151,10 +176,14 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) { GenericFallbackReturnsVisitor const visitor( [&](If& _ifStmt) -> OptionalStatements { - if (_ifStmt.body.statements.empty()) + if (_ifStmt.body.statements.empty() && m_dialect.discardFunction()) { OptionalStatements s = vector{}; - s->emplace_back(makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition))); + s->emplace_back(makeDiscardCall( + _ifStmt.location, + m_dialect, + std::move(*_ifStmt.condition) + )); return s; } return {}; @@ -164,9 +193,9 @@ void ControlFlowSimplifier::simplify(std::vector& _statements) removeEmptyCasesFromSwitch(_switchStmt); if (_switchStmt.cases.empty()) - return reduceNoCaseSwitch(_switchStmt); + return reduceNoCaseSwitch(m_dialect, _switchStmt); else if (_switchStmt.cases.size() == 1) - return reduceSingleCaseSwitch(_switchStmt); + return reduceSingleCaseSwitch(m_dialect, _switchStmt); return {}; } diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 63b1f9e8a..a927a7eca 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -26,21 +26,61 @@ #include #include #include +#include #include #include +#include using namespace std; using namespace dev; using namespace yul; + +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 (!( + m_knowledgeBase.knownToBeDifferent(vars->first, item.first) || + m_knowledgeBase.knownToBeEqual(vars->second, item.second) + )) + keysToErase.insert(item.first); + for (YulString const& key: keysToErase) + m_storage.eraseKey(key); + } + else if (auto vars = isSimpleStore(dev::eth::Instruction::MSTORE, _statement)) + { + ASTModifier::operator()(_statement); + set keysToErase; + 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); + } + else + { + clearKnowledgeIfInvalidated(_statement.expression); + ASTModifier::operator()(_statement); + } +} + void DataFlowAnalyzer::operator()(Assignment& _assignment) { set names; for (auto const& var: _assignment.variableNames) names.emplace(var.name); assertThrow(_assignment.value, OptimizerException, ""); + clearKnowledgeIfInvalidated(*_assignment.value); visit(*_assignment.value); handleAssignment(names, _assignment.value.get()); } @@ -53,15 +93,24 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl) m_variableScopes.back().variables += names; if (_varDecl.value) + { + clearKnowledgeIfInvalidated(*_varDecl.value); visit(*_varDecl.value); + } handleAssignment(names, _varDecl.value.get()); } void DataFlowAnalyzer::operator()(If& _if) { + clearKnowledgeIfInvalidated(*_if.condition); + InvertibleMap storage = m_storage; + InvertibleMap memory = m_memory; + ASTModifier::operator()(_if); + joinKnowledge(storage, memory); + Assignments assignments; assignments(_if.body); clearValues(assignments.names()); @@ -69,17 +118,25 @@ void DataFlowAnalyzer::operator()(If& _if) void DataFlowAnalyzer::operator()(Switch& _switch) { + clearKnowledgeIfInvalidated(*_switch.expression); visit(*_switch.expression); set assignedVariables; for (auto& _case: _switch.cases) { + InvertibleMap storage = m_storage; + InvertibleMap memory = m_memory; (*this)(_case.body); + joinKnowledge(storage, memory); + Assignments assignments; assignments(_case.body); assignedVariables += assignments.names(); // This is a little too destructive, we could retain the old values. clearValues(assignments.names()); + clearKnowledgeIfInvalidated(_case.body); } + for (auto& _case: _switch.cases) + clearKnowledgeIfInvalidated(_case.body); clearValues(assignedVariables); } @@ -88,11 +145,13 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) // Save all information. We might rather reinstantiate this class, // but this could be difficult if it is subclassed. map value; - map> references; - map> referencedBy; + InvertibleRelation references; + InvertibleMap storage; + InvertibleMap memory; m_value.swap(value); - m_references.swap(references); - m_referencedBy.swap(referencedBy); + swap(m_references, references); + swap(m_storage, storage); + swap(m_memory, memory); pushScope(true); for (auto const& parameter: _fun.parameters) @@ -106,8 +165,9 @@ void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) popScope(); m_value.swap(value); - m_references.swap(references); - m_referencedBy.swap(referencedBy); + swap(m_references, references); + swap(m_storage, storage); + swap(m_memory, memory); } void DataFlowAnalyzer::operator()(ForLoop& _for) @@ -124,11 +184,20 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) assignments(_for.post); clearValues(assignments.names()); + // break/continue are tricky for storage and thus we almost always clear here. + clearKnowledgeIfInvalidated(*_for.condition); + clearKnowledgeIfInvalidated(_for.post); + clearKnowledgeIfInvalidated(_for.body); + visit(*_for.condition); (*this)(_for.body); clearValues(assignmentsSinceCont.names()); + clearKnowledgeIfInvalidated(_for.body); (*this)(_for.post); clearValues(assignments.names()); + clearKnowledgeIfInvalidated(*_for.condition); + clearKnowledgeIfInvalidated(_for.post); + clearKnowledgeIfInvalidated(_for.body); } void DataFlowAnalyzer::operator()(Block& _block) @@ -163,9 +232,15 @@ void DataFlowAnalyzer::handleAssignment(set const& _variables, Expres auto const& referencedVariables = movableChecker.referencedVariables(); for (auto const& name: _variables) { - m_references[name] = referencedVariables; - for (auto const& ref: referencedVariables) - m_referencedBy[ref].emplace(name); + m_references.set(name, referencedVariables); + // assignment to slot denoted by "name" + m_storage.eraseKey(name); + // assignment to slot contents denoted by "name" + m_storage.eraseValue(name); + // assignment to slot denoted by "name" + m_memory.eraseKey(name); + // assignment to slot contents denoted by "name" + m_memory.eraseValue(name); } } @@ -195,20 +270,78 @@ void DataFlowAnalyzer::clearValues(set _variables) // This cannot be easily tested since the substitutions will be done // one by one on the fly, and the last line will just be add(1, 1) - // Clear variables that reference variables to be cleared. + // First clear storage knowledge, because we do not have to clear + // storage knowledge of variables whose expression has changed, + // since the value is still unchanged. for (auto const& name: _variables) - for (auto const& ref: m_referencedBy[name]) + { + // clear slot denoted by "name" + m_storage.eraseKey(name); + // clear slot contents denoted by "name" + m_storage.eraseValue(name); + // assignment to slot denoted by "name" + m_memory.eraseKey(name); + // assignment to slot contents denoted by "name" + m_memory.eraseValue(name); + } + + // Also clear variables that reference variables to be cleared. + for (auto const& name: _variables) + for (auto const& ref: m_references.backward[name]) _variables.emplace(ref); // Clear the value and update the reference relation. for (auto const& name: _variables) m_value.erase(name); for (auto const& name: _variables) + m_references.eraseKey(name); +} + +void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) +{ + SideEffectsCollector sideEffects(m_dialect, _block); + if (sideEffects.invalidatesStorage()) + m_storage.clear(); + if (sideEffects.invalidatesMemory()) + m_memory.clear(); +} + +void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr) +{ + SideEffectsCollector sideEffects(m_dialect, _expr); + if (sideEffects.invalidatesStorage()) + m_storage.clear(); + if (sideEffects.invalidatesMemory()) + m_memory.clear(); +} + +void DataFlowAnalyzer::joinKnowledge( + InvertibleMap const& _olderStorage, + InvertibleMap const& _olderMemory +) +{ + joinKnowledgeHelper(m_storage, _olderStorage); + joinKnowledgeHelper(m_memory, _olderMemory); +} + +void DataFlowAnalyzer::joinKnowledgeHelper( + InvertibleMap& _this, + InvertibleMap const& _older +) +{ + // We clear if the key does not exist in the older map or if the value is different. + // This also works for memory because _older is an "older version" + // of m_memory and thus any overlapping write would have cleared the keys + // that are not known to be different inside m_memory already. + set keysToErase; + for (auto const& item: _this.values) { - for (auto const& ref: m_references[name]) - m_referencedBy[ref].erase(name); - m_references[name].clear(); + auto it = _older.values.find(item.first); + if (it == _older.values.end() || it->second != item.second) + keysToErase.insert(item.first); } + for (auto const& key: keysToErase) + _this.eraseKey(key); } bool DataFlowAnalyzer::inScope(YulString _variableName) const @@ -222,3 +355,33 @@ bool DataFlowAnalyzer::inScope(YulString _variableName) const } return false; } + +boost::optional> DataFlowAnalyzer::isSimpleStore( + dev::eth::Instruction _store, + ExpressionStatement const& _statement +) const +{ + yulAssert( + _store == dev::eth::Instruction::MSTORE || + _store == dev::eth::Instruction::SSTORE, + "" + ); + if (_statement.expression.type() == typeid(FunctionCall)) + { + FunctionCall const& funCall = boost::get(_statement.expression); + if (EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (auto const* builtin = dialect->builtin(funCall.functionName.name)) + if (builtin->instruction == _store) + if ( + funCall.arguments.at(0).type() == typeid(Identifier) && + funCall.arguments.at(1).type() == typeid(Identifier) + ) + { + YulString key = boost::get(funCall.arguments.at(0)).name; + YulString value = boost::get(funCall.arguments.at(1)).name; + return make_pair(key, value); + } + } + return {}; +} + diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 78d8fa7e2..5df45dcb8 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -23,9 +23,15 @@ #pragma once #include +#include #include #include +// TODO avoid +#include + +#include + #include #include @@ -40,14 +46,34 @@ struct Dialect; * * A special zero constant expression is used for the default value of variables. * + * The class also tracks contents in storage and memory. Both keys and values + * are names of variables. Whenever such a variable is re-assigned, the knowledge + * is cleared. + * + * For elementary statements, we check if it is an SSTORE(x, y) / MSTORE(x, y) + * If yes, visit the statement. Then record that fact and clear all storage slots t + * where we cannot prove x != t or y == m_storage[t] using the current values of the variables x and t. + * Otherwise, determine if the statement invalidates storage/memory. If yes, clear all knowledge + * about storage/memory before visiting the statement. Then visit the statement. + * + * For forward-joining control flow, storage/memory information from the branches is combined. + * If the keys or values are different or non-existent in one branch, the key is deleted. + * This works also for memory (where addresses overlap) because one branch is always an + * older version of the other and thus overlapping contents would have been deleted already + * at the point of assignment. + * * Prerequisite: Disambiguator, ForLoopInitRewriter. */ class DataFlowAnalyzer: public ASTModifier { public: - explicit DataFlowAnalyzer(Dialect const& _dialect): m_dialect(_dialect) {} + explicit DataFlowAnalyzer(Dialect const& _dialect): + m_dialect(_dialect), + m_knowledgeBase(_dialect, m_value) + {} using ASTModifier::operator(); + void operator()(ExpressionStatement& _statement) override; void operator()(Assignment& _assignment) override; void operator()(VariableDeclaration& _varDecl) override; void operator()(If& _if) override; @@ -70,15 +96,45 @@ protected: /// for example at points where control flow is merged. void clearValues(std::set _names); + /// Clears knowledge about storage or memory if they may be modified inside the block. + void clearKnowledgeIfInvalidated(Block const& _block); + + /// Clears knowledge about storage or memory if they may be modified inside the expression. + void clearKnowledgeIfInvalidated(Expression const& _expression); + + /// Joins knowledge about storage and memory with an older point in the control-flow. + /// This only works if the current state is a direct successor of the older point, + /// i.e. `_otherStorage` and `_otherMemory` cannot have additional changes. + void joinKnowledge( + InvertibleMap const& _olderStorage, + InvertibleMap const& _olderMemory + ); + + static void joinKnowledgeHelper( + InvertibleMap& _thisData, + InvertibleMap const& _olderData + ); + /// Returns true iff the variable is in scope. bool inScope(YulString _variableName) const; + boost::optional> isSimpleStore( + dev::eth::Instruction _store, + ExpressionStatement const& _statement + ) const; + + Dialect const& m_dialect; + /// Current values of variables, always movable. std::map m_value; - /// m_references[a].contains(b) <=> the current expression assigned to a references b - std::map> m_references; - /// m_referencedBy[b].contains(a) <=> the current expression assigned to a references b - std::map> m_referencedBy; + /// m_references.forward[a].contains(b) <=> the current expression assigned to a references b + /// m_references.backward[b].contains(a) <=> the current expression assigned to a references b + InvertibleRelation m_references; + + InvertibleMap m_storage; + InvertibleMap m_memory; + + KnowledgeBase m_knowledgeBase; struct Scope { @@ -91,7 +147,6 @@ protected: Expression const m_zero{Literal{{}, LiteralKind::Number, YulString{"0"}, {}}}; /// List of scopes. std::vector m_variableScopes; - Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/KnowledgeBase.cpp b/libyul/optimiser/KnowledgeBase.cpp new file mode 100644 index 000000000..2c13ac05b --- /dev/null +++ b/libyul/optimiser/KnowledgeBase.cpp @@ -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 . +*/ +/** + * Class that can answer questions about values of variables and their relations. + */ + +#include + +#include +#include +#include +#include + +#include + +using namespace yul; +using namespace dev; + +bool KnowledgeBase::knownToBeDifferent(YulString _a, YulString _b) +{ + // Try to use the simplification rules together with the + // current values to turn `sub(_a, _b)` into a nonzero constant. + // If that fails, try `eq(_a, _b)`. + + Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr1.type() == typeid(Literal)) + return valueOfLiteral(boost::get(expr1)) != 0; + + Expression expr2 = simplify(FunctionCall{{}, {{}, "eq"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr2.type() == typeid(Literal)) + return valueOfLiteral(boost::get(expr2)) == 0; + + return false; +} + +bool KnowledgeBase::knownToBeDifferentByAtLeast32(YulString _a, YulString _b) +{ + // Try to use the simplification rules together with the + // current values to turn `sub(_a, _b)` into a constant whose absolute value is at least 32. + + Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, make_vector(Identifier{{}, _a}, Identifier{{}, _b})}); + if (expr1.type() == typeid(Literal)) + { + u256 val = valueOfLiteral(boost::get(expr1)); + return val >= 32 && val <= u256(0) - 32; + } + + return false; +} + +Expression KnowledgeBase::simplify(Expression _expression) +{ + bool startedRecursion = (m_recursionCounter == 0); + dev::ScopeGuard{[&] { if (startedRecursion) m_recursionCounter = 0; }}; + + if (startedRecursion) + m_recursionCounter = 100; + else if (m_recursionCounter == 1) + return _expression; + else + --m_recursionCounter; + + if (_expression.type() == typeid(FunctionCall)) + for (Expression& arg: boost::get(_expression).arguments) + arg = simplify(arg); + else if (_expression.type() == typeid(FunctionalInstruction)) + for (Expression& arg: boost::get(_expression).arguments) + arg = simplify(arg); + + if (auto match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_variableValues)) + return simplify(match->action().toExpression(locationOf(_expression))); + + return _expression; +} diff --git a/libyul/optimiser/KnowledgeBase.h b/libyul/optimiser/KnowledgeBase.h new file mode 100644 index 000000000..35e2d40ee --- /dev/null +++ b/libyul/optimiser/KnowledgeBase.h @@ -0,0 +1,57 @@ +/* + 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 . +*/ +/** + * Class that can answer questions about values of variables and their relations. + */ + +#pragma once + +#include +#include +#include + +namespace yul +{ + +struct Dialect; + +/** + * Class that can answer questions about values of variables and their relations. + * + * The reference to the map of values provided at construction is assumed to be updating. + */ +class KnowledgeBase +{ +public: + KnowledgeBase(Dialect const& _dialect, std::map const& _variableValues): + m_dialect(_dialect), + m_variableValues(_variableValues) + {} + + bool knownToBeDifferent(YulString _a, YulString _b); + bool knownToBeDifferentByAtLeast32(YulString _a, YulString _b); + bool knownToBeEqual(YulString _a, YulString _b) const { return _a == _b; } + +private: + Expression simplify(Expression _expression); + + Dialect const& m_dialect; + std::map const& m_variableValues; + size_t m_recursionCounter = 0; +}; + +} diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp new file mode 100644 index 000000000..b33963e94 --- /dev/null +++ b/libyul/optimiser/LoadResolver.cpp @@ -0,0 +1,66 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` by the value + * currently stored in storage, if known. + */ + +#include + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace yul; + +void LoadResolver::run(Dialect const& _dialect, Block& _ast) +{ + bool containsMSize = SideEffectsCollector(_dialect, _ast).containsMSize(); + LoadResolver{_dialect, !containsMSize}(_ast); +} + +void LoadResolver::visit(Expression& _e) +{ + 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; + } + } + } +} diff --git a/libyul/optimiser/LoadResolver.h b/libyul/optimiser/LoadResolver.h new file mode 100644 index 000000000..3db124594 --- /dev/null +++ b/libyul/optimiser/LoadResolver.h @@ -0,0 +1,57 @@ +/* + 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 . +*/ +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` by the value + * currently stored in storage, if known. + */ + +#pragma once + +#include + +namespace yul +{ + +struct EVMDialect; + +/** + * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value + * currently stored in storage resp. memory, if known. + * + * Works best if the code is in SSA form. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class LoadResolver: public DataFlowAnalyzer +{ +public: + static void run(Dialect const& _dialect, Block& _ast); + +private: + LoadResolver(Dialect const& _dialect, bool _optimizeMLoad): + DataFlowAnalyzer(_dialect), + m_optimizeMLoad(_optimizeMLoad) + {} + +protected: + using ASTModifier::visit; + void visit(Expression& _e) override; + + bool m_optimizeMLoad = false; +}; + +} diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index a8f9807df..dfc8d98b5 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -169,93 +169,6 @@ void CodeCost::addInstructionCost(eth::Instruction _instruction) m_cost += 49; } -size_t GasMeter::costs(Expression const& _expression) const -{ - return combineCosts(GasMeterVisitor::costs(_expression, m_dialect, m_isCreation)); -} - -size_t GasMeter::instructionCosts(eth::Instruction _instruction) const -{ - return combineCosts(GasMeterVisitor::instructionCosts(_instruction, m_dialect, m_isCreation)); -} - -size_t GasMeter::combineCosts(std::pair _costs) const -{ - return _costs.first * m_runs + _costs.second; -} - - -pair GasMeterVisitor::costs( - Expression const& _expression, - EVMDialect const& _dialect, - bool _isCreation -) -{ - GasMeterVisitor gmv(_dialect, _isCreation); - gmv.visit(_expression); - return {gmv.m_runGas, gmv.m_dataGas}; -} - -pair GasMeterVisitor::instructionCosts( - dev::eth::Instruction _instruction, - EVMDialect const& _dialect, - bool _isCreation -) -{ - GasMeterVisitor gmv(_dialect, _isCreation); - gmv.instructionCostsInternal(_instruction); - return {gmv.m_runGas, gmv.m_dataGas}; -} - -void GasMeterVisitor::operator()(FunctionCall const& _funCall) -{ - ASTWalker::operator()(_funCall); - if (BuiltinFunctionForEVM const* f = m_dialect.builtin(_funCall.functionName.name)) - if (f->instruction) - { - instructionCostsInternal(*f->instruction); - return; - } - yulAssert(false, "Functions not implemented."); -} - -void GasMeterVisitor::operator()(FunctionalInstruction const& _fun) -{ - ASTWalker::operator()(_fun); - instructionCostsInternal(_fun.instruction); -} - -void GasMeterVisitor::operator()(Literal const& _lit) -{ - m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::PUSH1); - m_dataGas += - singleByteDataGas() + - size_t(dev::eth::GasMeter::dataGas(dev::toCompactBigEndian(valueOfLiteral(_lit), 1), m_isCreation)); -} - -void GasMeterVisitor::operator()(Identifier const&) -{ - m_runGas += dev::eth::GasMeter::runGas(dev::eth::Instruction::DUP1); - m_dataGas += singleByteDataGas(); -} - -size_t GasMeterVisitor::singleByteDataGas() const -{ - if (m_isCreation) - return dev::eth::GasCosts::txDataNonZeroGas; - else - return dev::eth::GasCosts::createDataGas; -} - -void GasMeterVisitor::instructionCostsInternal(dev::eth::Instruction _instruction) -{ - if (_instruction == eth::Instruction::EXP) - m_runGas += dev::eth::GasCosts::expGas + dev::eth::GasCosts::expByteGas(m_dialect.evmVersion()); - else - m_runGas += dev::eth::GasMeter::runGas(_instruction); - m_dataGas += singleByteDataGas(); -} - void AssignmentCounter::operator()(Assignment const& _assignment) { for (auto const& variable: _assignment.variableNames) diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 51c6df9a7..5fe3eef7b 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -22,9 +22,6 @@ #include #include -#include - -#include namespace yul { @@ -96,75 +93,6 @@ private: size_t m_cost = 0; }; -/** - * Gas meter for expressions only involving literals, identifiers and - * EVM instructions. - * - * Assumes that EXP is not used with exponents larger than a single byte. - * Is not particularly exact for anything apart from arithmetic. - */ -class GasMeter -{ -public: - GasMeter(EVMDialect const& _dialect, bool _isCreation, size_t _runs): - m_dialect(_dialect), - m_isCreation{_isCreation}, - m_runs(_runs) - {} - - /// @returns the full combined costs of deploying and evaluating the expression. - size_t costs(Expression const& _expression) const; - /// @returns the combined costs of deploying and running the instruction, not including - /// the costs for its arguments. - size_t instructionCosts(dev::eth::Instruction _instruction) const; - -private: - size_t combineCosts(std::pair _costs) const; - - EVMDialect const& m_dialect; - bool m_isCreation = false; - size_t m_runs; -}; - -class GasMeterVisitor: public ASTWalker -{ -public: - static std::pair costs( - Expression const& _expression, - EVMDialect const& _dialect, - bool _isCreation - ); - - static std::pair instructionCosts( - dev::eth::Instruction _instruction, - EVMDialect const& _dialect, - bool _isCreation = false - ); - -public: - GasMeterVisitor(EVMDialect const& _dialect, bool _isCreation): - m_dialect(_dialect), - m_isCreation{_isCreation} - {} - - void operator()(FunctionCall const& _funCall) override; - void operator()(FunctionalInstruction const& _instr) override; - void operator()(Literal const& _literal) override; - void operator()(Identifier const& _identifier) override; - -private: - size_t singleByteDataGas() const; - /// Computes the cost of storing and executing the single instruction (excluding its arguments). - /// For EXP, it assumes that the exponent is at most 255. - /// Does not work particularly exact for anything apart from arithmetic. - void instructionCostsInternal(dev::eth::Instruction _instruction); - - EVMDialect const& m_dialect; - bool m_isCreation = false; - size_t m_runGas = 0; - size_t m_dataGas = 0; -}; - /** * Counts the number of assignments to every variable. * Only works after running the Disambiguator. diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp index 2a0c1e44f..f5bc1314a 100644 --- a/libyul/optimiser/NameDispenser.cpp +++ b/libyul/optimiser/NameDispenser.cpp @@ -32,8 +32,8 @@ using namespace std; using namespace dev; using namespace yul; -NameDispenser::NameDispenser(Dialect const& _dialect, Block const& _ast): - NameDispenser(_dialect, NameCollector(_ast).names()) +NameDispenser::NameDispenser(Dialect const& _dialect, Block const& _ast, set _reservedNames): + NameDispenser(_dialect, NameCollector(_ast).names() + std::move(_reservedNames)) { } diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h index 9591dd135..db871f905 100644 --- a/libyul/optimiser/NameDispenser.h +++ b/libyul/optimiser/NameDispenser.h @@ -39,13 +39,17 @@ class NameDispenser { public: /// Initialize the name dispenser with all the names used in the given AST. - explicit NameDispenser(Dialect const& _dialect, Block const& _ast); + explicit NameDispenser(Dialect const& _dialect, Block const& _ast, std::set _reservedNames = {}); /// Initialize the name dispenser with the given used names. explicit NameDispenser(Dialect const& _dialect, std::set _usedNames); /// @returns a currently unused name that should be similar to _nameHint. YulString newName(YulString _nameHint); + /// Mark @a _name as used, i.e. the dispenser's newName function will not + /// return it. + void markUsed(YulString _name) { m_usedNames.insert(_name); } + private: bool illegalName(YulString _name); diff --git a/libyul/optimiser/NameDisplacer.cpp b/libyul/optimiser/NameDisplacer.cpp new file mode 100644 index 000000000..b143e3d98 --- /dev/null +++ b/libyul/optimiser/NameDisplacer.cpp @@ -0,0 +1,86 @@ +/* + 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 . +*/ +/** + * Optimiser component that renames identifiers to free up certain names. + */ + +#include + +#include + + +using namespace std; +using namespace dev; +using namespace yul; + +void NameDisplacer::operator()(Identifier& _identifier) +{ + checkAndReplace(_identifier.name); +} + +void NameDisplacer::operator()(VariableDeclaration& _varDecl) +{ + for (TypedName& var: _varDecl.variables) + checkAndReplaceNew(var.name); + + ASTModifier::operator()(_varDecl); +} + +void NameDisplacer::operator()(FunctionDefinition& _function) +{ + // Should have been done in the block already. + yulAssert(!m_namesToFree.count(_function.name), ""); + + for (auto& param: _function.parameters) + checkAndReplaceNew(param.name); + for (auto& retVar: _function.returnVariables) + checkAndReplaceNew(retVar.name); + + ASTModifier::operator()(_function); +} + +void NameDisplacer::operator()(FunctionCall& _funCall) +{ + checkAndReplace(_funCall.functionName.name); + ASTModifier::operator()(_funCall); +} + +void NameDisplacer::operator()(Block& _block) +{ + // First replace all the names of function definitions + // because of scoping. + for (auto& st: _block.statements) + if (st.type() == typeid(FunctionDefinition)) + checkAndReplaceNew(boost::get(st).name); + + ASTModifier::operator()(_block); +} + +void NameDisplacer::checkAndReplaceNew(YulString& _name) +{ + yulAssert(!m_translations.count(_name), ""); + if (m_namesToFree.count(_name)) + _name = (m_translations[_name] = m_nameDispenser.newName(_name)); +} + +void NameDisplacer::checkAndReplace(YulString& _name) const +{ + if (m_translations.count(_name)) + _name = m_translations.at(_name); + yulAssert(!m_namesToFree.count(_name), ""); +} + diff --git a/libyul/optimiser/NameDisplacer.h b/libyul/optimiser/NameDisplacer.h new file mode 100644 index 000000000..ed025d9f6 --- /dev/null +++ b/libyul/optimiser/NameDisplacer.h @@ -0,0 +1,70 @@ +/* + 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 . +*/ +/** + * Optimiser component that renames identifiers to free up certain names. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace yul +{ +struct Dialect; + +/** + * Optimiser component that renames identifiers to free up certain names. + * + * Prerequisites: Disambiguator + */ +class NameDisplacer: public ASTModifier +{ +public: + explicit NameDisplacer( + NameDispenser& _dispenser, + std::set const& _namesToFree + ): + m_nameDispenser(_dispenser), + m_namesToFree(_namesToFree) + { + for (YulString n: _namesToFree) + m_nameDispenser.markUsed(n); + } + + using ASTModifier::operator(); + void operator()(Identifier& _identifier) override; + void operator()(VariableDeclaration& _varDecl) override; + void operator()(FunctionDefinition& _function) override; + void operator()(FunctionCall& _funCall) override; + void operator()(Block& _block) override; + +protected: + /// Check if the newly introduced identifier @a _name has to be replaced. + void checkAndReplaceNew(YulString& _name); + /// Replace the identifier @a _name if it is in the translation map. + void checkAndReplace(YulString& _name) const; + + NameDispenser& m_nameDispenser; + std::set const& m_namesToFree; + std::map m_translations; +}; + +} diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index c70b02423..e11ed98f3 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -81,7 +81,7 @@ void Rematerialiser::visit(Expression& _e) if (refs <= 1 || cost == 0 || (refs <= 5 && cost <= 1) || m_varsToAlwaysRematerialize.count(name)) { assertThrow(m_referenceCounts[name] > 0, OptimizerException, ""); - for (auto const& ref: m_references[name]) + for (auto const& ref: m_references.forward[name]) assertThrow(inScope(ref), OptimizerException, ""); // update reference counts m_referenceCounts[name]--; diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index f9e9e8338..4426c63a8 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -63,6 +63,10 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr) 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; } void SideEffectsCollector::operator()(FunctionCall const& _functionCall) @@ -79,12 +83,18 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall) m_sideEffectFreeIfNoMSize = false; if (f->isMSize) m_containsMSize = true; + if (f->invalidatesStorage) + m_invalidatesStorage = true; + if (f->invalidatesMemory) + m_invalidatesMemory = true; } else { m_movable = false; m_sideEffectFree = false; m_sideEffectFreeIfNoMSize = false; + m_invalidatesStorage = true; + m_invalidatesMemory = true; } } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 6c36ca0d4..4dcec2e5e 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -54,6 +54,8 @@ public: } bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } bool containsMSize() const { return m_containsMSize; } + bool invalidatesStorage() const { return m_invalidatesStorage; } + bool invalidatesMemory() const { return m_invalidatesMemory; } private: Dialect const& m_dialect; @@ -69,6 +71,10 @@ private: /// 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 coded under all + /// circumstances. + bool m_invalidatesStorage = false; + bool m_invalidatesMemory = false; }; /** diff --git a/libyul/optimiser/StackCompressor.cpp b/libyul/optimiser/StackCompressor.cpp index 826bd2e05..87492e131 100644 --- a/libyul/optimiser/StackCompressor.cpp +++ b/libyul/optimiser/StackCompressor.cpp @@ -57,7 +57,7 @@ public: for (auto const& codeCost: m_expressionCodeCost) { size_t numRef = m_numReferences[codeCost.first]; - cand.emplace(make_tuple(codeCost.second * numRef, codeCost.first, m_references[codeCost.first])); + cand.emplace(make_tuple(codeCost.second * numRef, codeCost.first, m_references.forward[codeCost.first])); } return cand; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 4772bbb3a..60d105263 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -45,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -60,7 +60,7 @@ using namespace yul; void OptimiserSuite::run( Dialect const& _dialect, - GasMeter const& _meter, + GasMeter const* _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, @@ -68,6 +68,7 @@ void OptimiserSuite::run( ) { set reservedIdentifiers = _externallyUsedIdentifiers; + reservedIdentifiers += _dialect.fixedFunctionNames(); Block ast = boost::get(Disambiguator(_dialect, _analysisInfo, reservedIdentifiers)(_ast)); @@ -87,7 +88,7 @@ void OptimiserSuite::run( // None of the above can make stack problems worse. - NameDispenser dispenser{_dialect, ast}; + NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; size_t codeSize = 0; for (size_t rounds = 0; rounds < 12; ++rounds) @@ -209,7 +210,11 @@ void OptimiserSuite::run( FunctionGrouper{}(ast); - ConstantOptimiser{dynamic_cast(_dialect), _meter}(ast); + if (EVMDialect const* dialect = dynamic_cast(&_dialect)) + { + yulAssert(_meter, ""); + ConstantOptimiser{*dialect, *_meter}(ast); + } VarNameCleaner{ast, _dialect, reservedIdentifiers}(ast); yul::AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, ast); diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 03806eee6..3f35b572a 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -41,7 +41,7 @@ class OptimiserSuite public: static void run( Dialect const& _dialect, - GasMeter const& _meter, + GasMeter const* _meter, Block& _ast, AsmAnalysisInfo const& _analysisInfo, bool _optimizeStackAllocation, diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 00ccbc8e5..624f9d335 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -92,12 +93,10 @@ void UnusedPruner::operator()(Block& _block) subtractReferences(ReferencesCounter::countReferences(*varDecl.value)); statement = Block{std::move(varDecl.location), {}}; } - else if (varDecl.variables.size() == 1) - // In pure Yul, this should be replaced by a function call to `drop` - // instead of `pop`. - statement = ExpressionStatement{varDecl.location, FunctionalInstruction{ + else if (varDecl.variables.size() == 1 && m_dialect.discardFunction()) + statement = ExpressionStatement{varDecl.location, FunctionCall{ varDecl.location, - dev::eth::Instruction::POP, + {varDecl.location, m_dialect.discardFunction()->name}, {*std::move(varDecl.value)} }}; } diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt index d6538ee29..4e78207e2 100644 --- a/lllc/CMakeLists.txt +++ b/lllc/CMakeLists.txt @@ -1,5 +1,5 @@ add_executable(lllc main.cpp) -target_link_libraries(lllc PRIVATE lll ${Boost_SYSTEM_LIBRARY}) +target_link_libraries(lllc PRIVATE lll Boost::boost Boost::system) if (INSTALL_LLLC) include(GNUInstallDirs) diff --git a/scripts/check_style.sh b/scripts/check_style.sh index 01e50662a..afecc06bc 100755 --- a/scripts/check_style.sh +++ b/scripts/check_style.sh @@ -18,6 +18,7 @@ fi FORMATERROR=$( ( git grep -nIE "\<(if|for)\(" -- '*.h' '*.cpp' # no space after "if" or "for" + git grep -nIE "\\s*\([^=]*\>\s:\s.*\)" -- '*.h' '*.cpp' # no space before range based for-loop git grep -nIE "\\s*\(.*\)\s*\{\s*$" -- '*.h' '*.cpp' # "{\n" on same line as "if" / "for" git grep -nIE "[,\(<]\s*const " -- '*.h' '*.cpp' # const on left side of type git grep -nIE "^\s*(static)?\s*const " -- '*.h' '*.cpp' # const on left side of type (beginning of line) diff --git a/scripts/common.sh b/scripts/common.sh new file mode 100644 index 000000000..f1e5cf134 --- /dev/null +++ b/scripts/common.sh @@ -0,0 +1,96 @@ +# ------------------------------------------------------------------------------ +# vim:ts=4:et +# 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. +# ------------------------------------------------------------------------------ + +if [ "$CIRCLECI" ] +then + export TERM="${TERM:-xterm}" + function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } + function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput setaf 7)"; } +else + function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } + function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } +fi + +safe_kill() +{ + local PID=${1} + local NAME=${2:-${1}} + local n=1 + + # only proceed if $PID does exist + kill -0 $PID 2>/dev/null || return + + echo "Sending SIGTERM to ${NAME} (${PID}) ..." + kill $PID + + # wait until process terminated gracefully + while kill -0 $PID 2>/dev/null && [[ $n -le 4 ]]; do + echo "Waiting ($n) ..." + sleep 1 + n=$[n + 1] + done + + # process still alive? then hard-kill + if kill -0 $PID 2>/dev/null; then + echo "Sending SIGKILL to ${NAME} (${PID}) ..." + kill -9 $PID + fi +} + +# Ensures aleth executable and exposes the following information: +# +# - env var ALETH_PATH: to point to the aleth executable. +# - directory /tmp/test if needed. No cleanup is done on this directory +function download_aleth() +{ + if which aleth &>/dev/null; then + ALETH_PATH=`which aleth` + elif [[ "$OSTYPE" == "darwin"* ]]; then + ALETH_PATH="$(realpath $(dirname "$0")/..)/aleth" + elif [ "$CIRCLECI" ] || [ -z $CI ]; then + ALETH_PATH="aleth" + else + ALETH_PATH="/tmp/test/bin/aleth" + mkdir -p /tmp/test + # Any time the hash is updated here, the "Running the compiler tests" section in contributing.rst should also be updated. + local ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" + local ALETH_VERSION="1.6.0" + wget -q -O /tmp/test/aleth.tar.gz https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz + test "$(shasum /tmp/test/aleth.tar.gz)" = "$ALETH_HASH /tmp/test/aleth.tar.gz" + tar -xf /tmp/test/aleth.tar.gz -C /tmp/test + sync + chmod +x $ALETH_PATH + sync # Otherwise we might get a "text file busy" error + fi +} + +# Executes aleth in the background and echos its PID. +function run_aleth() +{ + $ALETH_PATH --db memorydb --test -d "${WORKDIR}" &> /dev/null & + echo $! + + # Wait until the IPC endpoint is available. + while [ ! -S "${WORKDIR}/geth.ipc" ] ; do sleep 1; done + sleep 2 +} + diff --git a/scripts/run_proofs.sh b/scripts/run_proofs.sh new file mode 100755 index 000000000..3a89d6b70 --- /dev/null +++ b/scripts/run_proofs.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e + +( +REPO_ROOT="$(dirname "$0")"/.. +cd "$REPO_ROOT" + +git fetch origin +error=0 +for new_proof in $(git diff origin/develop --name-only test/formal/) +do + set +e + echo "Proving $new_proof..." + output=$(python "$new_proof") + result=$? + set -e + + if [[ "$result" != 0 ]] + then + echo "Proof $(basename "$new_proof" ".py") failed: $output." + error=1 + fi +done + +if [[ "error" -eq 0 ]] +then + echo "All proofs succeeded." +fi + +exit $error +) diff --git a/scripts/tests.sh b/scripts/tests.sh index d0882aff4..0350de5a0 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -28,7 +28,9 @@ set -e -REPO_ROOT="$(dirname "$0")"/.. +REPO_ROOT="$(dirname "$0")/.." + +source "${REPO_ROOT}/scripts/common.sh" WORKDIR=`mktemp -d` # Will be printed in case of a test failure @@ -40,38 +42,8 @@ CMDLINE_PID= if [[ "$OSTYPE" == "darwin"* ]] then SMT_FLAGS="--no-smt" - if [ "$CIRCLECI" ] - then - IPC_ENABLED=false - IPC_FLAGS="--no-ipc" - fi fi -safe_kill() { - local PID=${1} - local NAME=${2:-${1}} - local n=1 - - # only proceed if $PID does exist - kill -0 $PID 2>/dev/null || return - - echo "Sending SIGTERM to ${NAME} (${PID}) ..." - kill $PID - - # wait until process terminated gracefully - while kill -0 $PID 2>/dev/null && [[ $n -le 4 ]]; do - echo "Waiting ($n) ..." - sleep 1 - n=$[n + 1] - done - - # process still alive? then hard-kill - if kill -0 $PID 2>/dev/null; then - echo "Sending SIGKILL to ${NAME} (${PID}) ..." - kill -9 $PID - fi -} - cleanup() { # ensure failing commands don't cause termination during cleanup (especially within safe_kill) set +e @@ -103,15 +75,6 @@ else log_directory="" fi -if [ "$CIRCLECI" ] -then - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } -else - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } -fi - printTask "Running commandline tests..." # Only run in parallel if this is run on CI infrastructure if [[ -n "$CI" ]] @@ -126,41 +89,6 @@ else fi fi -function download_aleth() -{ - if [[ "$OSTYPE" == "darwin"* ]]; then - ALETH_PATH="$REPO_ROOT/aleth" - elif [ -z $CI ]; then - ALETH_PATH="aleth" - else - mkdir -p /tmp/test - # Any time the hash is updated here, the "Running the compiler tests" section in contributing.rst should also be updated. - ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6" - ALETH_VERSION=1.6.0 - wget -q -O /tmp/test/aleth.tar.gz https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz - test "$(shasum /tmp/test/aleth.tar.gz)" = "$ALETH_HASH /tmp/test/aleth.tar.gz" - tar -xf /tmp/test/aleth.tar.gz -C /tmp/test - ALETH_PATH="/tmp/test/bin/aleth" - sync - chmod +x $ALETH_PATH - sync # Otherwise we might get a "text file busy" error - fi - -} - -# $1: data directory -# echos the PID -function run_aleth() -{ - # Use this to have aleth log output - #$REPO_ROOT/scripts/aleth_with_log.sh $ALETH_PATH $ALETH_TMP_OUT --log-verbosity 3 --db memorydb --test -d "${WORKDIR}" &> /dev/null & - $ALETH_PATH --db memorydb --test -d "${WORKDIR}" &> /dev/null & - echo $! - # Wait until the IPC endpoint is available. - while [ ! -S "${WORKDIR}/geth.ipc" ] ; do sleep 1; done - sleep 2 -} - function check_aleth() { printTask "Running IPC tests with $ALETH_PATH..." if ! hash $ALETH_PATH 2>/dev/null; then @@ -176,17 +104,11 @@ then ALETH_PID=$(run_aleth) fi -progress="--show-progress" -if [ "$CIRCLECI" ] -then - progress="" -fi - EVM_VERSIONS="homestead byzantium" -if [ "$CIRCLECI" ] || [ -z "$CI" ] +if [ -z "$CI" ] then -EVM_VERSIONS+=" constantinople petersburg" + EVM_VERSIONS+=" constantinople petersburg" fi # And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer @@ -212,16 +134,16 @@ do log="" if [ -n "$log_directory" ] then - if [ -n "$optimize" ] - then - log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs - else - log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt - fi + if [ -n "$optimize" ] + then + log=--logger=JUNIT,error,$log_directory/opt_$vm.xml $testargs + else + log=--logger=JUNIT,error,$log_directory/noopt_$vm.xml $testargs_no_opt + fi fi set +e - "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS $force_abiv2_flag --ipcpath "${WORKDIR}/geth.ipc" + "$REPO_ROOT"/build/test/soltest --show-progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" $SMT_FLAGS $IPC_FLAGS $force_abiv2_flag --ipcpath "${WORKDIR}/geth.ipc" if test "0" -ne "$?"; then if [ -n "$log_directory" ] diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 5b5540b6d..a35a4f49d 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -64,15 +64,14 @@ fi # Boost echo -en 'travis_fold:start:compiling_boost\\r' -cd "$WORKSPACE"/boost_1_68_0 -# if b2 exists, it is a fresh checkout, otherwise it comes from the cache -# and is already compiled -test -e b2 && ( +test -e "$WORKSPACE"/boost_1_70_0_install/include/boost/version.hpp || ( +cd "$WORKSPACE"/boost_1_70_0 ./b2 toolset=emscripten link=static variant=release threading=single runtime-link=static \ - system regex filesystem unit_test_framework program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" -find . -name 'libboost*.a' -exec cp {} . \; -rm -rf b2 libs doc tools more bin.v2 status + --with-system --with-regex --with-filesystem --with-test --with-program_options cxxflags="-Wno-unused-local-typedef -Wno-variadic-macros -Wno-c99-extensions -Wno-all" \ + --prefix="$WORKSPACE"/boost_1_70_0_install install ) +ln -sf "$WORKSPACE"/boost_1_70_0_install/lib/* /emsdk_portable/sdk/system/lib +ln -sf "$WORKSPACE"/boost_1_70_0_install/include/* /emsdk_portable/sdk/system/include echo -en 'travis_fold:end:compiling_boost\\r' echo -en 'travis_fold:start:install_cmake.sh\\r' @@ -87,15 +86,8 @@ cd $BUILD_DIR cmake \ -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/emscripten.cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DBoost_FOUND=1 \ -DBoost_USE_STATIC_LIBS=1 \ -DBoost_USE_STATIC_RUNTIME=1 \ - -DBoost_INCLUDE_DIR="$WORKSPACE"/boost_1_68_0/ \ - -DBoost_FILESYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_filesystem.a \ - -DBoost_PROGRAM_OPTIONS_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_program_options.a \ - -DBoost_REGEX_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_regex.a \ - -DBoost_SYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_system.a \ - -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_unit_test_framework.a \ -DTESTS=0 \ .. make -j 4 diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 5959ef129..fc81851c2 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -30,15 +30,15 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' -test -e boost_1_68_0 -a -e boost_1_68_0/boost || ( -rm -rf boost_1_68_0 -rm -f boost.tar.xz -wget -q 'https://sourceforge.net/projects/boost/files/boost/1.68.0/boost_1_68_0.tar.gz/download'\ - -O boost.tar.xz -test "$(shasum boost.tar.xz)" = "a78cf6ebb111a48385dd0c135e145a6819a8c856 boost.tar.xz" -tar -xzf boost.tar.xz -rm boost.tar.xz -cd boost_1_68_0 +test -e boost_1_70_0_install/include/boost/version.hpp || ( +rm -rf boost_1_70_0 +rm -f boost.tar.gz +wget -q 'https://sourceforge.net/projects/boost/files/boost/1.70.0/boost_1_70_0.tar.gz/download'\ + -O boost.tar.gz +test "$(shasum boost.tar.gz)" = "7804c782deb00f36ac80b1000b71a3707eadb620 boost.tar.gz" +tar -xzf boost.tar.gz +rm boost.tar.gz +cd boost_1_70_0 ./bootstrap.sh wget -q 'https://raw.githubusercontent.com/tee3/boost-build-emscripten/master/emscripten.jam' test "$(shasum emscripten.jam)" = "a7e13fc2c1e53b0e079ef440622f879aa6da3049 emscripten.jam" diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt index d9b12a063..3fbdefe6a 100644 --- a/solc/CMakeLists.txt +++ b/solc/CMakeLists.txt @@ -5,7 +5,7 @@ set( ) add_executable(solc ${sources}) -target_link_libraries(solc PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(solc PRIVATE solidity Boost::boost Boost::program_options) include(GNUInstallDirs) install(TARGETS solc DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 764376236..e49359ecd 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -112,6 +112,7 @@ static string const g_strBinaryRuntime = "bin-runtime"; static string const g_strCombinedJson = "combined-json"; static string const g_strCompactJSON = "compact-format"; static string const g_strContracts = "contracts"; +static string const g_strErrorRecovery = "error-recovery"; static string const g_strEVM = "evm"; static string const g_strEVM15 = "evm15"; static string const g_strEVMVersion = "evm-version"; @@ -163,6 +164,7 @@ static string const g_argBinary = g_strBinary; static string const g_argBinaryRuntime = g_strBinaryRuntime; static string const g_argCombinedJson = g_strCombinedJson; static string const g_argCompactJSON = g_strCompactJSON; +static string const g_argErrorRecovery = g_strErrorRecovery; static string const g_argGas = g_strGas; static string const g_argHelp = g_strHelp; static string const g_argInputFile = g_strInputFile; @@ -689,6 +691,7 @@ Allowed options)", (g_argColor.c_str(), "Force colored output.") (g_argNoColor.c_str(), "Explicitly disable colored output, disabling terminal auto-detection.") (g_argNewReporter.c_str(), "Enables new diagnostics reporter.") + (g_argErrorRecovery.c_str(), "Enables additional parser error recovery.") (g_argIgnoreMissingFiles.c_str(), "Ignore missing files."); po::options_description outputComponents("Output Components"); outputComponents.add_options() @@ -923,6 +926,8 @@ bool CommandLineInterface::processInput() m_compiler->setSources(m_sourceCodes); if (m_args.count(g_argLibraries)) m_compiler->setLibraries(m_libraries); + if (m_args.count(g_argErrorRecovery)) + m_compiler->setParserErrorRecovery(true); m_compiler->setEVMVersion(m_evmVersion); // TODO: Perhaps we should not compile unless requested @@ -984,6 +989,13 @@ bool CommandLineInterface::processInput() serr() << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << "Unknown exception during compilation" << ( + _e.what() ? ": " + string(_e.what()) : "." + ) << endl; + return false; + } catch (...) { serr() << "Unknown exception during compilation." << endl; @@ -1090,8 +1102,7 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->ast(sourceCode.first)); map gasCosts; - for (auto const& contract : m_compiler->contractNames()) - { + for (auto const& contract: m_compiler->contractNames()) if (auto const* assemblyItems = m_compiler->runtimeAssemblyItems(contract)) { auto ret = GasEstimator::breakToStatementLevel( @@ -1102,8 +1113,6 @@ void CommandLineInterface::handleAst(string const& _argStr) gasCosts[it.first] += it.second; } - } - bool legacyFormat = !m_args.count(g_argAstCompactJson); if (m_args.count(g_argOutputDir)) { @@ -1274,6 +1283,14 @@ bool CommandLineInterface::assemble( serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << + "Unknown exception during compilation" << + (_e.what() ? ": " + string(_e.what()) : ".") << + endl; + return false; + } catch (...) { serr() << "Unknown exception in assembler." << endl; @@ -1324,6 +1341,13 @@ bool CommandLineInterface::assemble( serr() << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl; return false; } + catch (std::exception const& _e) + { + serr() << "Unknown exception during compilation" << ( + _e.what() ? ": " + string(_e.what()) : "." + ) << endl; + return false; + } catch (...) { serr() << "Unknown exception while assembling." << endl; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index acc43b68d..a666fb5f4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -30,7 +30,15 @@ add_executable(soltest ${sources} ${headers} ${libsolidity_sources} ${libsolidity_headers} ${libsolidity_util_sources} ${libsolidity_util_headers} ) -target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore Boost::boost Boost::program_options Boost::unit_test_framework) + +# Special compilation flag for Visual Studio (version 2019 at least affected) +# in order to compile SolidityEndToEndTest.cpp, which is quite huge. +# We can remove this flag once we've extracted the tests. +# TODO: Remove this option as soon as we have reduced the SLoC's in SolidityEndToEndTest.cpp +if (MSVC) + target_compile_options(soltest PUBLIC "/bigobj") +endif() if (LLL) target_link_libraries(soltest PRIVATE lll) diff --git a/test/InteractiveTests.h b/test/InteractiveTests.h index 24a5ad69e..3f46558d4 100644 --- a/test/InteractiveTests.h +++ b/test/InteractiveTests.h @@ -56,6 +56,7 @@ Testsuite const g_interactiveTestsuites[] = { {"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create}, {"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create}, {"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create}, + {"Error Recovery", "libsolidity", "errorRecoveryTests", false, false, &SyntaxTest::createErrorRecovery}, {"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create}, {"JSON AST", "libsolidity", "ASTJSON", false, false, &ASTJSONTest::create}, {"SMT Checker", "libsolidity", "smtCheckerTests", true, false, &SyntaxTest::create}, @@ -66,4 +67,3 @@ Testsuite const g_interactiveTestsuites[] = { } } } - diff --git a/test/TestCase.cpp b/test/TestCase.cpp index c86635735..76c27d41a 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -95,6 +95,20 @@ string TestCase::parseSourceAndSettings(istream& _stream) return source; } +string TestCase::parseSimpleExpectations(std::istream& _file) +{ + string result; + string line; + while (getline(_file, line)) + if (boost::algorithm::starts_with(line, "// ")) + result += line.substr(3) + "\n"; + else if (line == "//") + result += "\n"; + else + BOOST_THROW_EXCEPTION(runtime_error("Test expectations must start with \"// \".")); + return result; +} + void TestCase::expect(string::iterator& _it, string::iterator _end, string::value_type _c) { if (_it == _end || *_it != _c) diff --git a/test/TestCase.h b/test/TestCase.h index ba5669506..c08f2d77e 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -92,6 +92,8 @@ protected: std::string parseSourceAndSettings(std::istream& _file); static void expect(std::string::iterator& _it, std::string::iterator _end, std::string::value_type _c); + static std::string parseSimpleExpectations(std::istream& _file); + template static void skipWhitespace(IteratorType& _it, IteratorType _end) { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index fc3ab9558..d0e01278d 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -31,6 +31,7 @@ set -e ## GLOBAL VARIABLES REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) +source "${REPO_ROOT}/scripts/common.sh" SOLC="$REPO_ROOT/build/solc/solc" INTERACTIVE=true if ! tty -s || [ "$CI" ] @@ -40,17 +41,13 @@ fi FULLARGS="--optimize --ignore-missing --combined-json abi,asm,ast,bin,bin-runtime,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc" -## FUNCTIONS - -if [ "$CIRCLECI" ] -then - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo "$(tput setaf 1)$1$(tput setaf 7)"; } -else - function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } +# extend stack size in case we run via ASAN +if [[ -n "${CIRCLECI}" ]] || [[ -n "$CI" ]]; then + ulimit -s 16384 + ulimit -a fi +## FUNCTIONS function compileFull() { @@ -112,6 +109,8 @@ function ask_expectation_update() q* ) exit 1;; esac done + else + exit 1 fi } diff --git a/test/cmdlineTests/standard_irOptimized_requested/output.json b/test/cmdlineTests/standard_irOptimized_requested/output.json index 123b975df..ef9af5ab4 100644 --- a/test/cmdlineTests/standard_irOptimized_requested/output.json +++ b/test/cmdlineTests/standard_irOptimized_requested/output.json @@ -1 +1 @@ -{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned_petersburg(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue() { revert(0, 0) }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default { }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n { tail := add(headStart, 0) }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n function shift_right_224_unsigned_petersburg(value) -> newValue\n { newValue := shr(224, value) }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} +{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue() { revert(0, 0) }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default { }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n { tail := add(headStart, 0) }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n for { let return_flag := 1 } return_flag { }\n { break }\n }\n function shift_right_224_unsigned(value) -> newValue\n { newValue := shr(224, value) }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index 1294cdc50..e64f4eea5 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -1 +1 @@ -{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned_petersburg(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\n\t\t\tfunction shift_right_224_unsigned_petersburg(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}} +{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\nobject \"C_6\" {\n\tcode {\n\t\tmstore(64, 128)\n\t\t\n\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\t\n\t\t\n\t\tfunction fun_f_5() {\n\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\tobject \"C_6_deployed\" {\n\t\tcode {\n\t\t\tmstore(64, 128)\n\t\t\t\n\t\t\tif iszero(lt(calldatasize(), 4))\n\t\t\t{\n\t\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\t\tswitch selector\n\t\t\t\t\n\t\t\t\tcase 0x26121ff0\n\t\t\t\t{\n\t\t\t\t\t// f()\n\t\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t\tabi_decode_tuple_(4, calldatasize())\n\t\t\t\t\tfun_f_5()\n\t\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tdefault {}\n\t\t\t}\n\t\t\trevert(0, 0)\n\t\t\t\n\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\t\n\t\t\tfunction fun_f_5() {\n\t\t\t\tfor { let return_flag := 1 } return_flag {} {\n\t\t\t\t\t\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\tnewValue :=\n\t\t\t\t\n\t\t\t\tshr(224, value)\n\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t}\n\t}\n}\n\n"}}},"sources":{"A":{"id":0}}} diff --git a/test/externalTests.sh b/test/externalTests.sh index 482819458..acac38629 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -37,6 +37,7 @@ fi SOLJSON="$1" REPO_ROOT="$(dirname "$0")" +source scripts/common.sh source test/externalTests/common.sh printTask "Running external tests..." diff --git a/test/externalTests/colony.sh b/test/externalTests/colony.sh index 9745d10ce..0e867c997 100755 --- a/test/externalTests/colony.sh +++ b/test/externalTests/colony.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" diff --git a/test/externalTests/common.sh b/test/externalTests/common.sh index ebcdaa9a8..d6574da4d 100644 --- a/test/externalTests/common.sh +++ b/test/externalTests/common.sh @@ -20,16 +20,7 @@ #------------------------------------------------------------------------------ set -e -if [ "$CIRCLECI" ] -then - function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput setaf 7)"; } - function printError() { echo ""; echo "$(tput setaf 1)$1$(tput setaf 7)"; } - function printLog() { echo "$(tput setaf 3)$1$(tput setaf 7)"; } -else - function printTask() { echo ""; echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } - function printError() { echo ""; echo "$(tput setaf 1)$1$(tput sgr0)"; } - function printLog() { echo "$(tput setaf 3)$1$(tput sgr0)"; } -fi +# Requires "${REPO_ROOT}/scripts/common.sh" to be included before. function verify_input { diff --git a/test/externalTests/gnosis.sh b/test/externalTests/gnosis.sh index 095207acc..f86816fd0 100755 --- a/test/externalTests/gnosis.sh +++ b/test/externalTests/gnosis.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" diff --git a/test/externalTests/zeppelin.sh b/test/externalTests/zeppelin.sh index b85309921..b24617f37 100755 --- a/test/externalTests/zeppelin.sh +++ b/test/externalTests/zeppelin.sh @@ -18,6 +18,7 @@ # # (c) 2019 solidity contributors. #------------------------------------------------------------------------------ +source scripts/common.sh source test/externalTests/common.sh verify_input "$1" diff --git a/test/formal/README.md b/test/formal/README.md new file mode 100644 index 000000000..83bd79e76 --- /dev/null +++ b/test/formal/README.md @@ -0,0 +1,6 @@ +The Solidity compiler implements several [optimization rules](https://github.com/ethereum/solidity/blob/develop/libevmasm/RuleList.h). + +This directory contains an effort to formally prove the correctness of those rules in: + +- HOL with [EthIsabelle](https://github.com/ekpyron/eth-isabelle) +- FOL with SMT solvers using [Integers and BitVectors](http://smtlib.cs.uiowa.edu/theories.shtml) diff --git a/test/formal/checked_int_add.py b/test/formal/checked_int_add.py new file mode 100644 index 000000000..6e74944e2 --- /dev/null +++ b/test/formal/checked_int_add.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer addition. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVAddNoOverflow(X_short, Y_short, True)) + actual_underflow = Not(BVAddNoUnderflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntAddFunction + overflow_check = AND(ISZERO(SLT(X, 0)), SGT(Y, SUB(maxValue, X))) + underflow_check = AND(SLT(X, 0), SLT(Y, SUB(minValue, X))) + + rule.check(actual_overflow, overflow_check != 0) + rule.check(actual_underflow, underflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_int_div.py b/test/formal/checked_int_div.py new file mode 100644 index 000000000..2a36a9488 --- /dev/null +++ b/test/formal/checked_int_div.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer division. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow conditions + actual_overflow = Not(BVSDivNoOverflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntDivFunction + overflow_check = AND(EQ(X, minValue), EQ(Y, SUB(0, 1))) + + rule.check(actual_overflow, overflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_int_mul_16.py b/test/formal/checked_int_mul_16.py new file mode 100644 index 000000000..cc8743b1d --- /dev/null +++ b/test/formal/checked_int_mul_16.py @@ -0,0 +1,42 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer multiplication. +""" + +# Approximation with 16-bit base types. +n_bits = 16 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True)) + actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction + overflow_check_1 = AND(AND(SGT(X, 0), SGT(Y, 0)), GT(X, DIV(maxValue, Y))) + underflow_check_1 = AND(AND(SGT(X, 0), SLT(Y, 0)), SLT(Y, SDIV(minValue, X))) + underflow_check_2 = AND(AND(SLT(X, 0), SGT(Y, 0)), SLT(X, SDIV(minValue, Y))) + overflow_check_2 = AND(AND(SLT(X, 0), SLT(Y, 0)), SLT(X, SDIV(maxValue, Y))) + + rule.check(actual_overflow, Or(overflow_check_1 != 0, overflow_check_2 != 0)) + rule.check(actual_underflow, Or(underflow_check_1 != 0, underflow_check_2 != 0)) + + type_bits *= 2 diff --git a/test/formal/checked_int_sub.py b/test/formal/checked_int_sub.py new file mode 100644 index 000000000..72a0a2109 --- /dev/null +++ b/test/formal/checked_int_sub.py @@ -0,0 +1,39 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked signed integer subtraction. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow and underflow conditions + actual_overflow = Not(BVSubNoOverflow(X_short, Y_short)) + actual_underflow = Not(BVSubNoUnderflow(X_short, Y_short, True)) + + # cast to full n_bits values + X = BVSignedUpCast(X_short, n_bits) + Y = BVSignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVSignedMax(type_bits, n_bits) + minValue = BVSignedMin(type_bits, n_bits) + + # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntSubFunction + underflow_check = AND(ISZERO(SLT(Y, 0)), SLT(X, ADD(minValue, Y))) + overflow_check = AND(SLT(Y, 0), SGT(X, ADD(maxValue, Y))) + + rule.check(actual_underflow, underflow_check != 0) + rule.check(actual_overflow, overflow_check != 0) + + type_bits *= 2 diff --git a/test/formal/checked_uint_add.py b/test/formal/checked_uint_add.py new file mode 100644 index 000000000..596fa04f0 --- /dev/null +++ b/test/formal/checked_uint_add.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer addition. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVAddNoOverflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntAddFunction + overflow_check = GT(X, SUB(maxValue, Y)) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/checked_uint_mul_16.py b/test/formal/checked_uint_mul_16.py new file mode 100644 index 000000000..6e8901799 --- /dev/null +++ b/test/formal/checked_uint_mul_16.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer multiplication. +""" + +# Approximation with 16-bit base types. +n_bits = 16 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntMulFunction + overflow_check = AND(ISZERO(ISZERO(X)), GT(Y, DIV(maxValue, X))) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/checked_uint_sub.py b/test/formal/checked_uint_sub.py new file mode 100644 index 000000000..b0f25b582 --- /dev/null +++ b/test/formal/checked_uint_sub.py @@ -0,0 +1,35 @@ +from rule import Rule +from opcodes import * +from util import * + +""" +Overflow checked unsigned integer subtraction. +""" + +n_bits = 256 +type_bits = 8 + +while type_bits <= n_bits: + + rule = Rule() + + # Input vars + X_short = BitVec('X', type_bits) + Y_short = BitVec('Y', type_bits) + + # Z3's overflow condition + actual_overflow = Not(BVSubNoUnderflow(X_short, Y_short, False)) + + # cast to full n_bits values + X = BVUnsignedUpCast(X_short, n_bits) + Y = BVUnsignedUpCast(Y_short, n_bits) + + # Constants + maxValue = BVUnsignedMax(type_bits, n_bits) + + # Overflow check in YulUtilFunction::overflowCheckedIntSubFunction + overflow_check = LT(X, Y) + + rule.check(overflow_check != 0, actual_overflow) + + type_bits *= 2 diff --git a/test/formal/combine_div_shl_one_32.py b/test/formal/combine_div_shl_one_32.py new file mode 100644 index 000000000..2ee7d2137 --- /dev/null +++ b/test/formal/combine_div_shl_one_32.py @@ -0,0 +1,24 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +DIV(X, SHL(Y, 1)) -> SHR(Y, X) +Requirements: +""" + +rule = Rule() + +n_bits = 32 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Non optimized result +nonopt = DIV(X, SHL(Y, 1)) + +# Optimized result +opt = SHR(Y, X) + +rule.check(nonopt, opt) diff --git a/test/formal/combine_mul_shl_one_64.py b/test/formal/combine_mul_shl_one_64.py new file mode 100644 index 000000000..44d031b98 --- /dev/null +++ b/test/formal/combine_mul_shl_one_64.py @@ -0,0 +1,30 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +MUL(X, SHL(Y, 1)) -> SHL(Y, X) +MUL(SHL(X, 1), Y) -> SHL(X, Y) +Requirements: +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +Y = BitVec('Y', n_bits) + +# Requirements + +# Non optimized result +nonopt_1 = MUL(X, SHL(Y, 1)) +nonopt_2 = MUL(SHL(X, 1), Y) + +# Optimized result +opt_1 = SHL(Y, X) +opt_2 = SHL(X, Y) + +rule.check(nonopt_1, opt_1) +rule.check(nonopt_2, opt_2) diff --git a/test/formal/combine_shl_shr_by_constant_64.py b/test/formal/combine_shl_shr_by_constant_64.py new file mode 100644 index 000000000..fc7ec64e8 --- /dev/null +++ b/test/formal/combine_shl_shr_by_constant_64.py @@ -0,0 +1,44 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d()) +SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) +Requirements: +A < BitWidth +B < BitWidth +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(A, BitWidth)) +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt = SHR(B, SHL(A, X)) + +# Optimized result +Mask = SHR(B, SHL(A, Int2BV(IntVal(-1), n_bits))) +opt = If( + UGT(A, B), + AND(SHL(A - B, X), Mask), + If( + UGT(B, A), + AND(SHR(B - A, X), Mask), + AND(X, Mask) + ) + ) + +rule.check(nonopt, opt) diff --git a/test/formal/combine_shr_shl_by_constant_64.py b/test/formal/combine_shr_shl_by_constant_64.py new file mode 100644 index 000000000..c011a2616 --- /dev/null +++ b/test/formal/combine_shr_shl_by_constant_64.py @@ -0,0 +1,44 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d())) +SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) +Requirements: +A < BitWidth +B < BitWidth +""" + +rule = Rule() + +n_bits = 64 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(A, BitWidth)) +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt = SHL(B, SHR(A, X)) + +# Optimized result +Mask = SHL(B, SHR(A, Int2BV(IntVal(-1), n_bits))) +opt = If( + UGT(A, B), + AND(SHR(A - B, X), Mask), + If( + UGT(B, A), + AND(SHL(B - A, X), Mask), + AND(X, Mask) + ) + ) + +rule.check(nonopt, opt) diff --git a/test/formal/move_and_across_shl_128.py b/test/formal/move_and_across_shl_128.py new file mode 100644 index 000000000..d3f5c3a66 --- /dev/null +++ b/test/formal/move_and_across_shl_128.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SHL(B, AND(X, A)) -> AND(SHL(B, X), A << B) +SHL(B, AND(A, X)) -> AND(SHL(B, X), A << B) +Requirements: +B < BitWidth +""" + +rule = Rule() + +n_bits = 128 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt_1 = SHL(B, AND(X, A)) +nonopt_2 = SHL(B, AND(A, X)) + +# Optimized result +Mask = SHL(B, A) +opt = AND(SHL(B, X), Mask) + +rule.check(nonopt_1, opt) +rule.check(nonopt_2, opt) diff --git a/test/formal/move_and_across_shr_128.py b/test/formal/move_and_across_shr_128.py new file mode 100644 index 000000000..df673ff59 --- /dev/null +++ b/test/formal/move_and_across_shr_128.py @@ -0,0 +1,36 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SHR(B, AND(X, A)) -> AND(SHR(B, X), A >> B) +SHR(B, AND(A, X)) -> AND(SHR(B, X), A >> B) +Requirements: +B < BitWidth +""" + +rule = Rule() + +n_bits = 128 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', n_bits) + +# Constants +BitWidth = BitVecVal(n_bits, n_bits) + +# Requirements +rule.require(ULT(B, BitWidth)) + +# Non optimized result +nonopt_1 = SHR(B, AND(X, A)); +nonopt_2 = SHR(B, AND(A, X)); + +# Optimized result +Mask = SHR(B, A); +opt = AND(SHR(B, X), Mask); + +rule.check(nonopt_1, opt) +rule.check(nonopt_2, opt) diff --git a/test/formal/opcodes.py b/test/formal/opcodes.py new file mode 100644 index 000000000..cdd60ddae --- /dev/null +++ b/test/formal/opcodes.py @@ -0,0 +1,52 @@ +from z3 import * + +def ADD(x, y): + return x + y + +def MUL(x, y): + return x * y + +def SUB(x, y): + return x - y + +def DIV(x, y): + return If(y == 0, 0, UDiv(x, y)) + +def SDIV(x, y): + return If(y == 0, 0, x / y) + +def MOD(x, y): + return If(y == 0, 0, URem(x, y)) + +def SMOD(x, y): + return If(y == 0, 0, x % y) + +def LT(x, y): + return If(ULT(x, y), BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def GT(x, y): + return If(UGT(x, y), BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def SLT(x, y): + return If(x < y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def SGT(x, y): + return If(x > y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def EQ(x, y): + return If(x == y, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def ISZERO(x): + return If(x == 0, BitVecVal(1, x.size()), BitVecVal(0, x.size())) + +def AND(x, y): + return x & y + +def SHL(x, y): + return y << x + +def SHR(x, y): + return LShR(y, x) + +def SAR(x, y): + return y >> x diff --git a/test/formal/rule.py b/test/formal/rule.py new file mode 100644 index 000000000..9327f7e5a --- /dev/null +++ b/test/formal/rule.py @@ -0,0 +1,44 @@ +import sys + +from z3 import * + +class Rule: + def __init__(self): + self.requirements = [] + self.constraints = [] + self.solver = Solver() + self.setTimeout(60000) + + def setTimeout(self, _t): + self.solver.set("timeout", _t) + + def __lshift__(self, _c): + self.constraints.append(_c) + + def require(self, _r): + self.requirements.append(_r) + + def check(self, _nonopt, _opt): + self.solver.add(self.requirements) + result = self.solver.check() + + if result == unknown: + self.error('Unable to satisfy requirements.') + elif result == unsat: + self.error('Requirements are unsatisfiable.') + + self.solver.push() + self.solver.add(self.constraints) + self.solver.add(_nonopt != _opt) + + result = self.solver.check() + if result == unknown: + self.error('Unable to prove rule.') + elif result == sat: + m = self.solver.model() + self.error('Rule is incorrect.\nModel: ' + str(m)) + self.solver.pop() + + def error(self, msg): + print(msg) + sys.exit(1) diff --git a/test/formal/shl_workaround_8.py b/test/formal/shl_workaround_8.py new file mode 100644 index 000000000..2ca711cdf --- /dev/null +++ b/test/formal/shl_workaround_8.py @@ -0,0 +1,27 @@ +from rule import Rule +from opcodes import * + +""" +Shift left workaround that Solidity implements +due to a bug in Boost. +""" + +rule = Rule() + +n_bits = 8 +bigint_bits = 16 + +# Input vars +X = BitVec('X', n_bits) +A = BitVec('A', n_bits) +B = BitVec('B', bigint_bits) + +# Compute workaround +workaround = Int2BV( + BV2Int( + (Int2BV(BV2Int(X), bigint_bits) << Int2BV(BV2Int(A), bigint_bits)) & + Int2BV(BV2Int(Int2BV(IntVal(-1), n_bits)), bigint_bits) + ), n_bits +) + +rule.check(workaround, SHL(A, X)) diff --git a/test/formal/sub_not_zero_x_to_not_x_256.py b/test/formal/sub_not_zero_x_to_not_x_256.py new file mode 100644 index 000000000..eb3301100 --- /dev/null +++ b/test/formal/sub_not_zero_x_to_not_x_256.py @@ -0,0 +1,26 @@ +from rule import Rule +from opcodes import * + +""" +Rule: +SUB(~0, X) -> NOT(X) +Requirements: +""" + +rule = Rule() + +n_bits = 256 + +# Input vars +X = BitVec('X', n_bits) + +# Constants +ZERO = BitVecVal(0, n_bits) + +# Non optimized result +nonopt = SUB(~ZERO, X) + +# Optimized result +opt = NOT(X) + +rule.check(nonopt, opt) diff --git a/test/formal/util.py b/test/formal/util.py new file mode 100644 index 000000000..ec98f9c81 --- /dev/null +++ b/test/formal/util.py @@ -0,0 +1,27 @@ +from z3 import * + +def BVUnsignedUpCast(x, n_bits): + assert(x.size() <= n_bits) + if x.size() < n_bits: + return Concat(BitVecVal(0, n_bits - x.size()), x) + else: + return x + +def BVUnsignedMax(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal((1 << type_bits) - 1, n_bits) + +def BVSignedUpCast(x, n_bits): + assert(x.size() <= n_bits) + if x.size() < n_bits: + return Concat(If(x < 0, BitVecVal(-1, n_bits - x.size()), BitVecVal(0, n_bits - x.size())), x) + else: + return x + +def BVSignedMax(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal((1 << (type_bits - 1)) - 1, n_bits) + +def BVSignedMin(type_bits, n_bits): + assert(type_bits <= n_bits) + return BitVecVal(-(1 << (type_bits - 1)), n_bits) diff --git a/test/libdevcore/Keccak256.cpp b/test/libdevcore/Keccak256.cpp new file mode 100644 index 000000000..b52a458eb --- /dev/null +++ b/test/libdevcore/Keccak256.cpp @@ -0,0 +1,76 @@ +/* + 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 . +*/ +/** + * Unit tests for keccak256. + */ +#include + +#include + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(Keccak256) + +BOOST_AUTO_TEST_CASE(empty) +{ + BOOST_CHECK_EQUAL( + keccak256(bytes()), + FixedHash<32>("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + ); +} + +BOOST_AUTO_TEST_CASE(zeros) +{ + BOOST_CHECK_EQUAL( + keccak256(bytes(1, '\0')), + FixedHash<32>("0xbc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(2, '\0')), + FixedHash<32>("0x54a8c0ab653c15bfb48b47fd011ba2b9617af01cb45cab344acd57c924d56798") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(5, '\0')), + FixedHash<32>("0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec") + ); + BOOST_CHECK_EQUAL( + keccak256(bytes(10, '\0')), + FixedHash<32>("0x6bd2dd6bd408cbee33429358bf24fdc64612fbf8b1b4db604518f40ffd34b607") + ); +} + +BOOST_AUTO_TEST_CASE(strings) +{ + BOOST_CHECK_EQUAL( + keccak256("test"), + FixedHash<32>("0x9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + ); + BOOST_CHECK_EQUAL( + keccak256("longer test string"), + FixedHash<32>("0x47bed17bfbbc08d6b5a0f603eff1b3e932c37c10b865847a7bc73d55b260f32a") + ); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libdevcore/Whiskers.cpp b/test/libdevcore/Whiskers.cpp index 8a785774e..924f2efd0 100644 --- a/test/libdevcore/Whiskers.cpp +++ b/test/libdevcore/Whiskers.cpp @@ -116,11 +116,11 @@ BOOST_AUTO_TEST_CASE(conditional_plus_list) BOOST_AUTO_TEST_CASE(complicated_replacement) { - string templ = "a x \n >."; + string templ = "a x \n >."; string result = Whiskers(templ) ("b", "BE") ("complicated", "COPL") - ("nesPL \n NEST>."); } @@ -180,6 +180,20 @@ BOOST_AUTO_TEST_CASE(parameter_collision) BOOST_CHECK_THROW(m.render(), WhiskersError); } +BOOST_AUTO_TEST_CASE(invalid_param) +{ + string templ = "a "; + Whiskers m(templ); + BOOST_CHECK_THROW(m("b ", "X"), WhiskersError); +} + +BOOST_AUTO_TEST_CASE(invalid_param_rendered) +{ + string templ = "a "; + Whiskers m(templ); + BOOST_CHECK_EQUAL(m.render(), templ); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/liblangutil/CharStream.cpp b/test/liblangutil/CharStream.cpp new file mode 100644 index 000000000..abd3d02db --- /dev/null +++ b/test/liblangutil/CharStream.cpp @@ -0,0 +1,53 @@ +/* + 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 . +*/ +/** + * @author Rocky Bernstein + * @date 2019 + * Unit tests for the CharStream class. + */ + +#include +#include + +#include + +namespace langutil +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(CharStreamtest) + +BOOST_AUTO_TEST_CASE(test_fail) +{ + auto const source = std::make_shared("now is the time for testing", "source"); + + BOOST_CHECK('n' == source->get()); + BOOST_CHECK('n' == source->get()); + BOOST_CHECK('o' == source->advanceAndGet()); + BOOST_CHECK('n' == source->rollback(1)); + BOOST_CHECK('w' == source->setPosition(2)); + BOOST_REQUIRE_THROW( + source->setPosition(200), + ::langutil::InternalCompilerError + ); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} // end namespaces diff --git a/test/libsolidity/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index faebfc3f5..c94e6c745 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -44,12 +44,15 @@ AnalysisFramework::parseAnalyseAndReturnError( string const& _source, bool _reportWarnings, bool _insertVersionPragma, - bool _allowMultipleErrors + bool _allowMultipleErrors, + bool _allowRecoveryErrors ) { compiler().reset(); compiler().setSources({{"", _insertVersionPragma ? "pragma solidity >=0.0;\n" + _source : _source}}); compiler().setEVMVersion(dev::test::Options::get().evmVersion()); + compiler().setParserErrorRecovery(_allowRecoveryErrors); + _allowMultipleErrors = _allowMultipleErrors || _allowRecoveryErrors; if (!compiler().parse()) { BOOST_FAIL("Parsing contract failed in analysis test suite:" + formatErrors()); diff --git a/test/libsolidity/AnalysisFramework.h b/test/libsolidity/AnalysisFramework.h index cb590674f..3fcb9e74a 100644 --- a/test/libsolidity/AnalysisFramework.h +++ b/test/libsolidity/AnalysisFramework.h @@ -50,7 +50,8 @@ protected: std::string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, - bool _allowMultipleErrors = false + bool _allowMultipleErrors = false, + bool _allowRecoveryErrors = false ); virtual ~AnalysisFramework() = default; diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index a08721f17..4cb7b6667 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -42,14 +42,16 @@ protected: std::string const& _source, bool _reportWarnings = false, bool _insertVersionPragma = true, - bool _allowMultipleErrors = false + bool _allowMultipleErrors = false, + bool _allowRecoveryErrors = false ) { return AnalysisFramework::parseAnalyseAndReturnError( "pragma experimental SMTChecker;\n" + _source, _reportWarnings, _insertVersionPragma, - _allowMultipleErrors + _allowMultipleErrors, + _allowRecoveryErrors ); } }; diff --git a/test/libsolidity/SemanticTest.cpp b/test/libsolidity/SemanticTest.cpp index f7d08e67c..adc3e0650 100644 --- a/test/libsolidity/SemanticTest.cpp +++ b/test/libsolidity/SemanticTest.cpp @@ -46,8 +46,18 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang m_source = parseSourceAndSettings(file); if (m_settings.count("compileViaYul")) { - m_validatedSettings["compileViaYul"] = m_settings["compileViaYul"]; - m_compileViaYul = true; + if (m_settings["compileViaYul"] == "also") + { + m_validatedSettings["compileViaYul"] = m_settings["compileViaYul"]; + m_runWithYul = true; + m_runWithoutYul = true; + } + else + { + m_validatedSettings["compileViaYul"] = "only"; + m_runWithYul = true; + m_runWithoutYul = false; + } m_settings.erase("compileViaYul"); } parseExpectations(file); @@ -55,68 +65,84 @@ SemanticTest::SemanticTest(string const& _filename, string const& _ipcPath, lang TestCase::TestResult SemanticTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) { - - - bool success = true; - for (auto& test: m_tests) - test.reset(); - - for (auto& test: m_tests) + for(bool compileViaYul: set{!m_runWithoutYul, m_runWithYul}) { - if (&test == &m_tests.front()) - if (test.call().isConstructor) - deploy("", 0, test.call().arguments.rawBytes()); + bool success = true; + + m_compileViaYul = compileViaYul; + if (compileViaYul) + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Running via Yul:" << endl; + + for (auto& test: m_tests) + test.reset(); + + for (auto& test: m_tests) + { + if (&test == &m_tests.front()) + if (test.call().isConstructor) + deploy("", 0, test.call().arguments.rawBytes()); + else + soltestAssert(deploy("", 0, bytes()), "Failed to deploy contract."); else - soltestAssert(deploy("", 0, bytes()), "Failed to deploy contract."); - else - soltestAssert(!test.call().isConstructor, "Constructor has to be the first function call."); + soltestAssert(!test.call().isConstructor, "Constructor has to be the first function call."); - if (test.call().isConstructor) - { - if (m_transactionSuccessful == test.call().expectations.failure) - success = false; + if (test.call().isConstructor) + { + if (m_transactionSuccessful == test.call().expectations.failure) + success = false; - test.setFailure(!m_transactionSuccessful); - test.setRawBytes(bytes()); + test.setFailure(!m_transactionSuccessful); + test.setRawBytes(bytes()); + } + else + { + bytes output = 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; + + test.setFailure(!m_transactionSuccessful); + test.setRawBytes(std::move(output)); + test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); + } } - else + + if (!success) { - bytes output = 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; - - test.setFailure(!m_transactionSuccessful); - test.setRawBytes(std::move(output)); - test.setContractABI(m_compiler.contractABI(m_compiler.lastContractName())); + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; + for (auto const& test: m_tests) + { + ErrorReporter errorReporter; + _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; + _stream << errorReporter.format(_linePrefix, _formatted); + } + _stream << endl; + AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; + for (auto const& test: m_tests) + { + ErrorReporter errorReporter; + _stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; + _stream << errorReporter.format(_linePrefix, _formatted); + } + AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << endl << _linePrefix + << "Attention: Updates on the test will apply the detected format displayed." << endl; + if (compileViaYul && m_runWithoutYul) + { + _stream << _linePrefix << endl << _linePrefix; + AnsiColorized(_stream, _formatted, {RED_BACKGROUND}) << "Note that the test passed without Yul."; + _stream << endl; + } + else if (!compileViaYul && m_runWithYul) + AnsiColorized(_stream, _formatted, {BOLD, YELLOW}) << _linePrefix << endl << _linePrefix + << "Note that the test also has to pass via Yul." << endl; + return TestResult::Failure; } } - if (!success) - { - AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Expected result:" << endl; - for (auto const& test: m_tests) - { - ErrorReporter errorReporter; - _stream << test.format(errorReporter, _linePrefix, false, _formatted) << endl; - _stream << errorReporter.format(_linePrefix, _formatted); - } - _stream << endl; - AnsiColorized(_stream, _formatted, {BOLD, CYAN}) << _linePrefix << "Obtained result:" << endl; - for (auto const& test: m_tests) - { - ErrorReporter errorReporter; - _stream << test.format(errorReporter, _linePrefix, true, _formatted) << endl; - _stream << errorReporter.format(_linePrefix, _formatted); - } - AnsiColorized(_stream, _formatted, {BOLD, RED}) << _linePrefix << endl << _linePrefix - << "Attention: Updates on the test will apply the detected format displayed." << endl; - return TestResult::Failure; - } return TestResult::Success; } diff --git a/test/libsolidity/SemanticTest.h b/test/libsolidity/SemanticTest.h index 019ca4b9f..ac9fb7dba 100644 --- a/test/libsolidity/SemanticTest.h +++ b/test/libsolidity/SemanticTest.h @@ -65,6 +65,8 @@ public: private: std::string m_source; std::vector m_tests; + bool m_runWithYul = false; + bool m_runWithoutYul = true; }; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7ca924ac1..d1b3a79de 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -64,47 +64,6 @@ int constexpr roundTo32(int _num) return (_num + 31) / 32 * 32; } -BOOST_AUTO_TEST_CASE(transaction_status) -{ - char const* sourceCode = R"( - contract test { - function f() public { } - function g() public { revert(); } - function h() public { assert(false); } - } - )"; - compileAndRun(sourceCode); - callContractFunction("f()"); - BOOST_CHECK(m_transactionSuccessful); - callContractFunction("g()"); - BOOST_CHECK(!m_transactionSuccessful); - callContractFunction("h()"); - BOOST_CHECK(!m_transactionSuccessful); -} - - -BOOST_AUTO_TEST_CASE(smoke_test) -{ - char const* sourceCode = R"( - contract test { - function f(uint a) public returns(uint d) { return a * 7; } - } - )"; - compileAndRun(sourceCode); - testContractAgainstCppOnRange("f(uint256)", [](u256 const& a) -> u256 { return a * 7; }, 0, 100); -} - -BOOST_AUTO_TEST_CASE(empty_contract) -{ - char const* sourceCode = R"( - contract test { } - )"; - ALSO_VIA_YUL( - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("i_am_not_there()", bytes()).empty()); - ) -} - BOOST_AUTO_TEST_CASE(exp_operator) { char const* sourceCode = R"( @@ -123,8 +82,10 @@ BOOST_AUTO_TEST_CASE(exp_operator_const) function f() public returns(uint d) { return 2 ** 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(8))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(8))); + ) } BOOST_AUTO_TEST_CASE(exp_operator_const_signed) @@ -134,8 +95,10 @@ BOOST_AUTO_TEST_CASE(exp_operator_const_signed) function f() public returns(int d) { return (-2) ** 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(-8))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(-8))); + ) } BOOST_AUTO_TEST_CASE(exp_zero) @@ -156,8 +119,10 @@ BOOST_AUTO_TEST_CASE(exp_zero_literal) function f() public returns(uint d) { return 0 ** 0; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(1))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(1))); + ) } @@ -391,11 +356,13 @@ BOOST_AUTO_TEST_CASE(c99_scoping_activation) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(3)); - ABI_CHECK(callContractFunction("g()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4)); - ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(3)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("h()"), encodeArgs(3, 3, 4)); + ABI_CHECK(callContractFunction("i()"), encodeArgs(3, 3)); + ) } BOOST_AUTO_TEST_CASE(recursive_calls) @@ -408,16 +375,18 @@ BOOST_AUTO_TEST_CASE(recursive_calls) } } )"; - compileAndRun(sourceCode); - function recursive_calls_cpp = [&recursive_calls_cpp](u256 const& n) -> u256 - { - if (n <= 1) - return 1; - else - return n * recursive_calls_cpp(n - 1); - }; + ALSO_VIA_YUL( + compileAndRun(sourceCode); + function recursive_calls_cpp = [&recursive_calls_cpp](u256 const& n) -> u256 + { + if (n <= 1) + return 1; + else + return n * recursive_calls_cpp(n - 1); + }; - testContractAgainstCppOnRange("f(uint256)", recursive_calls_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", recursive_calls_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(multiple_functions) @@ -430,12 +399,14 @@ BOOST_AUTO_TEST_CASE(multiple_functions) function f() public returns(uint n) { return 3; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("a()", bytes()), toBigEndian(u256(0))); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(1))); - ABI_CHECK(callContractFunction("c()", bytes()), toBigEndian(u256(2))); - ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(3))); - ABI_CHECK(callContractFunction("i_am_not_there()", bytes()), bytes()); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("a()", bytes()), toBigEndian(u256(0))); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(1))); + ABI_CHECK(callContractFunction("c()", bytes()), toBigEndian(u256(2))); + ABI_CHECK(callContractFunction("f()", bytes()), toBigEndian(u256(3))); + ABI_CHECK(callContractFunction("i_am_not_there()", bytes()), bytes()); + ) } BOOST_AUTO_TEST_CASE(named_args) @@ -446,8 +417,10 @@ BOOST_AUTO_TEST_CASE(named_args) function b() public returns (uint r) { r = a({a: 1, b: 2, c: 3}); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ) } BOOST_AUTO_TEST_CASE(disorder_named_args) @@ -458,8 +431,10 @@ BOOST_AUTO_TEST_CASE(disorder_named_args) function b() public returns (uint r) { r = a({c: 3, a: 1, b: 2}); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("b()", bytes()), toBigEndian(u256(123))); + ) } BOOST_AUTO_TEST_CASE(while_loop) @@ -473,19 +448,21 @@ BOOST_AUTO_TEST_CASE(while_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto while_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i = 2; - while (i <= n) - nfac *= i++; + auto while_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i = 2; + while (i <= n) + nfac *= i++; - return nfac; - }; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", while_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", while_loop_cpp, 0, 5); + ) } @@ -500,22 +477,24 @@ BOOST_AUTO_TEST_CASE(do_while_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto do_while_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i = 2; - do + auto do_while_loop_cpp = [](u256 const& n) -> u256 { - nfac *= i++; - } - while (i <= n); + u256 nfac = 1; + u256 i = 2; + do + { + nfac *= i++; + } + while (i <= n); - return nfac; - }; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", do_while_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", do_while_loop_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(do_while_loop_continue) @@ -534,9 +513,11 @@ BOOST_AUTO_TEST_CASE(do_while_loop_continue) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(42)); + ABI_CHECK(callContractFunction("f()"), encodeArgs(42)); + ) } BOOST_AUTO_TEST_CASE(array_multiple_local_vars) @@ -603,30 +584,32 @@ BOOST_AUTO_TEST_CASE(do_while_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto do_while = [](u256 n) -> u256 - { - u256 i = 0; - do + auto do_while = [](u256 n) -> u256 { - u256 z = n * 2; - if (z < 4) break; - else { - u256 k = z + 1; - if (k < 8) { - n++; - continue; + u256 i = 0; + do + { + u256 z = n * 2; + if (z < 4) break; + else { + u256 k = z + 1; + if (k < 8) { + n++; + continue; + } } - } - if (z > 12) return 0; - n++; - i++; - } while (true); - return 42; - }; + if (z > 12) return 0; + n++; + i++; + } while (true); + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", do_while, 0, 12); + testContractAgainstCppOnRange("f(uint256)", do_while, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_loops) @@ -651,33 +634,35 @@ BOOST_AUTO_TEST_CASE(nested_loops) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto nested_loops_cpp = [](u256 n) -> u256 - { - while (n > 1) + auto nested_loops_cpp = [](u256 n) -> u256 { - if (n == 10) - break; - while (n > 5) + while (n > 1) { - if (n == 8) + if (n == 10) break; + while (n > 5) + { + if (n == 8) + break; + n--; + if (n == 6) + continue; + return n; + } n--; - if (n == 6) + if (n == 3) continue; - return n; + break; } - n--; - if (n == 3) - continue; - break; - } - return n; - }; + return n; + }; - testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_loops_multiple_local_vars) @@ -715,33 +700,35 @@ BOOST_AUTO_TEST_CASE(nested_loops_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto nested_loops_cpp = [](u256 n) -> u256 - { - while (n > 0) + auto nested_loops_cpp = [](u256 n) -> u256 { - u256 z = n + 10; - u256 k = z + 1; - if (k > 20) break; - if (k > 15) { - n--; - continue; - } - while (k > 10) + while (n > 0) { - u256 m = k - 1; - if (m == 10) return n; - return k; + u256 z = n + 10; + u256 k = z + 1; + if (k > 20) break; + if (k > 15) { + n--; + continue; + } + while (k > 10) + { + u256 m = k - 1; + if (m == 10) return n; + return k; + } + n--; + break; } - n--; - break; - } - return n; - }; + return n; + }; - testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + testContractAgainstCppOnRange("f(uint256)", nested_loops_cpp, 0, 12); + ) } BOOST_AUTO_TEST_CASE(for_loop_multiple_local_vars) @@ -767,28 +754,30 @@ BOOST_AUTO_TEST_CASE(for_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop = [](u256 n) -> u256 - { - for (u256 i = 0; i < 12; i++) + auto for_loop = [](u256 n) -> u256 { - u256 z = n + 1; - if (z < 4) break; - else { - u256 k = z * 2; - if (i + k < 10) { - n++; - continue; + for (u256 i = 0; i < 12; i++) + { + u256 z = n + 1; + if (z < 4) break; + else { + u256 k = z * 2; + if (i + k < 10) { + n++; + continue; + } } + if (z > 8) return 0; + n++; } - if (z > 8) return 0; - n++; - } - return 42; - }; + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + ) } BOOST_AUTO_TEST_CASE(nested_for_loop_multiple_local_vars) @@ -826,30 +815,32 @@ BOOST_AUTO_TEST_CASE(nested_for_loop_multiple_local_vars) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop = [](u256 n) -> u256 - { - for (u256 i = 0; i < 5; i++) + auto for_loop = [](u256 n) -> u256 { - u256 z = n + 1; - if (z < 3) break; - for (u256 j = 0; j < 5; j++) + for (u256 i = 0; i < 5; i++) { - u256 k = z * 2; - if (j + k < 8) { + u256 z = n + 1; + if (z < 3) break; + for (u256 j = 0; j < 5; j++) + { + u256 k = z * 2; + if (j + k < 8) { + n++; + continue; + } n++; - continue; + if (n > 20) return 84; } - n++; - if (n > 20) return 84; + if (n > 30) return 42; } - if (n > 30) return 42; - } - return 42; - }; + return 42; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + testContractAgainstCppOnRange("f(uint256)", for_loop, 0, 12); + ) } BOOST_AUTO_TEST_CASE(for_loop) @@ -864,17 +855,19 @@ BOOST_AUTO_TEST_CASE(for_loop) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - for (auto i = 2; i <= n; i++) - nfac *= i; - return nfac; - }; + auto for_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + for (auto i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", for_loop_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(for_loop_empty) @@ -890,20 +883,22 @@ BOOST_AUTO_TEST_CASE(for_loop_empty) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_empty_cpp = []() -> u256 - { - u256 ret = 1; - for (;;) + auto for_loop_empty_cpp = []() -> u256 { - ret += 1; - if (ret >= 10) break; - } - return ret; - }; + u256 ret = 1; + for (;;) + { + ret += 1; + if (ret >= 10) break; + } + return ret; + }; - testContractAgainstCpp("f()", for_loop_empty_cpp); + testContractAgainstCpp("f()", for_loop_empty_cpp); + ) } BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr) @@ -918,18 +913,20 @@ BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256 - { - u256 nfac = 1; - u256 i; - for (i = 2; i <= n; i++) - nfac *= i; - return nfac; - }; + auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i; + for (i = 2; i <= n; i++) + nfac *= i; + return nfac; + }; - testContractAgainstCppOnRange("f(uint256)", for_loop_simple_init_expr_cpp, 0, 5); + testContractAgainstCppOnRange("f(uint256)", for_loop_simple_init_expr_cpp, 0, 5); + ) } BOOST_AUTO_TEST_CASE(for_loop_break_continue) @@ -1039,16 +1036,18 @@ BOOST_AUTO_TEST_CASE(many_local_variables) } } )"; - compileAndRun(sourceCode); - auto f = [](u256 const& x1, u256 const& x2, u256 const& x3) -> u256 - { - u256 a = 0x1; - u256 b = 0x10; - u256 c = 0x100; - u256 y = a + b + c + x1 + x2 + x3; - return y + b + x2; - }; - testContractAgainstCpp("run(uint256,uint256,uint256)", f, u256(0x1000), u256(0x10000), u256(0x100000)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + auto f = [](u256 const& x1, u256 const& x2, u256 const& x3) -> u256 + { + u256 a = 0x1; + u256 b = 0x10; + u256 c = 0x100; + u256 y = a + b + c + x1 + x2 + x3; + return y + b + x2; + }; + testContractAgainstCpp("run(uint256,uint256,uint256)", f, u256(0x1000), u256(0x10000), u256(0x100000)); + ) } BOOST_AUTO_TEST_CASE(packing_unpacking_types) @@ -1079,11 +1078,13 @@ BOOST_AUTO_TEST_CASE(packing_signed_types) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("run()"), - fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa") - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("run()"), + fromHex("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa") + ); + ) } BOOST_AUTO_TEST_CASE(multiple_return_values) @@ -1095,8 +1096,10 @@ BOOST_AUTO_TEST_CASE(multiple_return_values) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("run(bool,uint256)", true, 0xcd), encodeArgs(0xcd, true, 0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("run(bool,uint256)", true, 0xcd), encodeArgs(0xcd, true, 0)); + ) } BOOST_AUTO_TEST_CASE(short_circuiting) @@ -1109,15 +1112,17 @@ BOOST_AUTO_TEST_CASE(short_circuiting) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - auto short_circuiting_cpp = [](u256 n) -> u256 - { - (void)(n == 0 || (n = 8) > 0); - return n; - }; + auto short_circuiting_cpp = [](u256 n) -> u256 + { + (void)(n == 0 || (n = 8) > 0); + return n; + }; - testContractAgainstCppOnRange("run(uint256)", short_circuiting_cpp, 0, 2); + testContractAgainstCppOnRange("run(uint256)", short_circuiting_cpp, 0, 2); + ) } BOOST_AUTO_TEST_CASE(high_bits_cleaning) @@ -1807,8 +1812,10 @@ BOOST_AUTO_TEST_CASE(deleteLocal) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(deleteLocals) @@ -1825,8 +1832,10 @@ BOOST_AUTO_TEST_CASE(deleteLocals) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(6, 7)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("delLocal()"), encodeArgs(6, 7)); + ) } BOOST_AUTO_TEST_CASE(deleteLength) @@ -1842,9 +1851,11 @@ BOOST_AUTO_TEST_CASE(deleteLength) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); - BOOST_CHECK(storageEmpty(m_contractAddress)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + BOOST_CHECK(storageEmpty(m_contractAddress)); + ) } BOOST_AUTO_TEST_CASE(constructor) @@ -1860,15 +1871,19 @@ BOOST_AUTO_TEST_CASE(constructor) } } )"; - compileAndRun(sourceCode); + map data; data[7] = 8; auto get = [&](u256 const& _x) -> u256 { return data[_x]; }; - testContractAgainstCpp("get(uint256)", get, u256(6)); - testContractAgainstCpp("get(uint256)", get, u256(7)); + + ALSO_VIA_YUL( + compileAndRun(sourceCode); + testContractAgainstCpp("get(uint256)", get, u256(6)); + testContractAgainstCpp("get(uint256)", get, u256(7)); + ) } BOOST_AUTO_TEST_CASE(simple_accessor) @@ -2016,8 +2031,10 @@ BOOST_AUTO_TEST_CASE(balance) } } )"; - compileAndRun(sourceCode, 23); - ABI_CHECK(callContractFunction("getBalance()"), encodeArgs(23)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 23); + ABI_CHECK(callContractFunction("getBalance()"), encodeArgs(23)); + ) } BOOST_AUTO_TEST_CASE(blockchain) @@ -2047,8 +2064,10 @@ BOOST_AUTO_TEST_CASE(msg_sig) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ) } BOOST_AUTO_TEST_CASE(msg_sig_after_internal_call_is_same) @@ -2063,8 +2082,10 @@ BOOST_AUTO_TEST_CASE(msg_sig_after_internal_call_is_same) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("foo(uint256)", 0), encodeArgs(asString(FixedHash<4>(dev::keccak256("foo(uint256)")).asBytes()))); + ) } BOOST_AUTO_TEST_CASE(now) @@ -2077,15 +2098,17 @@ BOOST_AUTO_TEST_CASE(now) } } )"; - compileAndRun(sourceCode); - u256 startBlock = m_blockNumber; - size_t startTime = blockTimestamp(startBlock); - auto ret = callContractFunction("someInfo()"); - u256 endBlock = m_blockNumber; - size_t endTime = blockTimestamp(endBlock); - BOOST_CHECK(startBlock != endBlock); - BOOST_CHECK(startTime != endTime); - ABI_CHECK(ret, encodeArgs(true, endTime)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 startBlock = m_blockNumber; + size_t startTime = blockTimestamp(startBlock); + auto ret = callContractFunction("someInfo()"); + u256 endBlock = m_blockNumber; + size_t endTime = blockTimestamp(endBlock); + BOOST_CHECK(startBlock != endBlock); + BOOST_CHECK(startTime != endTime); + ABI_CHECK(ret, encodeArgs(true, endTime)); + ) } BOOST_AUTO_TEST_CASE(type_conversions_cleanup) @@ -2097,10 +2120,12 @@ BOOST_AUTO_TEST_CASE(type_conversions_cleanup) function test() public returns (uint ret) { return uint(address(Test(address(0x11223344556677889900112233445566778899001122)))); } } )"; - compileAndRun(sourceCode); - BOOST_REQUIRE(callContractFunction("test()") == bytes({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, - 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_REQUIRE(callContractFunction("test()") == bytes({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, + 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); + ) } // fixed bytes to fixed bytes conversion tests BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_smaller_size) @@ -2112,8 +2137,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("ab")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("ab")); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_greater_size) @@ -2125,8 +2152,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes2)", "ab"), encodeArgs("ab")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes2)", "ab"), encodeArgs("ab")); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_same_size) @@ -2138,8 +2167,10 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_fixed_bytes_same_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("abcd")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("bytesToBytes(bytes4)", "abcd"), encodeArgs("abcd")); + ) } // fixed bytes to uint conversion tests @@ -2152,11 +2183,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes32)", string("abc2")), - encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes32)", string("abc2")), + encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_min_size) @@ -2168,11 +2201,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_same_min_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes1)", string("a")), - encodeArgs(u256("0x61")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes1)", string("a")), + encodeArgs(u256("0x61")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_smaller_size) @@ -2184,11 +2219,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes4)", string("abcd")), - encodeArgs(u256("0x6364")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes4)", string("abcd")), + encodeArgs(u256("0x6364")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) @@ -2200,11 +2237,13 @@ BOOST_AUTO_TEST_CASE(convert_fixed_bytes_to_uint_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("bytesToUint(bytes4)", string("abcd")), - encodeArgs(u256("0x61626364")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("bytesToUint(bytes4)", string("abcd")), + encodeArgs(u256("0x61626364")) + ); + ) } // uint fixed bytes conversion tests @@ -2217,9 +2256,11 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_size) } } )"; - compileAndRun(sourceCode); - u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); - ABI_CHECK(callContractFunction("uintToBytes(uint256)", a), encodeArgs(a)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); + ABI_CHECK(callContractFunction("uintToBytes(uint256)", a), encodeArgs(a)); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_min_size) @@ -2231,11 +2272,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_same_min_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("UintToBytes(uint8)", u256("0x61")), - encodeArgs(string("a")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("UintToBytes(uint8)", u256("0x61")), + encodeArgs(string("a")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_smaller_size) @@ -2247,11 +2290,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_smaller_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("uintToBytes(uint32)", u160("0x61626364")), - encodeArgs(string("cd")) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("uintToBytes(uint32)", u160("0x61626364")), + encodeArgs(string("cd")) + ); + ) } BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) @@ -2263,11 +2308,13 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) } } )"; - compileAndRun(sourceCode); - ABI_CHECK( - callContractFunction("UintToBytes(uint16)", u256("0x6162")), - encodeArgs(string("\0\0\0\0\0\0ab", 8)) - ); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK( + callContractFunction("UintToBytes(uint16)", u256("0x6162")), + encodeArgs(string("\0\0\0\0\0\0ab", 8)) + ); + ) } BOOST_AUTO_TEST_CASE(send_ether) @@ -2348,8 +2395,10 @@ BOOST_AUTO_TEST_CASE(blockhash_shadow_resolution) function f() public returns(bytes32) { return blockhash(3); } } )"; - compileAndRun(code, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(code, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(log0) @@ -2972,8 +3021,10 @@ BOOST_AUTO_TEST_CASE(functions_called_by_constructor) function setName(bytes3 _name) private { name = _name; } } )"; - compileAndRun(sourceCode); - BOOST_REQUIRE(callContractFunction("getName()") == encodeArgs("abc")); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_REQUIRE(callContractFunction("getName()") == encodeArgs("abc")); + ) } BOOST_AUTO_TEST_CASE(contracts_as_addresses) @@ -3062,9 +3113,11 @@ BOOST_AUTO_TEST_CASE(gaslimit) } } )"; - compileAndRun(sourceCode); - auto result = callContractFunction("f()"); - ABI_CHECK(result, encodeArgs(gasLimit())); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + auto result = callContractFunction("f()"); + ABI_CHECK(result, encodeArgs(gasLimit())); + ) } BOOST_AUTO_TEST_CASE(gasprice) @@ -3076,8 +3129,10 @@ BOOST_AUTO_TEST_CASE(gasprice) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(gasPrice())); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(gasPrice())); + ) } BOOST_AUTO_TEST_CASE(blockhash) @@ -3192,9 +3247,11 @@ BOOST_AUTO_TEST_CASE(virtual_function_calls) function g() public returns (uint i) { return 2; } } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); - ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(access_base_storage) @@ -3217,10 +3274,12 @@ BOOST_AUTO_TEST_CASE(access_base_storage) } } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("setData(uint256,uint256)", 1, 2), encodeArgs(true)); - ABI_CHECK(callContractFunction("getViaBase()"), encodeArgs(1)); - ABI_CHECK(callContractFunction("getViaDerived()"), encodeArgs(1, 2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("setData(uint256,uint256)", 1, 2), encodeArgs(true)); + ABI_CHECK(callContractFunction("getViaBase()"), encodeArgs(1)); + ABI_CHECK(callContractFunction("getViaDerived()"), encodeArgs(1, 2)); + ) } BOOST_AUTO_TEST_CASE(single_copy_with_multiple_inheritance) @@ -3235,10 +3294,12 @@ BOOST_AUTO_TEST_CASE(single_copy_with_multiple_inheritance) contract B is Base { function getViaB() public returns (uint i) { return getViaBase(); } } contract Derived is Base, B, A { } )"; - compileAndRun(sourceCode, 0, "Derived"); - ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("setViaA(uint256)", 23), encodeArgs()); - ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(23)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Derived"); + ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("setViaA(uint256)", 23), encodeArgs()); + ABI_CHECK(callContractFunction("getViaB()"), encodeArgs(23)); + ) } BOOST_AUTO_TEST_CASE(explicit_base_class) @@ -3543,8 +3604,10 @@ BOOST_AUTO_TEST_CASE(crazy_elementary_typenames_on_stack) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(-7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(-7))); + ) } BOOST_AUTO_TEST_CASE(super) @@ -3576,8 +3639,10 @@ BOOST_AUTO_TEST_CASE(super_alone) char const* sourceCode = R"( contract A { function f() public { super; } } )"; - compileAndRun(sourceCode, 0, "A"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "A"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(fallback_function) @@ -3589,10 +3654,12 @@ BOOST_AUTO_TEST_CASE(fallback_function) function getData() public returns (uint r) { return data; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); - ABI_CHECK(callContractFunction(""), encodeArgs()); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + 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) @@ -3605,10 +3672,12 @@ BOOST_AUTO_TEST_CASE(inherited_fallback_function) } contract B is A {} )"; - compileAndRun(sourceCode, 0, "B"); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); - ABI_CHECK(callContractFunction(""), encodeArgs()); - ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "B"); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(0)); + ABI_CHECK(callContractFunction(""), encodeArgs()); + ABI_CHECK(callContractFunction("getData()"), encodeArgs(1)); + ) } BOOST_AUTO_TEST_CASE(default_fallback_throws) @@ -3703,7 +3772,7 @@ BOOST_AUTO_TEST_CASE(event_emit) } )"; ALSO_VIA_YUL( - compileAndRun(sourceCode); + compileAndRun(sourceCode); u256 value(18); u256 id(0x1234); callContractFunctionWithValue("deposit(bytes32)", value, id); @@ -3787,9 +3856,9 @@ BOOST_AUTO_TEST_CASE(events_with_same_name) } } )"; - ALSO_VIA_YUL( - u160 const c_loggedAddress = m_contractAddress; + u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -3848,9 +3917,9 @@ BOOST_AUTO_TEST_CASE(events_with_same_name_inherited_emit) } } )"; - ALSO_VIA_YUL( - u160 const c_loggedAddress = m_contractAddress; + u160 const c_loggedAddress = m_contractAddress; + ALSO_VIA_YUL( compileAndRun(sourceCode); ABI_CHECK(callContractFunction("deposit()"), encodeArgs(u256(1))); BOOST_REQUIRE_EQUAL(m_logs.size(), 1); @@ -4147,14 +4216,16 @@ BOOST_AUTO_TEST_CASE(event_dynamic_array_storage) } } )"; - compileAndRun(sourceCode); - u256 x(42); - callContractFunction("createEvent(uint256)", x); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 x(42); + callContractFunction("createEvent(uint256)", x); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ) } BOOST_AUTO_TEST_CASE(event_dynamic_array_storage_v2) @@ -4173,14 +4244,16 @@ BOOST_AUTO_TEST_CASE(event_dynamic_array_storage_v2) } } )"; - compileAndRun(sourceCode); - u256 x(42); - callContractFunction("createEvent(uint256)", x); - BOOST_REQUIRE_EQUAL(m_logs.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); - BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); - BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); - BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + u256 x(42); + callContractFunction("createEvent(uint256)", x); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(0x20, 3, x, x + 1, x + 2)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("E(uint256[])"))); + ) } BOOST_AUTO_TEST_CASE(event_dynamic_nested_array_storage_v2) @@ -4258,9 +4331,11 @@ BOOST_AUTO_TEST_CASE(empty_name_input_parameter_with_named_one) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("f(uint256,uint256)", 5, 9) != encodeArgs(5, 8)); - ABI_CHECK(callContractFunction("f(uint256,uint256)", 5, 9), encodeArgs(9, 8)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(uint256,uint256)", 5, 9) != encodeArgs(5, 8)); + ABI_CHECK(callContractFunction("f(uint256,uint256)", 5, 9), encodeArgs(9, 8)); + ) } BOOST_AUTO_TEST_CASE(empty_name_return_parameter) @@ -4272,8 +4347,10 @@ BOOST_AUTO_TEST_CASE(empty_name_return_parameter) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f(uint256)", 9), encodeArgs(9)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f(uint256)", 9), encodeArgs(9)); + ) } BOOST_AUTO_TEST_CASE(sha256_empty) @@ -4988,14 +5065,16 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow) ActionChoices choice; } )"; - compileAndRun(sourceCode); - // These should throw - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 3), encodeArgs()); - ABI_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1), encodeArgs()); - ABI_CHECK(callContractFunction("getChoiceFromNegativeLiteral()"), encodeArgs()); - // These should work - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 2), encodeArgs(2)); - ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 0), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + // These should throw + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 3), encodeArgs()); + ABI_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1), encodeArgs()); + ABI_CHECK(callContractFunction("getChoiceFromNegativeLiteral()"), encodeArgs()); + // These should work + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 2), encodeArgs(2)); + ABI_CHECK(callContractFunction("getChoiceExp(uint256)", 0), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(storing_invalid_boolean) @@ -5104,8 +5183,10 @@ BOOST_AUTO_TEST_CASE(constructing_enums_from_ints) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("test()"), encodeArgs(1)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test()"), encodeArgs(1)); + ) } BOOST_AUTO_TEST_CASE(struct_referencing) @@ -5235,7 +5316,6 @@ BOOST_AUTO_TEST_CASE(inline_member_init) compileAndRun(sourceCode); ABI_CHECK(callContractFunction("get()"), encodeArgs(5, 6, 8)); } - BOOST_AUTO_TEST_CASE(inline_member_init_inheritence) { char const* sourceCode = R"( @@ -5283,8 +5363,10 @@ BOOST_AUTO_TEST_CASE(external_function) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("test(uint256,uint256)", 2, 3), encodeArgs(2+7, 3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("test(uint256,uint256)", 2, 3), encodeArgs(2+7, 3)); + ) } BOOST_AUTO_TEST_CASE(bytes_in_arguments) @@ -5385,15 +5467,17 @@ BOOST_AUTO_TEST_CASE(fixed_out_of_bounds_array_access) function length() public returns (uint) { return data.length; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 5), bytes()); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 400, 5), bytes()); - ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); - ABI_CHECK(callContractFunction("get(uint256)", 4), bytes()); - ABI_CHECK(callContractFunction("get(uint256)", 400), bytes()); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 5), bytes()); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 400, 5), bytes()); + ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); + ABI_CHECK(callContractFunction("get(uint256)", 4), bytes()); + ABI_CHECK(callContractFunction("get(uint256)", 400), bytes()); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ) } BOOST_AUTO_TEST_CASE(dynamic_out_of_bounds_array_access) @@ -5407,16 +5491,18 @@ BOOST_AUTO_TEST_CASE(dynamic_out_of_bounds_array_access) function length() public returns (uint) { return data.length; } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("length()"), encodeArgs(0)); - ABI_CHECK(callContractFunction("get(uint256)", 3), bytes()); - ABI_CHECK(callContractFunction("enlarge(uint256)", 4), encodeArgs(4)); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); - ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); - ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 8), bytes()); - ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("length()"), encodeArgs(0)); + ABI_CHECK(callContractFunction("get(uint256)", 3), bytes()); + ABI_CHECK(callContractFunction("enlarge(uint256)", 4), encodeArgs(4)); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 3, 4), encodeArgs(true)); + ABI_CHECK(callContractFunction("get(uint256)", 3), encodeArgs(4)); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ABI_CHECK(callContractFunction("set(uint256,uint256)", 4, 8), bytes()); + ABI_CHECK(callContractFunction("length()"), encodeArgs(4)); + ) } BOOST_AUTO_TEST_CASE(fixed_array_cleanup) @@ -6527,7 +6613,9 @@ BOOST_AUTO_TEST_CASE(constant_variables) bytes32 constant st = "abc\x00\xff__"; } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ) } BOOST_AUTO_TEST_CASE(assignment_to_const_var_involving_expression) @@ -6714,8 +6802,10 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_first) function g() public returns(uint d) { return f(3); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g()"), encodeArgs(3)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g()"), encodeArgs(3)); + ) } BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_second) @@ -6727,8 +6817,10 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_resolve_to_second) function g() public returns(uint d) { return f(3, 7); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ) } BOOST_AUTO_TEST_CASE(overloaded_function_call_with_if_else) @@ -6745,9 +6837,11 @@ BOOST_AUTO_TEST_CASE(overloaded_function_call_with_if_else) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(3)); - ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs(10)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(3)); + ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs(10)); + ) } BOOST_AUTO_TEST_CASE(derived_overload_base_function_direct) @@ -6759,8 +6853,10 @@ BOOST_AUTO_TEST_CASE(derived_overload_base_function_direct) function g() public returns(uint) { return f(1); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(derived_overload_base_function_indirect) @@ -6773,9 +6869,11 @@ BOOST_AUTO_TEST_CASE(derived_overload_base_function_indirect) function h() public returns(uint) { return f(1); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); - ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("g()"), encodeArgs(10)); + ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(super_overload) @@ -6801,8 +6899,10 @@ BOOST_AUTO_TEST_CASE(gasleft_shadow_resolution) function f() public returns(uint256) { return gasleft(); } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(0)); + ) } BOOST_AUTO_TEST_CASE(bool_conversion) @@ -6999,11 +7099,13 @@ BOOST_AUTO_TEST_CASE(invalid_enum_as_external_ret) } } )"; - compileAndRun(sourceCode, 0, "C"); - // both should throw - ABI_CHECK(callContractFunction("test_return()"), encodeArgs()); - ABI_CHECK(callContractFunction("test_inline_assignment()"), encodeArgs()); - ABI_CHECK(callContractFunction("test_assignment()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + // both should throw + ABI_CHECK(callContractFunction("test_return()"), encodeArgs()); + ABI_CHECK(callContractFunction("test_inline_assignment()"), encodeArgs()); + ABI_CHECK(callContractFunction("test_assignment()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(invalid_enum_as_external_arg) @@ -8779,28 +8881,32 @@ BOOST_AUTO_TEST_CASE(string_as_mapping_key) function get(string memory _s) public returns (uint) { return data[_s]; } } )"; - compileAndRun(sourceCode, 0, "Test"); + vector strings{ "Hello, World!", "Hello, World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1111", "", "1" }; - for (unsigned i = 0; i < strings.size(); i++) - ABI_CHECK(callContractFunction( - "set(string,uint256)", - u256(0x40), - u256(7 + i), - u256(strings[i].size()), - strings[i] - ), encodeArgs()); - for (unsigned i = 0; i < strings.size(); i++) - ABI_CHECK(callContractFunction( - "get(string)", - u256(0x20), - u256(strings[i].size()), - strings[i] - ), encodeArgs(u256(7 + i))); + + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "Test"); + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "set(string,uint256)", + u256(0x40), + u256(7 + i), + u256(strings[i].size()), + strings[i] + ), encodeArgs()); + for (unsigned i = 0; i < strings.size(); i++) + ABI_CHECK(callContractFunction( + "get(string)", + u256(0x20), + u256(strings[i].size()), + strings[i] + ), encodeArgs(u256(7 + i))); + ) } BOOST_AUTO_TEST_CASE(string_as_public_mapping_key) @@ -10036,8 +10142,10 @@ BOOST_AUTO_TEST_CASE(lone_struct_array_type) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(3))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(3))); + ) } BOOST_AUTO_TEST_CASE(create_memory_array) @@ -10575,9 +10683,11 @@ BOOST_AUTO_TEST_CASE(byte_optimization_bug) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("g(uint256)", u256(2)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("g(uint256)", u256(2)), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) @@ -10589,8 +10699,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7), string("abcdef"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(7), string("abcdef"))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) @@ -10603,8 +10715,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(45))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(45))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_memory_access) @@ -10724,8 +10838,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment) @@ -10749,8 +10865,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call_assignment) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_function_call2) @@ -10775,8 +10893,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_function_call2) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7), u256(0x10))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(2), u256(7), u256(0x10))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) @@ -10802,8 +10922,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_if) @@ -10817,11 +10939,13 @@ BOOST_AUTO_TEST_CASE(inline_assembly_if) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_switch) @@ -10838,11 +10962,13 @@ BOOST_AUTO_TEST_CASE(inline_assembly_switch) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(8))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(9))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(8))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(9))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_recursion) @@ -10862,12 +10988,14 @@ BOOST_AUTO_TEST_CASE(inline_assembly_recursion) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); - ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); + ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_for) @@ -10887,12 +11015,14 @@ BOOST_AUTO_TEST_CASE(inline_assembly_for) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); - ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(6))); + ABI_CHECK(callContractFunction("f(uint256)", u256(4)), encodeArgs(u256(24))); + ) } BOOST_AUTO_TEST_CASE(inline_assembly_for2) @@ -10913,10 +11043,12 @@ BOOST_AUTO_TEST_CASE(inline_assembly_for2) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0), u256(2), u256(0))); - ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1), u256(4), u256(3))); - ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0), u256(2), u256(0))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0), u256(2), u256(0))); + ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(1), u256(4), u256(3))); + ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(0), u256(2), u256(0))); + ) } BOOST_AUTO_TEST_CASE(index_access_with_type_conversion) @@ -11098,7 +11230,6 @@ BOOST_AUTO_TEST_CASE(cleanup_bytes_types) bool v2 = dev::test::Options::get().useABIEncoderV2; ABI_CHECK(callContractFunction("f(bytes2,uint16)", string("abc"), u256(0x040102)), v2 ? encodeArgs() : encodeArgs(0)); } - BOOST_AUTO_TEST_CASE(cleanup_bytes_types_shortening) { char const* sourceCode = R"( @@ -11116,7 +11247,6 @@ BOOST_AUTO_TEST_CASE(cleanup_bytes_types_shortening) compileAndRun(sourceCode, 0, "C"); ABI_CHECK(callContractFunction("f()"), encodeArgs("\xff\xff\xff\xff")); } - BOOST_AUTO_TEST_CASE(cleanup_address_types) { // Checks that address types are properly cleaned before they are compared. @@ -11162,9 +11292,11 @@ BOOST_AUTO_TEST_CASE(cleanup_address_types_shortening) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); - ABI_CHECK(callContractFunction("g()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ABI_CHECK(callContractFunction("g()"), encodeArgs(u256("0x1122334455667788990011223344556677889900"))); + ) } BOOST_AUTO_TEST_CASE(skip_dynamic_types) @@ -11502,7 +11634,9 @@ BOOST_AUTO_TEST_CASE(payable_constructor) constructor() public payable { } } )"; - compileAndRun(sourceCode, 27, "C"); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 27, "C"); + ) } BOOST_AUTO_TEST_CASE(payable_function) @@ -11591,9 +11725,11 @@ BOOST_AUTO_TEST_CASE(no_nonpayable_circumvention_by_modifier) } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunctionWithValue("f()", 27), encodeArgs()); - BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 0); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunctionWithValue("f()", 27), encodeArgs()); + BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 0); + ) } BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) @@ -11945,8 +12081,10 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) } )"; - compileAndRun(sourceCode, 0, "test"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(2))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "test"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(2))); + ) } BOOST_AUTO_TEST_CASE(mapping_of_functions) @@ -12108,8 +12246,10 @@ BOOST_AUTO_TEST_CASE(function_delete_stack) } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("test()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(copy_function_storage_array) @@ -12565,19 +12705,21 @@ BOOST_AUTO_TEST_CASE(shift_right_negative_literal) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f1()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f2()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f3()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f4()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f5()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("f6()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g1()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g2()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g3()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g4()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g5()"), encodeArgs(true)); - ABI_CHECK(callContractFunction("g6()"), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f1()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f2()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f3()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f4()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f5()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("f6()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g1()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g2()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g3()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g4()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g5()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("g6()"), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(shift_right_negative_lvalue_int8) @@ -12964,8 +13106,10 @@ BOOST_AUTO_TEST_CASE(inline_assembly_in_modifiers) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(packed_storage_overflow) @@ -12996,8 +13140,10 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment) **/ contract C2 {} )"; - compileAndRun(sourceCode, 0, "C1"); - compileAndRun(sourceCode, 0, "C2"); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C1"); + compileAndRun(sourceCode, 0, "C2"); + ) } @@ -13063,8 +13209,10 @@ BOOST_AUTO_TEST_CASE(invalid_instruction) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ) } BOOST_AUTO_TEST_CASE(assert_require) @@ -13084,12 +13232,14 @@ BOOST_AUTO_TEST_CASE(assert_require) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs()); - ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs()); - ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(true)); - ABI_CHECK(callContractFunction("h(bool)", false), encodeArgs()); - ABI_CHECK(callContractFunction("h(bool)", true), encodeArgs(true)); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs()); + ABI_CHECK(callContractFunction("g(bool)", false), encodeArgs()); + ABI_CHECK(callContractFunction("g(bool)", true), encodeArgs(true)); + ABI_CHECK(callContractFunction("h(bool)", false), encodeArgs()); + ABI_CHECK(callContractFunction("h(bool)", true), encodeArgs(true)); + ) } BOOST_AUTO_TEST_CASE(revert) @@ -13442,13 +13592,15 @@ BOOST_AUTO_TEST_CASE(scientific_notation) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(20000000000))); - ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(2))); - ABI_CHECK(callContractFunction("h()"), encodeArgs(u256(25))); - ABI_CHECK(callContractFunction("i()"), encodeArgs(u256(-20000000000))); - ABI_CHECK(callContractFunction("j()"), encodeArgs(u256(-2))); - ABI_CHECK(callContractFunction("k()"), encodeArgs(u256(-25))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(20000000000))); + ABI_CHECK(callContractFunction("g()"), encodeArgs(u256(2))); + ABI_CHECK(callContractFunction("h()"), encodeArgs(u256(25))); + ABI_CHECK(callContractFunction("i()"), encodeArgs(u256(-20000000000))); + ABI_CHECK(callContractFunction("j()"), encodeArgs(u256(-2))); + ABI_CHECK(callContractFunction("k()"), encodeArgs(u256(-25))); + ) } BOOST_AUTO_TEST_CASE(interface_contract) @@ -13497,8 +13649,10 @@ BOOST_AUTO_TEST_CASE(keccak256_assembly) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), fromHex("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")); + ) } BOOST_AUTO_TEST_CASE(multi_modifiers) @@ -13542,8 +13696,10 @@ BOOST_AUTO_TEST_CASE(inlineasm_empty_let) } } )"; - compileAndRun(sourceCode, 0, "C"); - ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0), u256(0))); + ALSO_VIA_YUL( + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(0), u256(0))); + ) } BOOST_AUTO_TEST_CASE(bare_call_invalid_address) @@ -14968,18 +15124,20 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(4))); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"))); - BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256(1), u256(2)) == encodeArgs(u256(4))); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"))); + BOOST_CHECK(callContractFunction("shl(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256(3), u256(1)) == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(1)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(255)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar(uint256,uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), u256(256)) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ) } BOOST_AUTO_TEST_CASE(bitwise_shifting_constants_constantinople) @@ -15038,13 +15196,15 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constants_constantinople) } } )"; - compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl_1()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shl_2()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shl_3()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_1()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_2()") == encodeArgs(u256(1))); - BOOST_CHECK(callContractFunction("shr_3()") == encodeArgs(u256(1))); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("shl_1()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shl_2()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shl_3()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_1()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_2()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("shr_3()") == encodeArgs(u256(1))); + ) } BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) @@ -15128,44 +15288,46 @@ BOOST_AUTO_TEST_CASE(bitwise_shifting_constantinople_combined) } } )"; - compileAndRun(sourceCode); + ALSO_VIA_YUL( + compileAndRun(sourceCode); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff"))); - BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff"))); + BOOST_CHECK(callContractFunction("shl_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_zero(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff0000"))); - BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000"))); - BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x00007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffff")) == encodeArgs(u256("0xffff0000"))); + BOOST_CHECK(callContractFunction("shl_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000"))); + BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0x00007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("sar_combined(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shl_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("shr_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); - BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shl_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("shr_combined_overflow(uint256)", u256(2)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256(0)) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256(0))); + BOOST_CHECK(callContractFunction("sar_combined_large(uint256)", u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) == encodeArgs(u256("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"))); + ) } BOOST_AUTO_TEST_CASE(senders_balance) @@ -15452,7 +15614,6 @@ BOOST_AUTO_TEST_CASE(flipping_sign_tests) compileAndRun(sourceCode); ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); } - BOOST_AUTO_TEST_CASE(external_public_override) { char const* sourceCode = R"( @@ -15464,9 +15625,11 @@ BOOST_AUTO_TEST_CASE(external_public_override) function g() public returns (uint) { return f(); } } )"; - compileAndRun(sourceCode); - ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); - ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ALSO_VIA_YUL( + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(2)); + ABI_CHECK(callContractFunction("g()"), encodeArgs(2)); + ) } BOOST_AUTO_TEST_CASE(base_access_to_function_type_variables) diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index 067aa8883..c6dd11b6d 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -209,7 +209,6 @@ BOOST_AUTO_TEST_CASE(unexpected_trailing_test) BOOST_CHECK(containsError(result, "JSONError", "* Line 10, Column 2\n Extra non-whitespace after JSON value.\n")); } - BOOST_AUTO_TEST_CASE(smoke_test) { char const* input = R"( @@ -226,6 +225,43 @@ BOOST_AUTO_TEST_CASE(smoke_test) BOOST_CHECK(containsAtMostWarnings(result)); } +BOOST_AUTO_TEST_CASE(error_recovery_field) +{ + auto input = R"( + { + "language": "Solidity", + "settings": { + "parserErrorRecovery": "1" + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + + Json::Value result = compile(input); + BOOST_CHECK(containsError(result, "JSONError", "\"settings.parserErrorRecovery\" must be a Boolean.")); + + input = R"( + { + "language": "Solidity", + "settings": { + "parserErrorRecovery": true + }, + "sources": { + "empty": { + "content": "" + } + } + } + )"; + + result = compile(input); + BOOST_CHECK(containsAtMostWarnings(result)); +} + BOOST_AUTO_TEST_CASE(optimizer_enabled_not_boolean) { char const* input = R"( diff --git a/test/libsolidity/SyntaxTest.cpp b/test/libsolidity/SyntaxTest.cpp index 61d1d20e6..45ca1e81d 100644 --- a/test/libsolidity/SyntaxTest.cpp +++ b/test/libsolidity/SyntaxTest.cpp @@ -52,7 +52,7 @@ int parseUnsignedInteger(string::iterator& _it, string::iterator _end) } -SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) +SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery): m_evmVersion(_evmVersion) { ifstream file(_filename); if (!file) @@ -67,6 +67,7 @@ SyntaxTest::SyntaxTest(string const& _filename, langutil::EVMVersion _evmVersion m_settings.erase("optimize-yul"); } m_expectations = parseExpectations(file); + m_parserErrorRecovery = _parserErrorRecovery; } TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix, bool _formatted) @@ -75,6 +76,7 @@ TestCase::TestResult SyntaxTest::run(ostream& _stream, string const& _linePrefix compiler().reset(); compiler().setSources({{"", versionPragma + m_source}}); compiler().setEVMVersion(m_evmVersion); + compiler().setParserErrorRecovery(m_parserErrorRecovery); compiler().setOptimiserSettings( m_optimiseYul ? OptimiserSettings::full() : diff --git a/test/libsolidity/SyntaxTest.h b/test/libsolidity/SyntaxTest.h index 37a078107..62a85d864 100644 --- a/test/libsolidity/SyntaxTest.h +++ b/test/libsolidity/SyntaxTest.h @@ -54,8 +54,14 @@ class SyntaxTest: AnalysisFramework, public EVMVersionRestrictedTestCase { public: static std::unique_ptr create(Config const& _config) - { return std::make_unique(_config.filename, _config.evmVersion); } - SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion); + { + return std::make_unique(_config.filename, _config.evmVersion, false); + } + static std::unique_ptr createErrorRecovery(Config const& _config) + { + return std::make_unique(_config.filename, _config.evmVersion, true); + } + SyntaxTest(std::string const& _filename, langutil::EVMVersion _evmVersion, bool _parserErrorRecovery = false); TestResult run(std::ostream& _stream, std::string const& _linePrefix = "", bool _formatted = false) override; @@ -84,6 +90,7 @@ protected: std::vector m_errorList; bool m_optimiseYul = false; langutil::EVMVersion const m_evmVersion; + bool m_parserErrorRecovery = false; }; } diff --git a/test/libsolidity/errorRecoveryTests/constructor_recovers.sol b/test/libsolidity/errorRecoveryTests/constructor_recovers.sol new file mode 100644 index 000000000..804bb7283 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/constructor_recovers.sol @@ -0,0 +1,20 @@ +pragma solidity >=0.0.0; + +contract Error1 { + constructor() public { + balances[tx.origin] = ; // missing RHS. + } + + // Without error recovery we stop due to the above error. + // Error recovery however recovers at the above ';' + // There should be an AST for the above, albeit with error + // nodes. + + // This function parses properly and should give AST info. + function five() public view returns(uint) { + return 5; + } +} +// ---- +// ParserError: (95-96): Expected primary expression. +// Warning: (95-96): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/contract_recovery.sol b/test/libsolidity/errorRecoveryTests/contract_recovery.sol new file mode 100644 index 000000000..4047adb31 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/contract_recovery.sol @@ -0,0 +1,7 @@ +contract Errort6 { + using foo for ; // missing type name +} + +// ---- +// ParserError: (36-37): Expected type name +// Warning: (59-60): Recovered in ContractDefinition at '}'. diff --git a/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol b/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol new file mode 100644 index 000000000..a7e4254b0 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/do_not_delete_at_error.sol @@ -0,0 +1,13 @@ +pragma solidity >=0.0.0; + +// Example to show why deleting the token at the +// is bad when error recovery is in effect. Here, ")" is missing +// and there is a ";" instead. That causes us to +// not be able to synchronize to ';'. Advance again and +// '}' is deleted and then we can't synchronize the contract. +// There should be an an AST created this contract (with errors). +contract Error2 { + mapping (address => uint balances; // missing ) before "balances" +} +// ---- +// ParserError: (417-425): Expected ')' but got identifier diff --git a/test/libsolidity/errorRecoveryTests/error_to_eos.sol b/test/libsolidity/errorRecoveryTests/error_to_eos.sol new file mode 100644 index 000000000..d396207b0 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/error_to_eos.sol @@ -0,0 +1,23 @@ +// Example which where scanning hits EOS, so we reset. +// Here we recover in the contractDefinition. +// There should be an an AST created this contract (with errors). +contract Error2 { + mapping (address => uint balances) // missing ; +} + +// There is no error in this contract +contract SendCoin { + function sendCoin(address receiver, uint amount) public returns(bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + emit Transfer(msg.sender, receiver, amount); + return true; + } +} + +// ---- +// ParserError: (212-220): Expected ')' but got identifier +// ParserError: (220-221): Expected ';' but got ')' +// ParserError: (220-221): Function, variable, struct or modifier declaration expected. +// Warning: (235-236): Recovered in ContractDefinition at '}'. diff --git a/test/libsolidity/errorRecoveryTests/missing_rhs.sol b/test/libsolidity/errorRecoveryTests/missing_rhs.sol new file mode 100644 index 000000000..991525b46 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/missing_rhs.sol @@ -0,0 +1,11 @@ +pragma solidity >=0.0.0; + +contract Error3 { + constructor() public { + balances[tx.origin] = ; // missing RHS. + } + +} +// ---- +// ParserError: (95-96): Expected primary expression. +// Warning: (95-96): Recovered in Statement at ';'. diff --git a/test/libsolidity/errorRecoveryTests/multiple_errors.sol b/test/libsolidity/errorRecoveryTests/multiple_errors.sol new file mode 100644 index 000000000..9127ef387 --- /dev/null +++ b/test/libsolidity/errorRecoveryTests/multiple_errors.sol @@ -0,0 +1,29 @@ +// An example with multiple errors. +// Most are caught by inserting an expected token. +// However some us S C Johnson recovery to +// skip over tokens. + +pragma solidity >=0.0.0; + +contract Error4 { + constructor() public { + balances[tx.origin] = 1 2; // missing operator + } + + function sendCoin(address receiver, uint amount) public returns(bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount // Missing ";" + balances[receiver] += amount // Another missing ";" + emit Transfer(msg.sender // truncated line + return true; + } + + +} +// ---- +// ParserError: (249-250): Expected ';' but got 'Number' +// ParserError: (471-479): Expected ';' but got identifier +// ParserError: (529-533): Expected ';' but got 'emit' +// ParserError: (577-583): Expected ',' but got 'return' +// ParserError: (577-583): Expected primary expression. +// Warning: (588-589): Recovered in Statement at ';'. diff --git a/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol new file mode 100644 index 000000000..a046a4376 --- /dev/null +++ b/test/libsolidity/semanticTests/abiEncoderV2/storage_array_encoding.sol @@ -0,0 +1,21 @@ +pragma experimental ABIEncoderV2; + +// tests encoding from storage arrays + +contract C { + uint256[2][] tmp_h; + function h(uint256[2][] calldata s) external returns (bytes memory) { + tmp_h = s; + return abi.encode(tmp_h); + } + uint256[2][2] tmp_i; + function i(uint256[2][2] calldata s) external returns (bytes memory) { + tmp_i = s; + return abi.encode(tmp_i); + } +} +// ==== +// EVMVersion: >homestead +// ---- +// h(uint256[2][]) : 0x20, 3, 123, 124, 223, 224, 323, 324 -> 32, 256, 0x20, 3, 123, 124, 223, 224, 323, 324 +// i(uint256[2][2]): 123, 124, 223, 224 -> 32, 128, 123, 124, 223, 224 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol index d74db942c..f1f57aca9 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_break.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 6 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol index f25a3c06a..96277887e 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_continue.sol @@ -9,5 +9,7 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f() -> 5 diff --git a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol index 3a13e9f0e..b8d35bb8f 100644 --- a/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol +++ b/test/libsolidity/semanticTests/asmForLoop/for_loop_nested.sol @@ -13,6 +13,8 @@ contract C { } } } +// ==== +// compileViaYul: also // ---- // f(uint256): 0 -> 2 // f(uint256): 1 -> 18 diff --git a/test/libsolidity/semanticTests/empty_contract.sol b/test/libsolidity/semanticTests/empty_contract.sol new file mode 100644 index 000000000..8950f01d1 --- /dev/null +++ b/test/libsolidity/semanticTests/empty_contract.sol @@ -0,0 +1,6 @@ +contract test { +} +// ==== +// compileViaYul: also +// ---- +// i_am_not_there() -> FAILURE diff --git a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol index f2016c967..b77e5be69 100644 --- a/test/libsolidity/semanticTests/functionCall/named_args_overload.sol +++ b/test/libsolidity/semanticTests/functionCall/named_args_overload.sol @@ -24,6 +24,8 @@ contract C { return 500; } } +// ==== +// compileViaYul: also // ---- // call(uint256): 0 -> 0 // call(uint256): 1 -> 1 diff --git a/test/libsolidity/semanticTests/functionCall/transaction_status.sol b/test/libsolidity/semanticTests/functionCall/transaction_status.sol new file mode 100644 index 000000000..6651630d5 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/transaction_status.sol @@ -0,0 +1,9 @@ +contract test { + function f() public { } + function g() public { revert(); } + function h() public { assert(false); } +} +// ---- +// f() -> +// g() -> FAILURE +// h() -> FAILURE diff --git a/test/libsolidity/semanticTests/shifts.sol b/test/libsolidity/semanticTests/shifts.sol index e202f2889..4f8892250 100644 --- a/test/libsolidity/semanticTests/shifts.sol +++ b/test/libsolidity/semanticTests/shifts.sol @@ -4,6 +4,7 @@ contract C { } } // ==== +// compileViaYul: also // EVMVersion: >=constantinople // ---- // f(uint256): 7 -> 28 diff --git a/test/libsolidity/semanticTests/smoke_test.sol b/test/libsolidity/semanticTests/smoke_test.sol index 7a87151b3..6e5cd9c6a 100644 --- a/test/libsolidity/semanticTests/smoke_test.sol +++ b/test/libsolidity/semanticTests/smoke_test.sol @@ -33,6 +33,9 @@ contract C { function p() public returns (string memory, uint, string memory) { return ("any", 42, "any"); } + function q(uint a) public returns (uint d) { + return a * 7; + } } // ---- // constructor(): 3 -> @@ -51,3 +54,5 @@ contract C { // n() -> 0x20, 3, "any" // o() -> 0x40, 0x80, 3, "any", 3, "any" // p() -> 0x60, 0x2a, 0xa0, 3, "any", 3, "any" +// q(uint256): 0 -> 0 +// q(uint256): 99 -> 693 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline.sol b/test/libsolidity/semanticTests/smoke_test_multiline.sol index 7395b1c3b..5d02e148e 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // -> 5 diff --git a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol index 17de40fc4..66bdfb915 100644 --- a/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol +++ b/test/libsolidity/semanticTests/smoke_test_multiline_comments.sol @@ -3,6 +3,8 @@ contract C { return a + b + c + d + e; } } +// ==== +// compileViaYul: also // ---- // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // # A comment on the function parameters. # diff --git a/test/libsolidity/semanticTests/storage/chop_sign_bits.sol b/test/libsolidity/semanticTests/storage/chop_sign_bits.sol new file mode 100644 index 000000000..51cba0261 --- /dev/null +++ b/test/libsolidity/semanticTests/storage/chop_sign_bits.sol @@ -0,0 +1,29 @@ +contract Test { + int16[] public x = [-1, -2]; + int16[2] public y = [-5, -6]; + int16 z; + function f() public returns (int16[] memory) { + int8[] memory t = new int8[](2); + t[0] = -3; + t[1] = -4; + x = t; + return x; + } + function g() public returns (int16[2] memory) { + int8[2] memory t = [-3, -4]; + y = t; + return y; + } + function h(int8 t) public returns (int16) { + z = t; + return z; + } +} +// ---- +// x(uint256): 0 -> -1 +// x(uint256): 1 -> -2 +// y(uint256): 0 -> -5 +// y(uint256): 1 -> -6 +// f() -> 0x20, 2, -3, -4 +// g() -> -3, -4 +// h(int8): -10 -> -10 diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol new file mode 100644 index 000000000..ca79ea742 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_access.sol @@ -0,0 +1,26 @@ +contract C { + uint[] storageArray; + function test_indicies(uint256 len) public + { + storageArray.length = len; + + for (uint i = 0; i < len; i++) + storageArray[i] = i + 1; + + for (uint i = 0; i < len; i++) + require(storageArray[i] == i + 1); + } +} +// ==== +// compileViaYul: true +// ---- +// test_indicies(uint256): 1 -> +// test_indicies(uint256): 129 -> +// test_indicies(uint256): 5 -> +// test_indicies(uint256): 10 -> +// test_indicies(uint256): 15 -> +// test_indicies(uint256): 0xFF -> +// test_indicies(uint256): 1000 -> +// test_indicies(uint256): 129 -> +// test_indicies(uint256): 128 -> +// test_indicies(uint256): 1 -> diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol new file mode 100644 index 000000000..8c5ee6845 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_boundery_test.sol @@ -0,0 +1,21 @@ +contract C { + uint[] storageArray; + function test_boundery_check(uint256 len, uint256 access) public returns +(uint256) + { + storageArray.length = len; + return storageArray[access]; + } +} +// ==== +// compileViaYul: true +// ---- +// test_boundery_check(uint256, uint256): 10, 11 -> FAILURE +// test_boundery_check(uint256, uint256): 10, 9 -> 0 +// test_boundery_check(uint256, uint256): 1, 9 -> FAILURE +// test_boundery_check(uint256, uint256): 1, 1 -> FAILURE +// test_boundery_check(uint256, uint256): 10, 10 -> FAILURE +// test_boundery_check(uint256, uint256): 256, 256 -> FAILURE +// test_boundery_check(uint256, uint256): 256, 255 -> 0 +// test_boundery_check(uint256, uint256): 256, 0xFFFF -> FAILURE +// test_boundery_check(uint256, uint256): 256, 2 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol b/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol new file mode 100644 index 000000000..344c0add5 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_index_zeroed_test.sol @@ -0,0 +1,51 @@ +contract C { + uint[] storageArray; + function test_zeroed_indicies(uint256 len) public + { + storageArray.length = len; + + for (uint i = 0; i < len; i++) + storageArray[i] = i + 1; + + if (len > 3) + { + storageArray.length = 3; + + for (uint i = 3; i < len; i++) + { + assembly { + mstore(0, storageArray_slot) + let pos := add(keccak256(0, 0x20), i) + + if iszero(eq(sload(pos), 0)) { + revert(0, 0) + } + } + } + + } + + storageArray.length = 0; + storageArray.length = len; + + for (uint i = 0; i < len; i++) + { + require(storageArray[i] == 0); + + uint256 val = storageArray[i]; + uint256 check; + + assembly { check := iszero(val) } + + require(check == 1); + } + } +} +// ==== +// compileViaYul: true +// ---- +// test_zeroed_indicies(uint256): 1 -> +// test_zeroed_indicies(uint256): 5 -> +// test_zeroed_indicies(uint256): 10 -> +// test_zeroed_indicies(uint256): 15 -> +// test_zeroed_indicies(uint256): 0xFF -> diff --git a/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol new file mode 100644 index 000000000..b972d8ade --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/array_storage_length_access.sol @@ -0,0 +1,19 @@ +contract C { + uint[] storageArray; + function set_get_length(uint256 len) public returns (uint256) + { + storageArray.length = len; + return storageArray.length; + } + +} +// ==== +// compileViaYul: true +// ---- +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 1 -> 1 +// set_get_length(uint256): 10 -> 10 +// set_get_length(uint256): 20 -> 20 +// set_get_length(uint256): 0 -> 0 +// set_get_length(uint256): 0xFF -> 0xFF +// set_get_length(uint256): 0xFFFF -> 0xFFFF diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul b/test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol similarity index 100% rename from test/libsolidity/semanticTests/viaYul/detect_add_overflow.yul rename to test/libsolidity/semanticTests/viaYul/detect_add_overflow.sol diff --git a/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol new file mode 100644 index 000000000..6e44c44ba --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_add_overflow_signed.sol @@ -0,0 +1,37 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a + b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a + b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> 11 +// f(int256,int256): -2, 1 -> -1 +// f(int256,int256): -2, 2 -> 0 +// f(int256,int256): 2, -2 -> 0 +// f(int256,int256): -5, -6 -> -11 +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, 0x0F -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x0F, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 1 -> FAILURE +// f(int256,int256): 1, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, -1 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -1, 0x8000000000000000000000000000000000000000000000000000000000000001 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000000, -1 -> FAILURE +// f(int256,int256): -1, 0x8000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// g(int8,int8): 5, 6 -> 11 +// g(int8,int8): -2, 1 -> -1 +// g(int8,int8): -2, 2 -> 0 +// g(int8,int8): 2, -2 -> 0 +// g(int8,int8): -5, -6 -> -11 +// g(int8,int8): 126, 1 -> 127 +// g(int8,int8): 1, 126 -> 127 +// g(int8,int8): 127, 1 -> FAILURE +// g(int8,int8): 1, 127 -> FAILURE +// g(int8,int8): -127, -1 -> -128 +// g(int8,int8): -1, -127 -> -128 +// g(int8,int8): -127, -2 -> FAILURE +// g(int8,int8): -2, -127 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol new file mode 100644 index 000000000..d8defa17a --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero.sol @@ -0,0 +1,25 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x) { + x = a % b; + } + function g(uint8 a, uint8 b) public pure returns (uint8 x) { + x = a % b; + } +} +// ==== +// compileViaYul: only +// ---- +// f(uint256,uint256): 10, 3 -> 1 +// f(uint256,uint256): 10, 2 -> 0 +// f(uint256,uint256): 11, 2 -> 1 +// f(uint256,uint256): 2, 2 -> 0 +// f(uint256,uint256): 1, 0 -> FAILURE +// f(uint256,uint256): 0, 0 -> FAILURE +// f(uint256,uint256): 0, 1 -> 0 +// g(uint8,uint8): 10, 3 -> 1 +// g(uint8,uint8): 10, 2 -> 0 +// g(uint8,uint8): 11, 2 -> 1 +// g(uint8,uint8): 2, 2 -> 0 +// g(uint8,uint8): 1, 0 -> FAILURE +// g(uint8,uint8): 0, 0 -> FAILURE +// g(uint8,uint8): 0, 1 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol new file mode 100644 index 000000000..9164bf06c --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mod_zero_signed.sol @@ -0,0 +1,35 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a % b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a % b; + } +} +// ==== +// compileViaYul: only +// ---- +// f(int256,int256): 10, 3 -> 1 +// f(int256,int256): 10, 2 -> 0 +// f(int256,int256): 11, 2 -> 1 +// f(int256,int256): -10, 3 -> -1 +// f(int256,int256): 10, -3 -> 1 +// f(int256,int256): -10, -3 -> -1 +// f(int256,int256): 2, 2 -> 0 +// f(int256,int256): 1, 0 -> FAILURE +// f(int256,int256): -1, 0 -> FAILURE +// f(int256,int256): 0, 0 -> FAILURE +// f(int256,int256): 0, 1 -> 0 +// f(int256,int256): 0, -1 -> 0 +// g(int8,int8): 10, 3 -> 1 +// g(int8,int8): 10, 2 -> 0 +// g(int8,int8): 11, 2 -> 1 +// g(int8,int8): -10, 3 -> -1 +// g(int8,int8): 10, -3 -> 1 +// g(int8,int8): -10, -3 -> -1 +// g(int8,int8): 2, 2 -> 0 +// g(int8,int8): 1, 0 -> FAILURE +// g(int8,int8): -1, 0 -> FAILURE +// g(int8,int8): 0, 0 -> FAILURE +// g(int8,int8): 0, 1 -> 0 +// g(int8,int8): 0, -1 -> 0 diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.yul b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol similarity index 100% rename from test/libsolidity/semanticTests/viaYul/detect_mul_overflow.yul rename to test/libsolidity/semanticTests/viaYul/detect_mul_overflow.sol diff --git a/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol new file mode 100644 index 000000000..930a24ed7 --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_mul_overflow_signed.sol @@ -0,0 +1,58 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a * b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a * b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> 30 +// f(int256,int256): -1, 1 -> -1 +// f(int256,int256): -1, 2 -> -2 +// # positive, positive # +// f(int256,int256): 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, 2 -> FAILURE +// f(int256,int256): 2, 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// # positive, negative # +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000000, -2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x4000000000000000000000000000000000000000000000000000000000000001, -2 -> FAILURE +// f(int256,int256): 2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 2, 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// # negative, positive # +// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000000 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -2, 0x4000000000000000000000000000000000000000000000000000000000000001 -> FAILURE +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, 2 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0xBFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 2 -> FAILURE +// # negative, negative # +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000001, -2 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): 0xC000000000000000000000000000000000000000000000000000000000000000, -2 -> FAILURE +// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000001 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE +// f(int256,int256): -2, 0xC000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// # small type # +// g(int8,int8): 5, 6 -> 30 +// g(int8,int8): -1, 1 -> -1 +// g(int8,int8): -1, 2 -> -2 +// # positive, positive # +// g(int8,int8): 63, 2 -> 126 +// g(int8,int8): 64, 2 -> FAILURE +// g(int8,int8): 2, 63 -> 126 +// g(int8,int8): 2, 64 -> FAILURE +// # positive, negative # +// g(int8,int8): 64, -2 -> -128 +// g(int8,int8): 65, -2 -> FAILURE +// g(int8,int8): 2, -64 -> -128 +// g(int8,int8): 2, -65 -> FAILURE +// # negative, positive # +// g(int8,int8): -2, 64 -> -128 +// g(int8,int8): -2, 65 -> FAILURE +// g(int8,int8): -64, 2 -> -128 +// g(int8,int8): -65, 2 -> FAILURE +// # negative, negative # +// g(int8,int8): -63, -2 -> 126 +// g(int8,int8): -64, -2 -> FAILURE +// g(int8,int8): -2, -63 -> 126 +// g(int8,int8): -2, -64 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol new file mode 100644 index 000000000..5342052ff --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow.sol @@ -0,0 +1,17 @@ +contract C { + function f(uint a, uint b) public pure returns (uint x) { + x = a - b; + } + function g(uint8 a, uint8 b) public pure returns (uint8 x) { + x = a - b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint256,uint256): 6, 5 -> 1 +// f(uint256,uint256): 6, 6 -> 0 +// f(uint256,uint256): 5, 6 -> FAILURE +// g(uint8,uint8): 6, 5 -> 1 +// g(uint8,uint8): 6, 6 -> 0 +// g(uint8,uint8): 5, 6 -> FAILURE diff --git a/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol new file mode 100644 index 000000000..1a87ef9ec --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/detect_sub_overflow_signed.sol @@ -0,0 +1,42 @@ +contract C { + function f(int a, int b) public pure returns (int x) { + x = a - b; + } + function g(int8 a, int8 b) public pure returns (int8 x) { + x = a - b; + } +} +// ==== +// compileViaYul: true +// ---- +// f(int256,int256): 5, 6 -> -1 +// f(int256,int256): -2, 1 -> -3 +// f(int256,int256): -2, 2 -> -4 +// f(int256,int256): 2, -2 -> 4 +// f(int256,int256): 2, 2 -> 0 +// f(int256,int256): -5, -6 -> 1 +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, -15 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0, -16 -> FAILURE +// f(int256,int256): 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, -1 -> FAILURE +// f(int256,int256): 15, 0x8000000000000000000000000000000000000000000000000000000000000010 -> 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// f(int256,int256): 16, 0x8000000000000000000000000000000000000000000000000000000000000010 -> FAILURE +// f(int256,int256): 1, 0x8000000000000000000000000000000000000000000000000000000000000000 -> FAILURE +// f(int256,int256): -1, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): -2, 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, 1 -> 0x8000000000000000000000000000000000000000000000000000000000000000 +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000001, 2 -> FAILURE +// f(int256,int256): 0x8000000000000000000000000000000000000000000000000000000000000000, 1 -> FAILURE +// g(int8,int8): 5, 6 -> -1 +// g(int8,int8): -2, 1 -> -3 +// g(int8,int8): -2, 2 -> -4 +// g(int8,int8): 2, -2 -> 4 +// g(int8,int8): 2, 2 -> 0 +// g(int8,int8): -5, -6 -> 1 +// g(int8,int8): 126, -1 -> 127 +// g(int8,int8): 1, -126 -> 127 +// g(int8,int8): 127, -1 -> FAILURE +// g(int8,int8): 1, -127 -> FAILURE +// g(int8,int8): -127, 1 -> -128 +// g(int8,int8): -1, 127 -> -128 +// g(int8,int8): -127, 2 -> FAILURE +// g(int8,int8): -2, 127 -> FAILURE diff --git a/test/libsolidity/smtCheckerTestsJSON/multi.json b/test/libsolidity/smtCheckerTestsJSON/multi.json index ba4cf2637..f892c28fe 100644 --- a/test/libsolidity/smtCheckerTestsJSON/multi.json +++ b/test/libsolidity/smtCheckerTestsJSON/multi.json @@ -3,8 +3,8 @@ { "smtlib2responses": { - "0x092d52dc5c2b54c1909592f7b3c8efedfd87afc0223ce421a24a1cc7905006b4": "sat\n((|EVALEXPR_0| 1))\n", - "0x8faacfc008b6f2278b5927ff22d76832956dfb46b3c21a64fab96583c241b88f": "unsat\n", + "0x0a0e9583fd983e7ce82e96bd95f7c0eb831e2dd3ce3364035e30bf1d22823b34": "sat\n((|EVALEXPR_0| 1))\n", + "0x15353582486fb1dac47801edbb366ae40a59ef0191ebe7c09ca32bdabecc2f1a": "unsat\n", "0xa66d08de30c873ca7d0e7e9e426f278640e0ee463a1aed2e4e80baee916b6869": "sat\n((|EVALEXPR_0| 0))\n" } } diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol similarity index 63% rename from test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol rename to test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol index 9acac7a64..9d99c8b4d 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_beginning.sol @@ -3,10 +3,9 @@ contract C { assembly { function f(a, b) {} f() - f(1,) f(,1) } } } // ---- -// ParserError: (113-114): Literal, identifier or instruction expected. +// ParserError: (101-102): Literal, identifier or instruction expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol new file mode 100644 index 000000000..0d041492d --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_end.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + assembly { + function f(a, b) {} + f() + f(1,) + } + } +} +// ---- +// ParserError: (103-104): Literal, identifier or instruction expected. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol new file mode 100644 index 000000000..09e0ba038 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/invalid/empty_fun_arg_middle.sol @@ -0,0 +1,10 @@ +contract C { + function f() public pure { + assembly { + function f(a, b, c) {} + f(1,,1) + } + } +} +// ---- +// ParserError: (96-97): Literal, identifier or instruction expected. diff --git a/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol b/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol index dfb706417..6e6546c99 100644 --- a/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol +++ b/test/libsolidity/syntaxTests/nameAndTypeResolution/297_library_functions_do_not_have_value.sol @@ -5,4 +5,4 @@ contract test { } } // ---- -// TypeError: (87-96): Member "value" is only available for payable functions. +// TypeError: (87-96): Member "value" is not allowed in delegated calls due to "msg.value" persisting. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol b/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol new file mode 100644 index 000000000..2df3bbe4f --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/gas_value_without_call.sol @@ -0,0 +1,13 @@ +contract C { + function f() external payable {} + function g(address a) external pure { + a.call.value(42); + a.call.gas(42); + a.staticcall.gas(42); + a.delegatecall.gas(42); + } + function h() external view { + this.f.value(42); + this.f.gas(42); + } +} diff --git a/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol b/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol new file mode 100644 index 000000000..0a58a516c --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/gas_with_call_nonpayable.sol @@ -0,0 +1,16 @@ +contract C { + function f(address a) external view returns (bool success) { + (success,) = a.call.gas(42)(""); + } + function g(address a) external view returns (bool success) { + (success,) = a.call.gas(42)(""); + } + function h() external payable {} + function i() external view { + this.h.gas(42)(); + } +} +// ---- +// TypeError: (90-108): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (190-208): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (279-295): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol new file mode 100644 index 000000000..6f8b31bfc --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/staticcall_gas_view.sol @@ -0,0 +1,11 @@ +contract C { + function f() external view {} + function test(address a) external view returns (bool status) { + // This used to incorrectly raise an error about violating the view mutability. + (status,) = a.staticcall.gas(42)(""); + this.f.gas(42)(); + } +} +// ==== +// EVMVersion: >=byzantium +// ---- diff --git a/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol b/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol new file mode 100644 index 000000000..cf5d885c9 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/value_with_call_nonpayable.sol @@ -0,0 +1,16 @@ +contract C { + function f(address a) external view returns (bool success) { + (success,) = a.call.value(42)(""); + } + function g(address a) external view returns (bool success) { + (success,) = a.call.value(42)(""); + } + function h() external payable {} + function i() external view { + this.h.value(42)(); + } +} +// ---- +// TypeError: (90-110): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (192-212): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. +// TypeError: (283-301): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index f33416c09..9773dedab 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -46,20 +46,14 @@ ObjectCompilerTest::ObjectCompilerTest(string const& _filename) BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\".")); file.exceptions(ios::badbit); - string line; - while (getline(file, line)) + m_source = parseSourceAndSettings(file); + if (m_settings.count("optimize")) { - if (boost::algorithm::starts_with(line, "// ----")) - break; - if (m_source.empty() && boost::algorithm::starts_with(line, "// optimize")) - m_optimize = true; - m_source += line + "\n"; + m_optimize = true; + m_validatedSettings["optimize"] = "true"; + m_settings.erase("optimize"); } - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "//")) - m_expectation += line.substr((line.size() >= 3 && line[2] == ' ') ? 3 : 2) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) diff --git a/test/libyul/StackReuseCodegen.cpp b/test/libyul/StackReuseCodegen.cpp index d386c83f8..a1331f68c 100644 --- a/test/libyul/StackReuseCodegen.cpp +++ b/test/libyul/StackReuseCodegen.cpp @@ -277,16 +277,15 @@ BOOST_AUTO_TEST_CASE(functions_multi_return) let unused := 7 })"; BOOST_CHECK_EQUAL(assemble(in), - "PUSH1 0xB JUMP " + "PUSH1 0x13 JUMP " "JUMPDEST PUSH1 0x0 SWAP3 SWAP2 POP POP JUMP " // f - "JUMPDEST PUSH1 0x17 JUMP " "JUMPDEST PUSH1 0x0 PUSH1 0x0 SWAP1 SWAP2 JUMP " // g - "JUMPDEST PUSH1 0x21 PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2) - "JUMPDEST PUSH1 0x2B PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4) + "JUMPDEST PUSH1 0x1D PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2) + "JUMPDEST PUSH1 0x27 PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4) "JUMPDEST SWAP1 POP " // assignment to x "POP " // remove x - "PUSH1 0x34 PUSH1 0xF JUMP " // g() - "JUMPDEST PUSH1 0x3A PUSH1 0xF JUMP " // g() + "PUSH1 0x30 PUSH1 0xB JUMP " // g() + "JUMPDEST PUSH1 0x36 PUSH1 0xB JUMP " // g() "JUMPDEST SWAP2 POP SWAP2 POP " // assignments "POP POP " // removal of y and z "PUSH1 0x7 POP " diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index d473c3a5c..141dd1ae2 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -54,13 +54,7 @@ YulInterpreterTest::YulInterpreterTest(string const& _filename) file.exceptions(ios::badbit); m_source = parseSourceAndSettings(file); - - string line; - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "// ")) - m_expectation += line.substr(3) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index e5552d675..2fd9a5ef4 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -36,7 +35,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -47,8 +48,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -99,12 +101,7 @@ YulOptimizerTest::YulOptimizerTest(string const& _filename) m_settings.erase("step"); } - string line; - while (getline(file, line)) - if (boost::algorithm::starts_with(line, "// ")) - m_expectation += line.substr(3) + "\n"; - else - m_expectation += line + "\n"; + m_expectation = parseSimpleExpectations(file); } TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) @@ -115,6 +112,15 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line soltestAssert(m_dialect, "Dialect not set."); if (m_optimizerStep == "disambiguator") disambiguate(); + else if (m_optimizerStep == "nameDisplacer") + { + disambiguate(); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + NameDisplacer{ + nameDispenser, + {"illegal1"_yulstring, "illegal2"_yulstring, "illegal3"_yulstring, "illegal4"_yulstring, "illegal5"_yulstring} + }(*m_ast); + } else if (m_optimizerStep == "blockFlattener") { disambiguate(); @@ -247,6 +253,21 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line SSATransform::run(*m_ast, nameDispenser); RedundantAssignEliminator::run(*m_dialect, *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); + + LoadResolver::run(*m_dialect, *m_ast); + + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + ExpressionJoiner::run(*m_ast); + ExpressionJoiner::run(*m_ast); + } else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); @@ -292,12 +313,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line disambiguate(); NameDispenser nameDispenser{*m_dialect, *m_ast}; ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); - WordSizeTransform::run(*m_ast, nameDispenser); + WordSizeTransform::run(*m_dialect, *m_ast, nameDispenser); } else if (m_optimizerStep == "fullSuite") { GasMeter meter(dynamic_cast(*m_dialect), false, 200); - OptimiserSuite::run(*m_dialect, meter, *m_ast, *m_analysisInfo, true); + OptimiserSuite::run(*m_dialect, &meter, *m_ast, *m_analysisInfo, true); } else { diff --git a/test/libyul/objectCompiler/function_series.yul b/test/libyul/objectCompiler/function_series.yul new file mode 100644 index 000000000..75754b920 --- /dev/null +++ b/test/libyul/objectCompiler/function_series.yul @@ -0,0 +1,28 @@ +object "Contract" { + code { + function f() {} + function g() {} + sstore(0, 1) + } +} + +// ---- +// Assembly: +// /* "source":33:48 */ +// jump(tag_1) +// tag_2: +// /* "source":46:48 */ +// jump +// /* "source":53:68 */ +// tag_3: +// /* "source":66:68 */ +// jump +// tag_1: +// /* "source":83:84 */ +// 0x01 +// /* "source":80:81 */ +// 0x00 +// /* "source":73:85 */ +// sstore +// Bytecode: 6007565b565b565b6001600055 +// Opcodes: PUSH1 0x7 JUMP JUMPDEST JUMP JUMPDEST JUMP JUMPDEST PUSH1 0x1 PUSH1 0x0 SSTORE diff --git a/test/libyul/objectCompiler/nested_optimizer.yul b/test/libyul/objectCompiler/nested_optimizer.yul index 2775c346d..1d5e568f1 100644 --- a/test/libyul/objectCompiler/nested_optimizer.yul +++ b/test/libyul/objectCompiler/nested_optimizer.yul @@ -1,4 +1,3 @@ -// optimize object "a" { code { let x := calldataload(0) @@ -15,24 +14,26 @@ object "a" { } } } +// ==== +// optimize: true // ---- // Assembly: -// /* "source":60:61 */ +// /* "source":48:49 */ // 0x00 // 0x00 -// /* "source":47:62 */ +// /* "source":35:50 */ // calldataload -// /* "source":119:139 */ +// /* "source":107:127 */ // sstore // stop // // sub_0: assembly { -// /* "source":200:201 */ +// /* "source":188:189 */ // 0x00 // 0x00 -// /* "source":187:202 */ +// /* "source":175:190 */ // calldataload -// /* "source":265:285 */ +// /* "source":253:273 */ // sstore // } // Bytecode: 600060003555fe diff --git a/test/libyul/objectCompiler/simple_optimizer.yul b/test/libyul/objectCompiler/simple_optimizer.yul index c757dee71..3d00e45d3 100644 --- a/test/libyul/objectCompiler/simple_optimizer.yul +++ b/test/libyul/objectCompiler/simple_optimizer.yul @@ -1,18 +1,19 @@ -// optimize { let x := calldataload(0) let y := calldataload(0) let z := sub(y, x) sstore(add(x, 0), z) } +// ==== +// optimize: true // ---- // Assembly: -// /* "source":38:39 */ +// /* "source":26:27 */ // 0x00 // 0x00 -// /* "source":25:40 */ +// /* "source":13:28 */ // calldataload -// /* "source":91:111 */ +// /* "source":79:99 */ // sstore // Bytecode: 600060003555 // Opcodes: PUSH1 0x0 PUSH1 0x0 CALLDATALOAD SSTORE diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul new file mode 100644 index 000000000..89075e3bb --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/clear_not_needed.yul @@ -0,0 +1,21 @@ +{ + let a := calldataload(0) + let x := calldataload(0x20) + x := a + let z := 0 + x := z + a := 9 + sstore(x, 3) +} +// ==== +// step: commonSubexpressionEliminator +// ---- +// { +// let a := calldataload(0) +// let x := calldataload(0x20) +// x := a +// let z := 0 +// x := z +// a := 9 +// sstore(z, 3) +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/storage.yul b/test/libyul/yulOptimizerTests/fullSuite/storage.yul new file mode 100644 index 000000000..9867345fa --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/storage.yul @@ -0,0 +1,15 @@ +{ + sstore(4, 5) + sstore(4, 3) + sstore(8, sload(4)) +} +// ==== +// step: fullSuite +// ---- +// { +// { +// sstore(4, 5) +// sstore(4, 3) +// sstore(8, sload(4)) +// } +// } 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 new file mode 100644 index 000000000..0bd777cf8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul @@ -0,0 +1,38 @@ +{ + mstore(2, 9) + sstore(0, mload(2)) + pop(call(0, 0, 0, 0, 0, 0, 0)) + sstore(0, mload(2)) + + mstore(2, 10) + mstore8(calldataload(0), 4) + sstore(0, mload(2)) + + mstore(2, 10) + g() + sstore(0, mload(2)) + + function g() {} +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := 9 +// let _2 := 2 +// mstore(_2, _1) +// let _4 := _1 +// let _5 := 0 +// sstore(_5, _4) +// pop(call(_5, _5, _5, _5, _5, _5, _5)) +// sstore(_5, mload(_2)) +// let _17 := 10 +// mstore(_2, _17) +// mstore8(calldataload(_5), 4) +// sstore(_5, mload(_2)) +// mstore(_2, _17) +// g() +// sstore(_5, mload(_2)) +// function g() +// { } +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul b/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul new file mode 100644 index 000000000..17a8761d9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_msize.yul @@ -0,0 +1,17 @@ +{ + // No mload removal because of msize + mstore(calldataload(0), msize()) + let t := mload(calldataload(10)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := msize() +// let _3 := calldataload(0) +// mstore(_3, _1) +// let t := mload(calldataload(10)) +// sstore(t, mload(_3)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul new file mode 100644 index 000000000..7f5dfb2f0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write.yul @@ -0,0 +1,22 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(calldataload(0), 1) + } + let t := mload(0) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _3 := 0 +// let _4 := calldataload(_3) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(_4, _5) } +// let t := mload(_3) +// sstore(t, mload(_4)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul new file mode 100644 index 000000000..f7cfdd7c3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_known_write_with_distance.yul @@ -0,0 +1,20 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(add(calldataload(0), 0x20), 1) + } + let t := mload(add(calldataload(0), 0x20)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _4 := calldataload(0) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(add(_4, 0x20), _5) } +// sstore(mload(add(_4, 0x20)), _2) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul b/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul new file mode 100644 index 000000000..4fc21bbf5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/merge_unknown_write.yul @@ -0,0 +1,22 @@ +{ + mstore(calldataload(0), calldataload(10)) + if calldataload(1) { + mstore(0, 1) + } + let t := mload(0) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// let _3 := 0 +// let _4 := calldataload(_3) +// mstore(_4, _2) +// let _5 := 1 +// if calldataload(_5) { mstore(_3, _5) } +// let t := mload(_3) +// sstore(t, mload(_4)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/reassign.yul b/test/libyul/yulOptimizerTests/loadResolver/reassign.yul new file mode 100644 index 000000000..fe7fccfe6 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/reassign.yul @@ -0,0 +1,16 @@ +{ + let a := calldataload(0) + sstore(a, 6) + a := calldataload(2) + mstore(0, sload(a)) +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := 0 +// let a := calldataload(_1) +// sstore(a, 6) +// a := calldataload(2) +// mstore(_1, sload(a)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul b/test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul new file mode 100644 index 000000000..d2d0999fa --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/reassign_value_expression.yul @@ -0,0 +1,32 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + sstore(a, 7) + // This clears the expression assigned to ``a`` but + // should not clear storage knowledge + x := 9 + mstore(sload(a), 11) + // This, on the other hand, actually clears knowledge + a := 33 + mstore(sload(a), 11) + // Try again with different expression to avoid + // clearing because we cannot know if it is different + a := 39 + mstore(sload(a), 11) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let _3 := 7 +// sstore(a, _3) +// x := 9 +// let _4 := 11 +// mstore(_3, _4) +// a := 33 +// mstore(sload(a), _4) +// a := 39 +// mstore(sload(a), _4) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul b/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul new file mode 100644 index 000000000..ae23c1b8b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/second_mstore_with_delta.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + let b := add(x, 42) + mstore(a, 7) + // does not invalidate the first store, because the + // difference is larger than 32, even if the absolute + // values are unknown + mstore(b, 8) + sstore(mload(a), mload(b)) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let b := add(x, 42) +// let _4 := 7 +// mstore(a, _4) +// let _5 := 8 +// mstore(b, _5) +// sstore(_4, _5) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/second_store.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store.yul new file mode 100644 index 000000000..67aa196f4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store.yul @@ -0,0 +1,19 @@ +{ + let x := calldataload(1) + sstore(x, 7) + sstore(calldataload(0), 6) + // We cannot replace this because we do not know + // if the two slots are different. + mstore(0, sload(x)) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// sstore(x, 7) +// let _3 := 6 +// let _4 := 0 +// sstore(calldataload(_4), _3) +// mstore(_4, sload(x)) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul new file mode 100644 index 000000000..b90b177b3 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store_same_value.yul @@ -0,0 +1,19 @@ +{ + let x := calldataload(1) + sstore(x, 7) + sstore(calldataload(0), 7) + // We can replace this because both values that were + // written are 7. + mstore(0, sload(x)) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// let _2 := 7 +// sstore(x, _2) +// let _4 := 0 +// sstore(calldataload(_4), _2) +// mstore(_4, _2) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul b/test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul new file mode 100644 index 000000000..a4154ce63 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/second_store_with_delta.yul @@ -0,0 +1,24 @@ +{ + let x := calldataload(1) + let a := add(x, 10) + let b := add(x, 20) + sstore(a, 7) + // does not invalidate the first store, because the + // difference is a constant, even if the absolute + // values are unknown + sstore(b, 8) + mstore(sload(a), sload(b)) +} +// ==== +// step: loadResolver +// ---- +// { +// let x := calldataload(1) +// let a := add(x, 10) +// let b := add(x, 20) +// let _4 := 7 +// sstore(a, _4) +// let _5 := 8 +// sstore(b, _5) +// mstore(_4, _5) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/simple.yul b/test/libyul/yulOptimizerTests/loadResolver/simple.yul new file mode 100644 index 000000000..a32a54241 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/simple.yul @@ -0,0 +1,14 @@ +{ + sstore(calldataload(0), calldataload(10)) + let t := sload(calldataload(10)) + let q := sload(calldataload(0)) + mstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// sstore(calldataload(0), _2) +// mstore(sload(_2), _2) +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul b/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul new file mode 100644 index 000000000..a4c06ebdc --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/simple_memory.yul @@ -0,0 +1,14 @@ +{ + mstore(calldataload(0), calldataload(10)) + let t := mload(calldataload(10)) + let q := mload(calldataload(0)) + sstore(t, q) +} +// ==== +// step: loadResolver +// ---- +// { +// let _2 := calldataload(10) +// mstore(calldataload(0), _2) +// sstore(mload(_2), _2) +// } diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul b/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul new file mode 100644 index 000000000..9c547d17a --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/funtion_call.yul @@ -0,0 +1,25 @@ +{ + let x := illegal4(1, 2) + function illegal4(illegal1, illegal2) -> illegal3 { illegal3 := add(illegal1, illegal2) } + { + let y := illegal5(3, 4) + function illegal5(illegal1, illegal2) -> illegal3 { illegal3 := add(illegal1, illegal2) } + } +} +// ==== +// step: nameDisplacer +// ---- +// { +// let x := illegal4_1(1, 2) +// function illegal4_1(illegal1_2, illegal2_3) -> illegal3_4 +// { +// illegal3_4 := add(illegal1_2, illegal2_3) +// } +// { +// let y := illegal5_5(3, 4) +// function illegal5_5(illegal1_1, illegal2_2) -> illegal3_3 +// { +// illegal3_3 := add(illegal1_1, illegal2_2) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul b/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul new file mode 100644 index 000000000..dae343217 --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/variables.yul @@ -0,0 +1,11 @@ +{ { let illegal1 := 1 } { let illegal2 := 2 let illegal3, illegal4 } } +// ==== +// step: nameDisplacer +// ---- +// { +// { let illegal1_1 := 1 } +// { +// let illegal2_2 := 2 +// let illegal3_3, illegal4_4 +// } +// } diff --git a/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul b/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul new file mode 100644 index 000000000..73228739a --- /dev/null +++ b/test/libyul/yulOptimizerTests/nameDisplacer/variables_inside_functions.yul @@ -0,0 +1,16 @@ +{ + function f(illegal1, illegal2) -> illegal3 { + let illegal4 := illegal1 + illegal3 := add(illegal1, illegal2) + } +} +// ==== +// step: nameDisplacer +// ---- +// { +// function f(illegal1_1, illegal2_2) -> illegal3_3 +// { +// let illegal4_4 := illegal1_1 +// illegal3_3 := add(illegal1_1, illegal2_2) +// } +// } diff --git a/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul b/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul new file mode 100644 index 000000000..016dd9bb8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/wordSizeTransform/or_bool_renamed.yul @@ -0,0 +1,25 @@ +{ + let or_bool := 2 + if or_bool { sstore(0, 1) } +} +// ==== +// step: wordSizeTransform +// ---- +// { +// let or_bool_3_0 := 0 +// let or_bool_3_1 := 0 +// let or_bool_3_2 := 0 +// let or_bool_3_3 := 2 +// if or_bool(or_bool_3_0, or_bool_3_1, or_bool_3_2, or_bool_3_3) +// { +// let _1_0 := 0 +// let _1_1 := 0 +// let _1_2 := 0 +// let _1_3 := 1 +// let _2_0 := 0 +// let _2_1 := 0 +// let _2_2 := 0 +// let _2_3 := 0 +// sstore(_2_0, _2_1, _2_2, _2_3, _1_0, _1_1, _1_2, _1_3) +// } +// } diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index 9a7df2489..b9e72d27b 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -2,13 +2,13 @@ add_subdirectory(ossfuzz) add_subdirectory(yulInterpreter) add_executable(yulrun yulrun.cpp) -target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm Boost::boost Boost::program_options) add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp) -target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) +target_link_libraries(solfuzzer PRIVATE libsolc evmasm Boost::boost Boost::program_options Boost::system) add_executable(yulopti yulopti.cpp) -target_link_libraries(yulopti PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) +target_link_libraries(yulopti PRIVATE solidity Boost::boost Boost::program_options Boost::system) add_executable(isoltest isoltest.cpp @@ -31,4 +31,4 @@ add_executable(isoltest ../libyul/YulOptimizerTest.cpp ../libyul/YulInterpreterTest.cpp ) -target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES}) +target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm Boost::boost Boost::program_options Boost::unit_test_framework) diff --git a/test/tools/isoltest.cpp b/test/tools/isoltest.cpp index a127525c1..f5c365e57 100644 --- a/test/tools/isoltest.cpp +++ b/test/tools/isoltest.cpp @@ -182,7 +182,7 @@ TestTool::Result TestTool::process() else return Result::Skipped; } - catch(boost::exception const& _e) + catch (boost::exception const& _e) { AnsiColorized(cout, formatted, {BOLD, RED}) << "Exception during test: " << boost::diagnostic_information(_e) << endl; @@ -191,7 +191,9 @@ TestTool::Result TestTool::process() catch (std::exception const& _e) { AnsiColorized(cout, formatted, {BOLD, RED}) << - "Exception during test: " << _e.what() << endl; + "Exception during test" << + (_e.what() ? ": " + string(_e.what()) : ".") << + endl; return Result::Exception; } catch (...) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index c4a137088..f32fda260 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -55,36 +55,30 @@ string ProtoConverter::createAlphaNum(string const& _strBytes) const bool ProtoConverter::isCaseLiteralUnique(Literal const& _x) { - std::string tmp; + dev::u256 mpCaseLiteralValue; bool isUnique = false; - bool isEmptyString = false; + switch (_x.literal_oneof_case()) { case Literal::kIntval: - tmp = std::to_string(_x.intval()); + mpCaseLiteralValue = dev::u256(_x.intval()); break; case Literal::kHexval: - tmp = "0x" + createHex(_x.hexval()); + // 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: - tmp = createAlphaNum(_x.strval()); - if (tmp.empty()) - { - isEmptyString = true; - tmp = std::to_string(0); - } - else - tmp = "\"" + tmp + "\""; + mpCaseLiteralValue = dev::u256(dev::h256(createAlphaNum(_x.strval()), dev::h256::FromBinary, dev::h256::AlignLeft)); break; case Literal::LITERAL_ONEOF_NOT_SET: - tmp = std::to_string(1); + // If the proto generator does not generate a valid Literal + // we generate a case 1: + mpCaseLiteralValue = 1; break; } - if (!_x.has_strval() || isEmptyString) - isUnique = m_switchLiteralSetPerScope.top().insert(dev::u256(tmp)).second; - else - isUnique = m_switchLiteralSetPerScope.top().insert( - dev::u256(dev::h256(tmp, dev::h256::FromBinary, dev::h256::AlignLeft))).second; + + isUnique = m_switchLiteralSetPerScope.top().insert(mpCaseLiteralValue).second; return isUnique; } @@ -622,7 +616,9 @@ void ProtoConverter::visit(FunctionCall const& _x) visit(_x.call_zero()); break; case FunctionCall::kCallMultidecl: - visit(_x.call_multidecl()); + // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" + if (!m_inForInitScope) + visit(_x.call_multidecl()); break; case FunctionCall::kCallMultiassign: visit(_x.call_multiassign()); @@ -661,17 +657,38 @@ void ProtoConverter::visit(StoreFunc const& _x) } void ProtoConverter::visit(ForStmt const& _x) +{ + bool wasInForBody = m_inForBodyScope; + bool wasInForInit = m_inForInitScope; + m_inForBodyScope = false; + m_inForInitScope = true; + m_output << "for "; + visit(_x.for_init()); + m_inForInitScope = false; + visit(_x.for_cond()); + visit(_x.for_post()); + m_inForBodyScope = true; + visit(_x.for_body()); + m_inForBodyScope = wasInForBody; + m_inForInitScope = wasInForInit; +} + +void ProtoConverter::visit(BoundedForStmt const& _x) { // Boilerplate for loop that limits the number of iterations to a maximum of 4. - // TODO: Generalize for loop init, condition, and post blocks. std::string loopVarName("i_" + std::to_string(m_numNestedForLoops++)); m_output << "for { let " << loopVarName << " := 0 } " - << "lt(" << loopVarName << ", 0x60) " - << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } "; - m_inForScope.push(true); + << "lt(" << loopVarName << ", 0x60) " + << "{ " << loopVarName << " := add(" << loopVarName << ", 0x20) } "; + // Store previous for body scope + bool wasInForBody = m_inForBodyScope; + bool wasInForInit = m_inForInitScope; + m_inForBodyScope = true; + m_inForInitScope = false; visit(_x.for_body()); - m_inForScope.pop(); - --m_numNestedForLoops; + // Restore previous for body scope and init + m_inForBodyScope = wasInForBody; + m_inForInitScope = wasInForInit; } void ProtoConverter::visit(CaseStmt const& _x) @@ -771,7 +788,9 @@ void ProtoConverter::visit(Statement const& _x) switch (_x.stmt_oneof_case()) { case Statement::kDecl: - visit(_x.decl()); + // Hack: Disallow (multi) variable declarations until scope extension is implemented for "for-init" + if (!m_inForInitScope) + visit(_x.decl()); break; case Statement::kAssignment: visit(_x.assignment()); @@ -788,15 +807,18 @@ void ProtoConverter::visit(Statement const& _x) case Statement::kForstmt: visit(_x.forstmt()); break; + case Statement::kBoundedforstmt: + visit(_x.boundedforstmt()); + break; case Statement::kSwitchstmt: visit(_x.switchstmt()); break; case Statement::kBreakstmt: - if (m_inForScope.top()) + if (m_inForBodyScope) m_output << "break\n"; break; case Statement::kContstmt: - if (m_inForScope.top()) + if (m_inForBodyScope) m_output << "continue\n"; break; case Statement::kLogFunc: @@ -993,4 +1015,4 @@ std::string ProtoConverter::functionTypeToString(NumFunctionReturns _type) case NumFunctionReturns::Multiple: return "multireturn"; } -} \ No newline at end of file +} diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index dc707a0fa..bea31b1e5 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -42,9 +42,10 @@ public: { m_numLiveVars = 0; m_numVarsPerScope.push(m_numLiveVars); - m_numNestedForLoops = 0; - m_inForScope.push(false); m_numFunctionSets = 0; + m_inForBodyScope = false; + m_inForInitScope = false; + m_numNestedForLoops = 0; } ProtoConverter(ProtoConverter const&) = delete; ProtoConverter(ProtoConverter&&) = delete; @@ -69,6 +70,7 @@ private: void visit(Statement const&); void visit(FunctionDefinition const&); void visit(ForStmt const&); + void visit(BoundedForStmt const&); void visit(CaseStmt const&); void visit(SwitchStmt const&); void visit(TernaryOp const&); @@ -131,9 +133,6 @@ private: std::stack m_numVarsPerScope; // Number of live variables in function scope unsigned m_numLiveVars; - // Number of nested for loops for loop index referencing - unsigned m_numNestedForLoops; - std::stack m_inForScope; // 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 @@ -146,6 +145,12 @@ private: // 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 + 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 + bool m_inForInitScope; }; } } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index 4793a029f..4a39ab258 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -256,8 +256,15 @@ message IfStmt { required Block if_body = 2; } +message BoundedForStmt { + required Block for_body = 1; +} + message ForStmt { required Block for_body = 1; + required Block for_init = 2; + required Block for_post = 3; + required Expression for_cond = 4; } message CaseStmt { @@ -324,6 +331,7 @@ message Statement { ExtCodeCopy extcode_copy = 12; TerminatingStmt terminatestmt = 13; FunctionCall functioncall = 14; + BoundedForStmt boundedforstmt = 15; } }