diff --git a/.circleci/README.md b/.circleci/README.md new file mode 100644 index 000000000..7f7b01455 --- /dev/null +++ b/.circleci/README.md @@ -0,0 +1,28 @@ +## CircleCI integration + +### Docker images + +The docker images are build locally on the developer machine: + +```!sh +cd .circleci/docker/ + +docker build -t ethereum/solidity-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 . +docker push ethereum/solidity-buildpack-deps:ubuntu1904 +``` + +which you can find on Dockerhub after the push at: + + https://hub.docker.com/r/ethereum/solidity-buildpack-deps + +where the image tag reflects the target OS to build Solidity and run its test on. + +### Testing docker images locally + +```!sh +cd solidity +# Mounts your local solidity directory in docker container for testing +docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904 /bin/bash +cd /src/solidity + +``` \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index cf9c92128..47c6518ee 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,193 +1,451 @@ +# 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" -o -n "$FORCE_RELEASE" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi + echo -n "$CIRCLE_SHA1" > commit_hash.txt mkdir -p build cd build - cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo $CMAKE_OPTIONS + [ -n "$COVERAGE" -a "$CIRCLE_BRANCH" != release -a -z "$CIRCLE_TAG" ] && CMAKE_OPTIONS="$CMAKE_OPTIONS -DCOVERAGE=ON" + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS -G "Unix Makefiles" make -j4 - - run_tests: &run_tests - name: Tests - command: scripts/tests.sh --junit_report test_results - - solc_artifact: &solc_artifact + + - run_build_ossfuzz: &run_build_ossfuzz + name: Build_ossfuzz + command: | + mkdir -p build + cd build + protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz + protoc --proto_path=../test/tools/ossfuzz abiV2Proto.proto --cpp_out=../test/tools/ossfuzz + cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS + make ossfuzz ossfuzz_proto ossfuzz_abiv2 -j4 + + - 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 -version: 2 + # compiled OSSFUZZ targets + - artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz + root: build + paths: + - test/tools/ossfuzz/abiv2_proto_ossfuzz + - test/tools/ossfuzz/const_opt_ossfuzz + - test/tools/ossfuzz/solc_noopt_ossfuzz + - test/tools/ossfuzz/solc_opt_ossfuzz + - test/tools/ossfuzz/strictasm_assembly_ossfuzz + - test/tools/ossfuzz/strictasm_diff_ossfuzz + - test/tools/ossfuzz/strictasm_opt_ossfuzz + - test/tools/ossfuzz/yul_proto_diff_ossfuzz + - test/tools/ossfuzz/yul_proto_ossfuzz + + # 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_soltest_all: &run_soltest_all + name: soltest_all + command: ./.circleci/soltest_all.sh + + - run_cmdline_tests: &run_cmdline_tests + name: command line tests + command: ./test/cmdlineTests.sh + + - test_ubuntu1904: &test_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_ubuntu1904_clang: &test_ubuntu1904_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_ubuntu1904_all: &test_ubuntu1904 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - attach_workspace: + at: build + - run: *run_soltest_all + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + - test_asan: &test_asan + <<: *test_ubuntu1904 + 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_clang: &workflow_ubuntu1904_clang + <<: *workflow_trigger_on_tags + requires: + - b_ubu_clang + + - workflow_ubuntu1904_release: &workflow_ubuntu1904_release + <<: *workflow_trigger_on_tags + requires: + - b_ubu_release + + - workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov + <<: *workflow_trigger_on_tags + requires: + - 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 + + # -------------------------------------------------------------------------- + # Notification Templates + - gitter_notify_failure: &gitter_notify_failure + name: Gitter notify failure + command: >- + curl -X POST -i + -i -H "Content-Type: application/json" + -H "Accept: application/json" + -H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages" + -d '{"text":" ❌ Nightly job **'$CIRCLE_JOB'** failed on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}' + when: on_fail + + - gitter_notify_success: &gitter_notify_success + name: Gitter notify success + command: >- + curl -X POST -i + -i -H "Content-Type: application/json" + -H "Accept: application/json" + -H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages" + -d '{"text":" ✅ Nightly job **'$CIRCLE_JOB'** succeeded on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}' + when: on_success + +# ----------------------------------------------------------------------------------------------- jobs: - build_emscripten: + + chk_spelling: docker: - - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + - image: circleci/python:3.6 environment: TERM: xterm steps: - checkout - - restore_cache: - name: Restore Boost build - key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} + - attach_workspace: + at: build - run: - name: Bootstrap Boost + name: Install dependencies command: | - scripts/travis-emscripten/install_deps.sh + pip install --user codespell - run: - name: Build - command: | - scripts/travis-emscripten/build_emscripten.sh - - save_cache: - name: Save Boost build - key: *boost-cache-key - paths: - - boost_1_57_0 - - store_artifacts: - path: build/libsolc/soljson.js - destination: soljson.js - - run: mkdir -p workspace - - run: cp build/libsolc/soljson.js workspace/soljson.js - - run: scripts/get_version.sh > workspace/version.txt - - persist_to_workspace: - root: workspace - paths: - - soljson.js - - version.txt - test_emscripten_solcjs: + name: Check spelling + command: ~/.local/bin/codespell -S "*.enc,.git,Dockerfile*" -I ./scripts/codespell_whitelist.txt + + chk_docs_examples: docker: - - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + - image: circleci/node environment: TERM: xterm steps: - checkout - attach_workspace: - at: /tmp/workspace + at: build - run: - name: Install external tests deps - command: | - apt-get -qq update - apt-get -qy install netcat curl - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | NVM_DIR=/usr/local/nvm bash - export NVM_DIR="/usr/local/nvm" - [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm - nvm --version - nvm install 8 - node --version - npm --version + name: JS deps + command: sudo npm install -g solhint - run: - name: Test solcjs - command: | - . /usr/local/nvm/nvm.sh - test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) - test_emscripten_external: + name: Test Docs examples + command: ./test/docsCodeStyle.sh + + 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: trzeci/emscripten:sdk-tag-1.37.21-64bit + - image: circleci/node environment: TERM: xterm steps: - checkout - - attach_workspace: - at: /tmp/workspace - run: - name: Install external tests deps + name: JS deps command: | - apt-get -qq update - apt-get -qy install netcat curl - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | NVM_DIR=/usr/local/nvm bash - export NVM_DIR="/usr/local/nvm" - [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm - nvm --version - nvm install 8 - node --version - npm --version + npm install download + npm install JSONPath + npm install mktemp - run: - name: External tests - command: | - . /usr/local/nvm/nvm.sh - test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js - build_x86_linux: + name: Test buglist + command: ./test/buglistTests.js + + chk_proofs: docker: - - image: buildpack-deps:artful + - image: buildpack-deps:disco environment: TERM: xterm - CMAKE_OPTIONS: -DCOVERAGE=OFF steps: - checkout - run: - name: Install build dependencies + name: Z3 python deps 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 libz3-dev - ./scripts/install_obsolete_jsoncpp_1_7_4.sh - - run: *setup_prerelease_commit_hash + apt-get -qy install python-pip + pip install --user z3-solver + - run: *run_proofs + + b_ubu_clang: &build_ubuntu1904_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang + environment: + CC: clang + CXX: clang++ + steps: + - checkout - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - "*" + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables - build_x86_clang7: + b_ubu: &build_ubuntu1904 docker: - - image: buildpack-deps:cosmic + - image: ethereum/solidity-buildpack-deps:ubuntu1904 + steps: + - checkout + - run: *run_build + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables + + b_ubu_release: &build_ubuntu1904_release + <<: *build_ubuntu1904 + environment: + FORCE_RELEASE: ON + + b_ubu18: &build_ubuntu1804 + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1804 + environment: + CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 + CMAKE_BUILD_TYPE: RelWithDebugInfo + 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*' -- --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++20 mode and uses debug build in order to speed up. + # Do *NOT* store any artifacts or workspace as we don't run tests on this build. + b_ubu_cxx20: + <<: *build_ubuntu1904 + environment: + CMAKE_BUILD_TYPE: Debug + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF + steps: + - checkout + - run: *run_build + + b_ubu_ossfuzz: + <<: *build_ubuntu1904_clang environment: TERM: xterm - CC: /usr/bin/clang-7 - CXX: /usr/bin/clang++-7 + CC: clang + CXX: clang++ + CMAKE_OPTIONS: -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_clang 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 + - run: *gitter_notify_failure + - run: *gitter_notify_success + - store_test_results: *store_test_results + - store_artifacts: *artifacts_test_results + + b_archlinux: + docker: + - image: archlinux/base + environment: + TERM: xterm + steps: - 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 libz3-dev - ./scripts/install_obsolete_jsoncpp_1_7_4.sh - - run: *setup_prerelease_commit_hash + pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar + - checkout - run: *run_build - - store_artifacts: *solc_artifact - - persist_to_workspace: - root: build - paths: - - "*" + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables - build_x86_mac: + 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 update - brew upgrade 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 + - store_artifacts: *artifacts_solc + - persist_to_workspace: *artifacts_executables - test_check_spelling: - docker: - - image: circleci/python:3.6 + t_osx_cli: + macos: + xcode: "10.0.0" environment: TERM: xterm steps: @@ -197,136 +455,256 @@ jobs: - run: name: Install dependencies command: | - pip install --user codespell + 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: + TERM: xterm + steps: + - checkout + - restore_cache: + name: Restore Boost build + key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/build_emscripten.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} - run: - name: Check spelling - command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt + name: Bootstrap Boost + command: | + scripts/travis-emscripten/install_deps.sh + - run: + name: Build + command: | + scripts/travis-emscripten/build_emscripten.sh + - save_cache: + name: Save Boost build + key: *boost-cache-key + paths: + - boost_1_70_0_install + - store_artifacts: + path: emscripten_build/libsolc/soljson.js + destination: soljson.js + - run: mkdir -p workspace + - run: cp emscripten_build/libsolc/soljson.js workspace/soljson.js + - run: scripts/get_version.sh > workspace/version.txt + - persist_to_workspace: + root: workspace + paths: + - soljson.js + - version.txt + + # 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 - test_check_style: + b_docs: docker: - - image: buildpack-deps:artful + - image: ethereum/solidity-buildpack-deps:ubuntu1904 steps: - checkout + - run: *setup_prerelease_commit_hash - run: - name: Check for trailing whitespace - command: ./scripts/check_style.sh + name: Build documentation + command: ./scripts/docs.sh + - store_artifacts: + path: docs/_build/html/ + destination: docs-html - test_buglist: + t_ubu_soltest: &t_ubu_soltest + <<: *test_ubuntu1904 + + t_ubu_clang_soltest: &t_ubu_clang_soltest + <<: *test_ubuntu1904_clang + environment: + EVM: constantinople + OPTIMIZE: 0 + + t_ubu_release_soltest: &t_ubu_release_soltest + <<: *t_ubu_soltest + + t_ubu_cli: &t_ubu_cli docker: - - image: circleci/node + - 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_release_cli: &t_ubu_release_cli + <<: *t_ubu_cli + + 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: - name: JS deps - command: | - npm install download - npm install JSONPath - npm install mktemp - - run: - name: Test buglist - command: ./test/buglistTests.js + <<: *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 + flags: --no-smt + ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2 - test_x86_linux: + t_ems_solcjs: docker: - - image: buildpack-deps:artful + - image: circleci/node:10 environment: TERM: xterm steps: - checkout - attach_workspace: - at: build + at: /tmp/workspace - run: - name: Install dependencies + name: Test solcjs command: | - apt-get -qq update - apt-get -qy install libz3-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 + node --version + npm --version + test/solcjsTests.sh /tmp/workspace/soljson.js $(cat /tmp/workspace/version.txt) + + t_ems_external_gnosis: + docker: + - image: circleci/node:10 + environment: + TERM: xterm + steps: + - checkout + - attach_workspace: + at: /tmp/workspace - 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/ + name: External GnosisSafe tests + command: | + test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success - test_x86_mac: - macos: - xcode: "10.0.0" + t_ems_external_zeppelin: + docker: + - image: circleci/node:10 environment: TERM: xterm steps: - checkout - attach_workspace: - at: build + at: /tmp/workspace - run: - name: Install dependencies + name: External Zeppelin tests command: | - brew update - brew upgrade - 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/ + test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success - docs: + t_ems_external_colony: docker: - - image: buildpack-deps:artful + - image: circleci/node:10 + environment: + TERM: xterm steps: - checkout + - attach_workspace: + at: /tmp/workspace - run: - name: Install build dependencies + name: Install test dependencies command: | - apt-get -qq update - apt-get -qy install python-sphinx python-pip - - run: *setup_prerelease_commit_hash + sudo apt-get -qy install lsof - run: - name: Build documentation - command: ./scripts/docs.sh - - store_artifacts: - path: docs/_build/html/ - destination: docs-html + name: External ColonyNetworks tests + command: | + test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js + - run: *gitter_notify_failure + - run: *gitter_notify_success workflows: version: 2 - build_all: + + main: 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 - - test_emscripten_external: - <<: *build_on_tags - requires: - - build_emscripten - - build_x86_linux: *build_on_tags - - build_x86_clang7: *build_on_tags - - build_x86_mac: *build_on_tags - - test_x86_linux: - <<: *build_on_tags - requires: - - build_x86_linux - - test_x86_mac: - <<: *build_on_tags - requires: - - build_x86_mac - - docs: *build_on_tags + # basic checks + - chk_spelling: *workflow_trigger_on_tags + - chk_coding_style: *workflow_trigger_on_tags + - chk_docs_examples: *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_cxx20: *workflow_trigger_on_tags + - b_ubu_ossfuzz: *workflow_trigger_on_tags + + # OS/X build and tests + - b_osx: *workflow_trigger_on_tags + - t_osx_cli: *workflow_osx + + # Ubuntu build and tests + - b_ubu: *workflow_trigger_on_tags + - b_ubu18: *workflow_trigger_on_tags + - t_ubu_cli: *workflow_ubuntu1904 + - t_ubu_soltest: *workflow_ubuntu1904 + - b_ubu_clang: *workflow_trigger_on_tags + - t_ubu_clang_soltest: *workflow_ubuntu1904_clang + + # Ubuntu fake release build and tests + - b_ubu_release: *workflow_trigger_on_tags + - t_ubu_release_cli: *workflow_ubuntu1904_release + - t_ubu_release_soltest: *workflow_ubuntu1904_release + + # ASan build and tests + - b_ubu_asan: *workflow_trigger_on_tags + - 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 * * *" + filters: + branches: + only: + - develop + - develop_060 + + jobs: + # Emscripten builds and external tests + - b_ems: *workflow_trigger_on_tags + - t_ems_external_zeppelin: *workflow_emscripten + - t_ems_external_gnosis: *workflow_emscripten + - t_ems_external_colony: *workflow_emscripten + + # OSSFUZZ builds and (regression) tests + - b_ubu_ossfuzz: *workflow_trigger_on_tags + - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz + + # 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.clang.ubuntu1904 b/.circleci/docker/Dockerfile.clang.ubuntu1904 new file mode 100644 index 000000000..d21d26bcb --- /dev/null +++ b/.circleci/docker/Dockerfile.clang.ubuntu1904 @@ -0,0 +1,131 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 19.04 (Disco Dingo) Clang variant +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:disco AS base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build \ + clang++-8 llvm-8-dev \ + libjsoncpp-dev \ + libleveldb1d \ + ; \ + apt-get install -qy python-pip python-sphinx; \ + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \ + update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 1; \ + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 1; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +ENV CC clang +ENV CXX clang++ + +# Boost +RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git \ + /usr/src/boost; \ + cd /usr/src/boost; \ + ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ + ./b2 toolset=clang headers; \ + ./b2 toolset=clang variant=release \ + system regex filesystem unit_test_framework program_options \ + install -j $(($(nproc)/2)); \ + rm -rf /usr/src/boost + +# Z3 +RUN git clone --depth 1 -b Z3-4.8.5 https://github.com/Z3Prover/z3.git \ + /usr/src/z3; \ + cd /usr/src/z3; \ + python scripts/mk_make.py --prefix=/usr ; \ + cd build; \ + make -j; \ + make install; \ + rm -rf /usr/src/z3; + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip; \ + rm -rf /usr/src/libprotobuf-mutator + +# ETHASH +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ + cd ethash; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/ethash + +# INTX +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ + cd intx; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/intx; + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.1.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include \ No newline at end of file diff --git a/.circleci/docker/Dockerfile.ubuntu1804 b/.circleci/docker/Dockerfile.ubuntu1804 new file mode 100644 index 000000000..8d6e25289 --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1804 @@ -0,0 +1,117 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 18.04 (Bionic Beaver) +# URL: https://hub.docker.com/r/ethereum/solidity-buildpack-deps +# +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2019 solidity contributors. +#------------------------------------------------------------------------------ +FROM buildpack-deps:bionic AS base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build clang++-8 \ + libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + libjsoncpp-dev \ + llvm-8-dev libz3-static-dev \ + ; \ + apt-get install -qy python-pip python-sphinx; \ + update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \ + pip install codecov; \ + rm -rf /var/lib/apt/lists/* + +FROM base AS libraries + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip; \ + rm -rf /usr/src/libprotobuf-mutator + +# OSSFUZZ: libfuzzer +RUN set -ex; \ + cd /var/tmp; \ + svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \ + mkdir -p build-libfuzzer; \ + cd build-libfuzzer; \ + clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \ + ar r /usr/lib/libFuzzingEngine.a *.o; \ + rm -rf /var/lib/libfuzzer + +# ETHASH +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ + cd ethash; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/ethash + +# INTX +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ + cd intx; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/intx; + +# EVMONE +ARG EVMONE_HASH="f10d12c190f55a9d373e78b2dc0074d35d752c02cb536bb6fe754fb3719dd69e" +ARG EVMONE_MAJOR="0" +ARG EVMONE_MINOR="1" +ARG EVMONE_MICRO="0" +RUN set -ex; \ + EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \ + TGZFILE="evmone-$EVMONE_VERSION-linux-x86_64.tar.gz"; \ + wget https://github.com/ethereum/evmone/releases/download/v$EVMONE_VERSION/$TGZFILE; \ + sha256sum $TGZFILE; \ + tar xzpf $TGZFILE -C /usr; \ + rm -f $TGZFILE; + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/.circleci/docker/Dockerfile.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904 new file mode 100644 index 000000000..f356d0bde --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1904 @@ -0,0 +1,116 @@ +# 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 AS base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN set -ex; \ + dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \ + echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \ + apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \ + apt-get update; \ + apt-get install -qqy --no-install-recommends \ + build-essential \ + software-properties-common \ + cmake ninja-build clang++-8 libc++-8-dev libc++abi-8-dev \ + libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \ + libboost-program-options-dev \ + libjsoncpp-dev \ + llvm-8-dev libcvc4-dev libz3-static-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/* + +FROM base AS libraries + +# OSSFUZZ: libprotobuf-mutator +RUN set -ex; \ + git clone https://github.com/google/libprotobuf-mutator.git \ + /usr/src/libprotobuf-mutator; \ + cd /usr/src/libprotobuf-mutator; \ + git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + mkdir build; \ + cd build; \ + cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ + -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + cp -vpr external.protobuf/bin/* /usr/bin/; \ + cp -vpr external.protobuf/include/* /usr/include/; \ + cp -vpr external.protobuf/lib/* /usr/lib/; \ + ninja install/strip; \ + rm -rf /usr/src/libprotobuf-mutator + +# OSSFUZZ: libfuzzer +RUN set -ex; \ + cd /var/tmp; \ + svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \ + mkdir -p build-libfuzzer; \ + cd build-libfuzzer; \ + clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \ + ar r /usr/lib/libFuzzingEngine.a *.o; \ + rm -rf /var/lib/libfuzzer + +# ETHASH +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \ + cd ethash; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/ethash + +# INTX +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \ + cd intx; \ + mkdir build; \ + cd build; \ + cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/intx; + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.1.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +FROM base +COPY --from=libraries /usr/lib /usr/lib +COPY --from=libraries /usr/bin /usr/bin +COPY --from=libraries /usr/include /usr/include diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh new file mode 100755 index 000000000..41f08db77 --- /dev/null +++ b/.circleci/soltest.sh @@ -0,0 +1,63 @@ +#! /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:-.} +REPODIR="$(realpath $(dirname $0)/..)" + +source "${REPODIR}/scripts/common.sh" +# Test result output directory (CircleCI is reading test results from here) +mkdir -p test_results + +# in case we run with ASAN enabled, we must increase stck size. +ulimit -s 16384 + +get_logfile_basename() { + local filename="${EVM}" + test "${OPTIMIZE}" = "1" && filename="${filename}_opt" + test "${ABI_ENCODER_V2}" = "1" && filename="${filename}_abiv2" + + echo -ne "${filename}" +} + +BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/`get_logfile_basename`.xml" +SOLTEST_ARGS="--evm-version=$EVM --evmonepath /usr/lib/libevmone.so $flags" +test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize" +test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul" + +echo "Running ${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS}" + +${REPODIR}/build/test/soltest ${BOOST_TEST_ARGS} -- ${SOLTEST_ARGS} diff --git a/.circleci/soltest_all.sh b/.circleci/soltest_all.sh new file mode 100755 index 000000000..7b4564ee7 --- /dev/null +++ b/.circleci/soltest_all.sh @@ -0,0 +1,37 @@ +#! /bin/bash +#------------------------------------------------------------------------------ +# Bash script to execute the Solidity tests by CircleCI. +# +# The documentation for solidity is hosted at: +# +# https://solidity.readthedocs.org +# +# ------------------------------------------------------------------------------ +# This file is part of solidity. +# +# solidity is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# solidity is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with solidity. If not, see +# +# (c) 2016-2019 solidity contributors. +# ------------------------------------------------------------------------------ +set -e + +REPODIR="$(realpath $(dirname $0)/..)" + +for OPTIMIZE in 0 1; do + for EVM in homestead byzantium constantinople petersburg; do + EVM=$EVM OPTIMIZE=$OPTIMIZE ${REPODIR}/.circleci/soltest.sh + done +done + +EVM=constantinople OPTIMIZE=1 ABI_ENCODER_V2=1 ${REPODIR}/.circleci/soltest.sh \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..f9953c17b --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +# Formatting approximately used in Solidity's C++ +# +# See https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# For an online formatter to test settings, see +# https://zed0.co.uk/clang-format-configurator/ +# Note that clang-format cannot express the style that closing parentheses +# behave similar to closing curly braces in a multi-line setting in that +# they have to be on a line of their own at the same indentation level +# as the opening part. + +Language: Cpp +BasedOnStyle: LLVM +AlignEscapedNewlinesLeft: true +AlwaysBreakAfterReturnType: None +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Allman +ColumnLimit: 120 +ContinuationIndentWidth: 4 +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 +PenaltyBreakBeforeFirstCallParameter: 2000 +SpaceAfterCStyleCast: true +SpaceBeforeParens: ControlStatements +TabWidth: 4 +UseTab: ForIndentation + +# Local Variables: +# mode: yaml +# End: diff --git a/.editorconfig b/.editorconfig index 7b8a7be9f..d80fb3570 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,3 +17,7 @@ indent_size = 4 [std/**.sol] indent_style = space indent_size = 4 + +[*.{txt,cmake}] +indent_style = tab +indent_size = 4 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c3caf86e3..0e20fb7a7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -21,6 +21,7 @@ about: Bug reports about the Solidity Compiler. ## Environment - Compiler version: +- Target EVM version (as per compiler settings): - Framework/IDE (e.g. Truffle or Remix): - EVM execution environment / backend / blockchain client: - Operating system: @@ -32,8 +33,8 @@ Please provide a *minimal* source code example to trigger the bug you have found Please also mention any command line flags that are necessary for triggering the bug. Provide as much information as necessary to reproduce the bug. -``` +```solidity // Some *minimal* Solidity source code to reproduce the bug. // ... ``` ---> \ No newline at end of file +--> diff --git a/.gitignore b/.gitignore index e0f010d34..bbdf9d68d 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ prerelease.txt # Build directory build/ +build*/ +emscripten_build/ docs/_build docs/utils/__pycache__ docs/utils/*.pyc @@ -43,6 +45,9 @@ deps/cache [._]*.sw[a-p] [._]sw[a-p] +# emacs stuff +*~ + # IDE files .idea .vscode @@ -52,3 +57,6 @@ CMakeLists.txt.user /.vs /.cproject /.project + +# place to put local temporary files +tmp diff --git a/.travis.yml b/.travis.yml index ef4f8b398..5754fa0a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,16 @@ matrix: env: - ZIP_SUFFIX=ubuntu-trusty - SOLC_STOREBYTECODE=On + before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo add-apt-repository -y ppa:mhier/libboost-latest + - sudo apt-get update -qq + install: + - sudo apt-get install -qq g++-8 gcc-8 + - sudo apt-get install -qq libboost1.67-dev + - sudo apt-get install -qq libleveldb1 + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 90 - os: linux dist: trusty @@ -63,6 +73,16 @@ matrix: env: - ZIP_SUFFIX=ubuntu-trusty-clang - SOLC_STOREBYTECODE=On + before_install: + - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + - sudo add-apt-repository -y ppa:mhier/libboost-latest + - sudo apt-get update -qq + install: + - sudo apt-get install -qq g++-8 gcc-8 + - sudo apt-get install -qq libboost1.67-dev + - sudo apt-get install -qq libleveldb1 + - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 + - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 90 # Docker target, which generates a statically linked alpine image - os: linux @@ -90,7 +110,7 @@ matrix: before_install: - nvm install 8 - nvm use 8 - - docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit + - docker pull trzeci/emscripten:sdk-tag-1.38.22-64bit env: - SOLC_EMSCRIPTEN=On - SOLC_INSTALL_DEPS_TRAVIS=Off @@ -98,6 +118,16 @@ matrix: - SOLC_TESTS=Off - ZIP_SUFFIX=emscripten - SOLC_STOREBYTECODE=On + # Travis doesn't seem to support "dynamic" cache keys where we could include + # the hashes of certain files. Our CircleCI configuration contains the hash of + # relevant emscripten files. + # + # It is important to invalidate the cache with each emscripten update, because + # dependencies, such as boost, might be broken otherwise. + # + # This key here has no significant on anything, apart from caching. Please keep + # it in sync with the version above. + - EMSCRIPTEN_VERSION_KEY="1.38.22" # OS X Mavericks (10.9) # https://en.wikipedia.org/wiki/OS_X_Mavericks @@ -153,24 +183,21 @@ git: cache: ccache: true directories: - - boost_1_57_0 + - boost_1_70_0_install - $HOME/.local install: - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) - - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) + - test "$TRAVIS_OS_NAME" != "linux" || (sudo scripts/install_cmake.sh) + +before_script: # Disable tests unless run on the release branch, on tags or with daily cron - #- if [ "$TRAVIS_BRANCH" != release -a -z "$TRAVIS_TAG" -a "$TRAVIS_EVENT_TYPE" != cron ]; then SOLC_TESTS=Off; fi - - SOLC_TESTS=Off + - if [ "$TRAVIS_BRANCH" != release -a -z "$TRAVIS_TAG" -a "$TRAVIS_EVENT_TYPE" != cron ]; then SOLC_TESTS=Off; fi - if [ "$TRAVIS_BRANCH" = release -o -n "$TRAVIS_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi - echo -n "$TRAVIS_COMMIT" > commit_hash.txt - -before_script: - test $SOLC_EMSCRIPTEN != On || (scripts/build_emscripten.sh) - test $SOLC_DOCKER != On || (scripts/docker_build.sh) - - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE - && scripts/release.sh $ZIP_SUFFIX - && scripts/create_source_tarball.sh) + - test $SOLC_RELEASE != On || (scripts/build.sh $SOLC_BUILD_TYPE -DBoost_USE_STATIC_LIBS=OFF && scripts/create_source_tarball.sh) script: - test $SOLC_EMSCRIPTEN != On -o $SOLC_TESTS != On || (scripts/test_emscripten.sh) @@ -204,7 +231,7 @@ deploy: - release - /^v\d/ # This is the deploy target for the native build (Linux and macOS) - # which generates ZIPs per commit and the source tarball. + # which generates the source tarball. # # This runs for each tag that is created and adds the corresponding files. - provider: releases diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c56125a1..38cd30370 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,20 +1,29 @@ -cmake_minimum_required(VERSION 3.0.0) +cmake_minimum_required(VERSION 3.9.0) set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) +include(EthToolchains) + # Set cmake_policies include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 set(LITY_PROJECT_VERSION "1.2.8") -set(SOLC_PROJECT_VERSION "0.5.0") -project(lity VERSION ${LITY_PROJECT_VERSION}) - +set(SOLC_PROJECT_VERSION "0.5.12") +project(lity VERSION ${LITY_PROJECT_VERSION} LANGUAGES C CXX) option(LITYC_LINK_STATIC "Link lityc executable statically on supported platforms" OFF) + +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(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) -option(INSTALL_LLLC "Include lllc executable in installation" OFF) +option(INSTALL_LLLC "Include lllc executable in installation" ${LLL}) # Setup cccache. include(EthCcache) @@ -45,14 +54,18 @@ include(EthOptions) configure_project(TESTS) add_subdirectory(libdevcore) +add_subdirectory(liblangutil) add_subdirectory(libevmasm) +add_subdirectory(libyul) add_subdirectory(libsolidity) add_subdirectory(liblityc) if (NOT EMSCRIPTEN) add_subdirectory(lityc) - add_subdirectory(liblll) - add_subdirectory(lllc) + if (LLL) + add_subdirectory(liblll) + add_subdirectory(lllc) + endif() endif() if (TESTS AND NOT EMSCRIPTEN) diff --git a/CODING_STYLE.md b/CODING_STYLE.md index a0fe98644..3f44cd1f9 100644 --- a/CODING_STYLE.md +++ b/CODING_STYLE.md @@ -46,6 +46,8 @@ foo->bar(someLongVariableName, cout << "some very long string that contains completely irrelevant text that talks about this and that and contains the words \"lorem\" and \"ipsum\"" << endl; ``` +To set indentation and tab width settings uniformly, the repository contains an [EditorConfig](https://editorconfig.org/) [`.editorconfig`](https://github.com/ethereum/solidity/blob/develop/.editorconfig) file, which describes some of the styles used and which is recognized by many IDE's and editors. + ## 1. Namespaces 1. No `using namespace` declarations in header files. diff --git a/Changelog.md b/Changelog.md index 4655e5939..5927efb5d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -128,6 +128,396 @@ Language Features: * Assembly: Add ENI opcode for Ethereum Native Interface. * Code Generator: Use ENIHandler to encode ENI function parameters to memory section. * ERC Checker: Use ``--contract-standard`` to check whether contract's interface conforms with given standard and list missing functions for those standard. +### 0.5.12 (2019-10-01) + +Language Features: + * Type Checker: Allow assignment to external function arguments except for reference types. + + +Compiler Features: + * ABI Output: Change sorting order of functions from selector to kind, name. + * Optimizer: Add rule that replaces the BYTE opcode by 0 if the first argument is larger than 31. + * SMTChecker: Add loop support to the CHC engine. + * Yul Optimizer: Take side-effect-freeness of user-defined functions into account. + * Yul Optimizer: Remove redundant mload/sload operations. + + +Bugfixes: + * Code Generator: Fix internal error when popping a dynamic storage array of mappings. + * Name Resolver: Fix wrong source location when warning on shadowed aliases in import declarations. + * Scanner: Fix multi-line natspec comment parsing with triple slashes when file is encoded with CRLF instead of LF. + * Type System: Fix arrays of recursive structs. + * Yul Optimizer: Fix reordering bug in connection with shifted one and mul/div-instructions in for loop conditions. + + +### 0.5.11 (2019-08-12) + + +Language Features: + * Inline Assembly: Support direct constants of value type in inline assembly. + +Compiler Features: + * ABI: Additional internal type info in the field ``internalType``. + * eWasm: Highly experimental eWasm output using ``--ewasm`` in the commandline interface or output selection of ``ewasm.wast`` in standard-json. + * Metadata: Update the swarm hash to the current specification, changes ``bzzr0`` to ``bzzr1`` and urls to use ``bzz-raw://``. + * Standard JSON Interface: Compile only selected sources and contracts. + * Standard JSON Interface: Provide secondary error locations (e.g. the source position of other conflicting declarations). + * SMTChecker: Do not erase knowledge about storage pointers if another storage pointer is assigned. + * SMTChecker: Support string literal type. + * SMTChecker: New Horn-based algorithm that proves assertions via multi-transaction contract invariants. + * Standard JSON Interface: Provide AST even on errors if ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` is true. + * Yul Optimizer: Do not inline function if it would result in expressions being duplicated that are not cheap. + + +Bugfixes: + * ABI decoder: Ensure that decoded arrays always point to distinct memory locations. + * Code Generator: Treat dynamically encoded but statically sized arrays and structs in calldata properly. + * SMTChecker: Fix internal error when inlining functions that contain tuple expressions. + * SMTChecker: Fix pointer knowledge erasing in loops. + * SMTChecker: Fix internal error when using compound bitwise assignment operators inside branches. + * SMTChecker: Fix internal error when inlining a function that returns a tuple containing an unsupported type inside a branch. + * SMTChecker: Fix internal error when inlining functions that use state variables and belong to a different source. + * SMTChecker: Fix internal error when reporting counterexamples concerning state variables from different source files. + * SMTChecker: Fix SMT sort mismatch when using string literals. + * View/Pure Checker: Properly detect state variable access through base class. + * Yul Analyzer: Check availability of data objects already in analysis phase. + * Yul Optimizer: Fix an issue where memory-accessing code was removed even though ``msize`` was used in the program. + + +### 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 or StandardCompiler `settings.parserErrorRecovery` boolean. + * 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: + * Inline Assembly: Revert change introduced in 0.5.7: The ``callvalue()`` instruction does not require ``payable`` anymore. + * Static Analyzer: Disallow libraries calling themselves externally. + + +Compiler Features: + * Assembler: Encode the compiler version in the deployed bytecode. + * Code Generator: Fix handling of structs of dynamic size as constructor parameters. + * Inline Assembly: Disallow the combination of ``msize()`` and the Yul optimizer. + * Metadata: Add IPFS hashes of source files. + * Optimizer: Add rule to simplify SHL/SHR combinations. + * Optimizer: Add rules for multiplication and division by left-shifted one. + * SMTChecker: Support inherited state variables. + * SMTChecker: Support tuples and function calls with multiple return values. + * SMTChecker: Support ``delete``. + * SMTChecker: Inline external function calls to ``this``. + * Yul Optimizer: Simplify single-run ``for`` loops to ``if`` statements. + * Yul Optimizer: Optimize representation of numbers. + * 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. + * Compiler Internals: Reset the Yul string repository before each compilation, freeing up memory. + * SMTChecker: Fix bad cast in base constructor modifier. + * SMTChecker: Fix internal error when visiting state variable inherited from base class. + * SMTChecker: Fix internal error in fixed point operations. + * SMTChecker: Fix internal error in assignment to unsupported type. + * SMTChecker: Fix internal error in branching when inlining function calls that modify local variables. + + +### 0.5.8 (2019-04-30) + +Important Bugfixes: + * Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context. + * Yul Optimizer: Fix SSA transform for multi-assignments. + + +Language Features: + * ABIEncoderV2: Implement encoding of calldata arrays and structs. + * Code Generation: Implement copying recursive structs from storage to memory. + * Yul: Disallow function definitions inside for-loop init blocks. + + +Compiler Features: + * ABI Decoder: Raise a runtime error on dirty inputs when using the experimental decoder. + * Optimizer: Add rule for shifts by constants larger than 255 for Constantinople. + * Optimizer: Add rule to simplify certain ANDs and SHL combinations + * SMTChecker: Support arithmetic compound assignment operators. + * SMTChecker: Support unary increment and decrement for array and mapping access. + * SMTChecker: Show unsupported warning for inline assembly blocks. + * SMTChecker: Support mod. + * SMTChecker: Support ``contract`` type. + * SMTChecker: Support ``this`` as address. + * SMTChecker: Support address members. + * Standard JSON Interface: Metadata settings now re-produce the original ``"useLiteralContent"`` setting from the compilation input. + * 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: + * SMTChecker: Implement Boolean short-circuiting. + * SMTChecker: SSA control-flow did not take into account state variables that were modified inside inlined functions that were called inside branches. + * Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation. + * Type System: Allow direct call to base class functions that have overloads. + * Type System: Warn about shadowing builtin variables if user variables are named ``this`` or ``super``. + * Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope. + + +Build System: + * Soltest: Add commandline option `--test` / `-t` to isoltest which takes a string that allows filtering unit tests. + * soltest.sh: allow environment variable ``SOLIDITY_BUILD_DIR`` to specify build folder and add ``--help`` usage. + + +### 0.5.7 (2019-03-26) + +Important Bugfixes: + * ABIEncoderV2: Fix bugs related to loading short value types from storage when encoding an array or struct from storage. + * ABIEncoderV2: Fix buffer overflow problem when encoding packed array from storage. + * Optimizer: Fix wrong ordering of arguments in byte optimization rule for constants. + + +Language Features: + * Function calls with named arguments now work with overloaded functions. + + +Compiler Features: + * Inline Assembly: Issue error when using ``callvalue()`` inside nonpayable function (in the same way that ``msg.value`` already does). + * Standard JSON Interface: Support "Yul" as input language. + * SMTChecker: Show callstack together with model if applicable. + * SMTChecker: Support modifiers. + * Yul Optimizer: Enable stack allocation optimization by default if Yul optimizer is active (disable in ``yulDetails``). + + +Bugfixes: + * Code Generator: Defensively pad memory for ``type(Contract).name`` to multiples of 32. + * Type System: Detect and disallow internal function pointers as parameters for public/external library functions, even when they are nested/wrapped in structs, arrays or other types. + * Yul Optimizer: Properly determine whether a variable can be eliminated during stack compression pass. + * Yul / Inline Assembly Parser: Disallow more than one case statement with the same label inside a switch based on the label's integer value. + + +Build System: + * Install scripts: Fix boost repository URL for CentOS 6. + * Soltest: Fix hex string update in soltest. + + +### 0.5.6 (2019-03-13) + +Important Bugfixes: + * Yul Optimizer: Fix visitation order bug for the structural simplifier. + * Optimizer: Fix overflow in optimization rule that simplifies double shift by constant. + +Language Features: + * Allow calldata arrays with dynamically encoded base types with ABIEncoderV2. + * Allow dynamically encoded calldata structs with ABIEncoderV2. + + +Compiler Features: + * Optimizer: Add rules for ``lt``-comparisons with constants. + * Peephole Optimizer: Remove double ``iszero`` before ``jumpi``. + * SMTChecker: Support enums without typecast. + * SMTChecker: Support one-dimensional arrays. + * Type Checker: Provide better error messages for some literal conversions. + * Yul Optimizer: Add rule to remove empty default switch cases. + * Yul Optimizer: Add rule to remove empty cases if no default exists. + * Yul Optimizer: Add rule to replace a switch with no cases with ``pop(expression)``. + + +Bugfixes: + * JSON ABI: Json description of library ABIs no longer contains functions with internal types like storage structs. + * SMTChecker: Fix internal compiler error when contract contains too large rational number. + * Type system: Detect if a contract's base uses types that require the experimental abi encoder while the contract still uses the old encoder. + + +Build System: + * Soltest: Add support for arrays in function signatures. + * Soltest: Add support for struct arrays in function signatures. + * Soltest: Add support for left-aligned, unpadded hex string literals. + +### 0.5.5 (2019-03-05) + +Language Features: + * Add support for getters of mappings with ``string`` or ``bytes`` key types. + * Meta programming: Provide access to the name of contracts via ``type(C).name``. + + +Compiler Features: + * Support ``petersburg`` as ``evmVersion`` and set as default. + * Commandline Interface: Option to activate the experimental yul optimizer using ``-optimize-yul``. + * Inline Assembly: Consider ``extcodehash`` as part of Constantinople. + * Inline Assembly: Instructions unavailable to the currently configured EVM are errors now. + * SMTChecker: Do not report underflow/overflow if they always revert. This removes false positives when using ``SafeMath``. + * Standard JSON Interface: Allow retrieving metadata without triggering bytecode generation. + * Standard JSON Interface: Provide fine-grained control over the optimizer via the settings. + * Static Analyzer: Warn about expressions with custom types when they have no effect. + * Optimizer: Add new rules with constants including ``LT``, ``GT``, ``AND`` and ``BYTE``. + * Optimizer: Add rule for shifts with constants for Constantinople. + * Optimizer: Combine multiple shifts with constant shift-by values into one. + * Optimizer: Do not mask with 160-bits after ``CREATE`` and ``CREATE2`` as they are guaranteed to return an address or 0. + * Optimizer: Support shifts in the constant optimiser for Constantinople. + * Yul Optimizer: Add rule to replace switch statements with literals by matching case body. + + +Bugfixes: + * ABIEncoderV2: Fix internal error related to bare delegatecall. + * ABIEncoderV2: Fix internal error related to ecrecover. + * ABIEncoderV2: Fix internal error related to mappings as library parameters. + * ABIEncoderV2: Fix invalid signature for events containing structs emitted in libraries. + * Inline Assembly: Proper error message for missing variables. + * Optimizer: Fix internal error related to unused tag removal across assemblies. This never generated any invalid code. + * SMTChecker: Fix crash related to statically-sized arrays. + * TypeChecker: Fix internal error and disallow index access on contracts and libraries. + * Yul: Properly detect name clashes with functions before their declaration. + * Yul: Take built-in functions into account in the compilability checker. + * Yul Optimizer: Properly take reassignments to variables in sub-expressions into account when replacing in the ExpressionSimplifier. + + +Build System: + * Soltest: Add support for left-aligned, padded hex literals. + * Soltest: Add support for right-aligned, padded boolean literals. + +### 0.5.4 (2019-02-12) + +Language Features: + * Allow calldata structs without dynamically encoded members with ABIEncoderV2. + + +Compiler Features: + * ABIEncoderV2: Implement packed encoding. + * C API (``libsolc`` / raw ``soljson.js``): Introduce ``solidity_free`` method which releases all internal buffers to save memory. + * Commandline Interface: Adds new option ``--new-reporter`` for improved diagnostics formatting + along with ``--color`` and ``--no-color`` for colorized output to be forced (or explicitly disabled). + + +Bugfixes: + * Code Generator: Defensively pad allocation of creationCode and runtimeCode to multiples of 32 bytes. + * Commandline Interface: Allow yul optimizer only for strict assembly. + * Parser: Disallow empty import statements. + * Type Checker: Disallow mappings with data locations other than ``storage``. + * Type Checker: Fix internal error when a struct array index does not fit into a uint256. + * Type System: Properly report packed encoded size for arrays and structs (mostly unused until now). + + +Build System: + * Add support for continuous fuzzing via Google oss-fuzz + * SMT: If using Z3, require version 4.6.0 or newer. + * Soltest: Add parser that is used in the file-based unit test environment. + * Ubuntu PPA Packages: Use CVC4 as SMT solver instead of Z3 + + +### 0.5.3 (2019-01-22) + +Language Features: + * Provide access to creation and runtime code of contracts via ``type(C).creationCode`` / ``type(C).runtimeCode``. + + +Compiler Features: + * Control Flow Graph: Warn about unreachable code. + * SMTChecker: Support basic typecasts without truncation. + * SMTChecker: Support external function calls and erase all knowledge regarding storage variables and references. + + +Bugfixes: + * Emscripten: Split simplification rule initialization up further to work around issues with soljson.js in some browsers. + * Type Checker: Disallow calldata structs until implemented. + * Type Checker: Return type error if fixed point encoding is attempted instead of throwing ``UnimplementedFeatureError``. + * Yul: Check that arguments to ``dataoffset`` and ``datasize`` are literals at parse time and properly take this into account in the optimizer. + * Yul: Parse number literals for detecting duplicate switch cases. + * Yul: Require switch cases to have the same type. + + +Build System: + * Emscripten: Upgrade to emscripten 1.38.8 on travis and circleci. + + +### 0.5.2 (2018-12-19) + +Language Features: + * Control Flow Graph: Detect every access to uninitialized storage pointers. + + +Compiler Features: + * Inline Assembly: Improve error messages around invalid function argument count. + * Code Generator: Only check callvalue once if all functions are non-payable. + * Code Generator: Use codecopy for string constants more aggressively. + * Code Generator: Use binary search for dispatch function if more efficient. The size/speed tradeoff can be tuned using ``--optimize-runs``. + * SMTChecker: Support mathematical and cryptographic functions in an uninterpreted way. + * SMTChecker: Support one-dimensional mappings. + * Standard JSON Interface: Disallow unknown keys in standard JSON input. + * Standard JSON Interface: Only run code generation if it has been requested. This could lead to unsupported feature errors only being reported at the point where you request bytecode. + * Static Analyzer: Do not warn about unused variables or state mutability for functions with an empty body. + * Type Checker: Add an additional reason to be displayed when type conversion fails. + * Yul: Support object access via ``datasize``, ``dataoffset`` and ``datacopy`` in standalone assembly mode. + + +Bugfixes: + * Standard JSON Interface: Report specific error message for json input errors instead of internal compiler error. + + +Build System: + * Replace the trusty PPA build by a static build on cosmic that is used for the trusty package instead. + * Remove support for Visual Studio 2015. + + +### 0.5.1 (2018-12-03) + +Language Features: + * Allow mapping type for parameters and return variables of public and external library functions. + * Allow public functions to override external functions. + +Compiler Features: + * Code generator: Do not perform redundant double cleanup on unsigned integers when loading from calldata. + * Commandline interface: Experimental ``--optimize`` option for assembly mode (``--strict-assembly`` and ``--yul``). + * SMTChecker: SMTLib2 queries and responses passed via standard JSON compiler interface. + * SMTChecker: Support ``msg``, ``tx`` and ``block`` member variables. + * SMTChecker: Support ``gasleft()`` and ``blockhash()`` functions. + * SMTChecker: Support internal bound function calls. + * Yul: Support Yul objects in ``--assemble``, ``--strict-assembly`` and ``--yul`` commandline options. + +Bugfixes: + * Assembly output: Do not mix in/out jump annotations with arguments. + * Commandline interface: Fix crash when using ``--ast`` on empty runtime code. + * Code Generator: Annotate jump from calldata decoder to function as "jump in". + * Code Generator: Fix internal error related to state variables of function type access via base contract name. + * Optimizer: Fix nondeterminism bug related to the boost version and constants representation. The bug only resulted in less optimal but still correct code because the generated routine is always verified to be correct. + * Type Checker: Properly detect different return types when overriding an external interface function with a public contract function. + * Type Checker: Disallow struct return types for getters of public state variables unless the new ABI encoder is active. + * Type Checker: Fix internal compiler error when a field of a struct used as a parameter in a function type has a non-existent type. + * Type Checker: Disallow functions ``sha3`` and ``suicide`` also without a function call. + * Type Checker: Fix internal compiler error with ``super`` when base contract function is not implemented. + * Type Checker: Fixed internal error when trying to create abstract contract in some cases. + * Type Checker: Fixed internal error related to double declaration of events. + * Type Checker: Disallow inline arrays of mapping type. + * Type Checker: Consider abstract function to be implemented by public state variable. + +Build System: + * CMake: LLL is not built anymore by default. Must configure it with CMake as `-DLLL=ON`. + * Docker: Includes both Scratch and Alpine images. + * Emscripten: Upgrade to Emscripten SDK 1.37.21 and boost 1.67. + +Solc-Js: + * Fix handling of standard-json in the commandline executable. + * Remove support of nodejs 4. ### 0.5.0 (2018-11-13) @@ -268,6 +658,20 @@ Bugfixes: * Parser: Fix incorrect source location for nameless parameters. * Command Line Interface: Fix internal error when compiling stdin with no content and --ast option. + +### 0.4.26 (2019-04-29) + +Important Bugfixes: + * Code Generator: Fix initialization routine of uninitialized internal function pointers in constructor context. + * Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation. + +Bugfixes: + * ABIEncoderV2: Refuse to generate code that is known to be potentially buggy. + * General: Split rule list such that JavaScript environments with small stacks can use the compiler. + +Note: The above changes are not included in 0.5.0, because they were backported. + + ### 0.4.25 (2018-09-12) Important Bugfixes: diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index b84ae4c4d..b6422654e 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -1,20 +1,51 @@ -Checklist for making a release: +## Checklist for making a release: +### Requirements + - [ ] Lauchpad (Ubuntu One) account + - [ ] gnupg key (has to be version 1, gpg2 won't work) for `your-name@ethereum.org` created and uploaded + - [ ] Readthedocs account, access to the Solidity project + - [ ] Write access to https://github.com/ethereum/homebrew-ethereum + +### Pre-release - [ ] Ensure that a Github project exists for the release. - [ ] Check that all issues and pull requests from the Github project to be released are merged to ``develop``. - - [ ] Create a commit in ``develop`` that updates the ``Changelog`` to include a release date (run ``./scripts/tests.sh`` to update the bug list). Sort the changelog entries alphabetically and correct any errors you notice. + +### Changelog + - [ ] Sort the changelog entries alphabetically and correct any errors you notice. + - [ ] Create a commit on a new branch that updates the ``Changelog`` to include a release date. + - [ ] Run ``./scripts/tests.sh`` to update the bug list. - [ ] Create a pull request and wait for the tests, merge it. - - [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it. - - [ ] Make a final check that there are no platform-dependency issues in the ``solc-test-bytecode`` repository. - - [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag. + +### Create the Release + - [ ] Create Github release page: https://github.com/ethereum/solidity/releases/new + - [ ] On the release page, select the ``release`` branch as new target and set tag to the new version (e.g. `v0.5.4`) (make sure you only `SAVE DRAFT` instead of `PUBLISH RELEASE` before the actual release) - [ ] Thank voluntary contributors in the Github release page (use ``git shortlog -s -n -e origin/release..origin/develop``). - - [ ] Wait for the CI runs on the tag itself (they should push artifacts onto the Github release page). + - [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it. + - [ ] Make a final check that there are no platform-dependency issues in the ``solidity-test-bytecode`` repository. + - [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag (click the `PUBLISH RELEASE` button on the release page.) + - [ ] Wait for the CI runs on the tag itself (travis and appveyor should push artifacts onto the Github release page). + - [ ] Run ``scripts/create_source_tarball.sh`` while being on the tag to create the source tarball. Make sure to create ``prerelease.txt`` before: (``echo -n > prerelease.txt``). This will create the tarball in a directory called ``upload``. + - [ ] Take the tarball from the upload directory (its name should be ``solidity_x.x.x.tar.gz``, otherwise ``prerelease.txt`` was missing in the step before) and upload the source tarball to the release page. + +### PPA + - [ ] Change ``scripts/release_ppa.sh`` to match your key's email and key id. - [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key). - - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems, run ``./scripts/docker_deploy_manual.sh release``). - - [ ] Update the homebrew realease in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb (version and hash) - - [ ] Update the default version on readthedocs. - - [ ] Make a release of ``solc-js``: Increment the version number, create a pull request for that, merge it after tests succeeded. + - [ ] Wait for the ``~ethereum/ubuntu/ethereum-static`` PPA build to be finished and published for *all platforms*. SERIOUSLY: DO NOT PROCEED EARLIER!!! *After* the static builds are *published*, copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty`` and ``Xenial`` while selecting ``Copy existing binaries``. + - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems, run ``./scripts/docker_deploy_manual.sh v0.x.x``). + +### Homebrew + - [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb + +### Documentation + - [ ] Build the new version on https://readthedocs.org/projects/solidity/ (select `latest` on the bottom of the page and click `BUILD`) + - [ ] In the admin panel, select `Versions` in the menu and set the default version to the released one. + +### Release solc-js + - [ ] Increment the version number, create a pull request for that, merge it after tests succeeded. - [ ] Run ``npm publish`` in the updated ``solc-js`` repository. + - [ ] Make sure to push the tag ``npm publish`` created with ``git push --tags``. + +### Post-release - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. - [ ] Merge ``release`` back into ``develop``. - [ ] Announce on Twitter and Reddit. diff --git a/appveyor.yml b/appveyor.yml index 91ea8af77..2e2a2fa8b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,6 @@ # # http://solidity.readthedocs.org # -# TODO - Tests currently disabled, because Tests-over-IPC code is using UNIX -# sockets unconditionally at the time of writing. -# # ------------------------------------------------------------------------------ # This file is part of solidity. # @@ -32,10 +29,9 @@ branches: - release - develop configuration: - - RelWithDebInfo + - Release environment: matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 # This is used for pushing to solidity-test-bytecodes priv_key: @@ -63,17 +59,16 @@ install: before_build: - if not exist build mkdir build - cd build - - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" ( cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On ) - else ( cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On ) + - cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On build_script: - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - cd %APPVEYOR_BUILD_FOLDER% - - if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" ( scripts\release.bat %CONFIGURATION% 2017 ) + - scripts\release.bat %CONFIGURATION% 2017 - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short test_script: - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - - soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-ipc --no-smt + - soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt # Skip bytecode compare if private key is not available - cd %APPVEYOR_BUILD_FOLDER% - ps: if ($env:priv_key) { diff --git a/cmake/EthBuildInfo.cmake b/cmake/EthBuildInfo.cmake index 94bd01319..b7dbc72f5 100644 --- a/cmake/EthBuildInfo.cmake +++ b/cmake/EthBuildInfo.cmake @@ -38,6 +38,10 @@ function(create_build_info NAME) -DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}" -DSOLC_PROJECT_VERSION="${SOLC_PROJECT_VERSION}" -DLITY_PROJECT_VERSION="${LITY_PROJECT_VERSION}" + -DPROJECT_VERSION="${LITY_PROJECT_VERSION}" + -DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}" + -DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}" + -DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}" -P "${ETH_SCRIPTS_DIR}/buildinfo.cmake" ) include_directories("${PROJECT_BINARY_DIR}/include") diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index b4cc6656f..7ffb45964 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -24,10 +24,6 @@ endif() eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough) if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - - # Use ISO C++11 standard language. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - # Enables all the warnings about constructions that some users consider questionable, # and that are easy to avoid. Also enable some extra warning flags that are not # enabled by -Wall. Finally, treat at warnings-as-errors, which forces developers @@ -37,19 +33,19 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-Werror) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g3") # 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. @@ -58,6 +54,12 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Set stack size to 32MB - by default Apple's clang defines a stack size of 8MB. # Normally 16MB is enough to run all tests, but it will exceed the stack, if -DSANITIZE=address is used. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size -Wl,0x2000000") + + # Boost libraries use visibility=hidden to reduce unnecessary DWARF entries. + # Unless we match visibility, ld will give a warning message like: + # ld: warning: direct access in function 'boost::filesystem... from file ... + # means the weak symbol cannot be overridden at runtime. This was likely caused by different translation units being compiled with different visibility settings. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") endif() # Some Linux-specific Clang settings. We don't want these for OS X. @@ -78,10 +80,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # into errors, which makes sense. # http://stackoverflow.com/questions/21617158/how-to-silence-unused-command-line-argument-error-with-clang-without-disabling-i add_compile_options(-Qunused-arguments) - endif() - - if (EMSCRIPTEN) - # Do not emit a separate memory initialiser file + elseif(EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --memory-init-file 0") # Leave only exported symbols as public and aggressively remove others set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections -fvisibility=hidden") @@ -104,7 +103,13 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Abort if linking results in any undefined symbols # Note: this is on by default in the CMake Emscripten module which we aren't using set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") - add_definitions(-DETH_EMSCRIPTEN=1) + # Disallow deprecated emscripten build options. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s STRICT=1") + # Export the Emscripten-generated auxiliary methods which are needed by solc-js. + # Which methods of libsolc itself are exported is specified in libsolc/CMakeLists.txt. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap','addFunction','removeFunction','Pointer_stringify','lengthBytesUTF8','_malloc','stringToUTF8','setValue']") + # Do not build as a WebAssembly target - we need an asm.js output. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=0") endif() endif() @@ -124,6 +129,7 @@ elseif (DEFINED MSVC) add_compile_options(/wd4800) # disable forcing value to bool 'true' or 'false' (performance warning) (4800) add_compile_options(-D_WIN32_WINNT=0x0600) # declare Windows Vista API requirement add_compile_options(-DNOMINMAX) # undefine windows.h MAX && MIN macros cause it cause conflicts with std::min && std::max functions + add_compile_options(/utf-8) # enable utf-8 encoding (solves warning 4819) # disable empty object file warning set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221") @@ -138,7 +144,13 @@ else () endif () if (SANITIZE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZE}") + # Perform case-insensitive string compare + string(TOLOWER "${SANITIZE}" san) + # -fno-omit-frame-pointer gives more informative stack trace in case of an error + # -fsanitize-address-use-after-scope throws an error when a variable is used beyond its scope + if (san STREQUAL "address") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope") + endif() endif() # Code coverage support. @@ -166,9 +178,8 @@ option(USE_CVC4 "Allow compiling with CVC4 SMT solver integration" ON) if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) option(USE_LD_GOLD "Use GNU gold linker" ON) if (USE_LD_GOLD) - execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) if ("${LD_VERSION}" MATCHES "GNU gold") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") endif () endif () diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index cc2f87114..53319e6c8 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -1,18 +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) - 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 @@ -38,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.54.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) +set(BOOST_COMPONENTS "regex;filesystem;unit_test_framework;program_options;system") + +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}") -eth_show_dependency(Boost boost) +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/EthOptions.cmake b/cmake/EthOptions.cmake index a79e5135a..6dca5e1be 100644 --- a/cmake/EthOptions.cmake +++ b/cmake/EthOptions.cmake @@ -1,8 +1,12 @@ +# CMAKE macros to set default CMAKE options and to show the +# resulting configuration. + macro(configure_project) set(NAME ${PROJECT_NAME}) # features eth_default_option(COVERAGE OFF) + eth_default_option(OSSFUZZ OFF) # components eth_default_option(TESTS ON) @@ -21,7 +25,7 @@ endmacro() macro(print_config NAME) message("") message("------------------------------------------------------------------------") - message("-- Configuring ${NAME}") + message("-- Configuring ${NAME} ${PROJECT_VERSION}") message("------------------------------------------------------------------------") message("-- CMake Version ${CMAKE_VERSION}") message("-- CMAKE_BUILD_TYPE Build type ${CMAKE_BUILD_TYPE}") @@ -35,6 +39,9 @@ endif() if (SUPPORT_TOOLS) message("-- TOOLS Build tools ${TOOLS}") endif() + message("------------------------------------------------------------------ flags") + message("-- OSSFUZZ ${OSSFUZZ}") + message("-- LLL ${LLL}") message("------------------------------------------------------------------------") message("") endmacro() diff --git a/cmake/EthToolchains.cmake b/cmake/EthToolchains.cmake new file mode 100644 index 000000000..a4263b7df --- /dev/null +++ b/cmake/EthToolchains.cmake @@ -0,0 +1,8 @@ +if(NOT CMAKE_TOOLCHAIN_FILE) + # Use default toolchain file if none is provided. + set( + CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_LIST_DIR}/toolchains/default.cmake" + CACHE FILEPATH "The CMake toolchain file" + ) +endif() diff --git a/cmake/FindCLN.cmake b/cmake/FindCLN.cmake index f2234bb40..0b574ab9f 100644 --- a/cmake/FindCLN.cmake +++ b/cmake/FindCLN.cmake @@ -1,3 +1,8 @@ find_library(CLN_LIBRARY NAMES cln) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CLN DEFAULT_MSG CLN_LIBRARY) + +if(CLN_FOUND AND NOT TARGET CLN::CLN) + add_library(CLN::CLN UNKNOWN IMPORTED) + set_property(TARGET CLN::CLN PROPERTY IMPORTED_LOCATION ${CLN_LIBRARY}) +endif() diff --git a/cmake/FindCVC4.cmake b/cmake/FindCVC4.cmake index 2649d7c7a..887b907b8 100644 --- a/cmake/FindCVC4.cmake +++ b/cmake/FindCVC4.cmake @@ -14,12 +14,19 @@ if (USE_CVC4) set(CVC4_LIBRARIES ${CVC4_LIBRARY}) if (CLN_FOUND) - set(CVC4_LIBRARIES ${CVC4_LIBRARIES} ${CLN_LIBRARY}) + set(CVC4_LIBRARIES ${CVC4_LIBRARIES} CLN::CLN) endif () if (GMP_FOUND) - set(CVC4_LIBRARIES ${CVC4_LIBRARIES} ${GMP_LIBRARY}) + set(CVC4_LIBRARIES ${CVC4_LIBRARIES} GMP::GMP) endif () + + if (NOT TARGET CVC4::CVC4) + add_library(CVC4::CVC4 UNKNOWN IMPORTED) + set_property(TARGET CVC4::CVC4 PROPERTY IMPORTED_LOCATION ${CVC4_LIBRARY}) + set_property(TARGET CVC4::CVC4 PROPERTY INTERFACE_LINK_LIBRARIES ${CVC4_LIBRARIES}) + set_property(TARGET CVC4::CVC4 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CVC4_INCLUDE_DIR}) + endif() endif() else() set(CVC4_FOUND FALSE) diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake index 8abe354ca..c3a026542 100644 --- a/cmake/FindGMP.cmake +++ b/cmake/FindGMP.cmake @@ -1,3 +1,8 @@ -find_library(GMP_LIBRARY NAMES gmp ) +find_library(GMP_LIBRARY NAMES gmp) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GMP DEFAULT_MSG GMP_LIBRARY) + +if(GMP_FOUND AND NOT TARGET GMP::GMP) + add_library(GMP::GMP UNKNOWN IMPORTED) + set_property(TARGET GMP::GMP PROPERTY IMPORTED_LOCATION ${GMP_LIBRARY}) +endif() diff --git a/cmake/FindZ3.cmake b/cmake/FindZ3.cmake index c017cac2d..092b8636b 100644 --- a/cmake/FindZ3.cmake +++ b/cmake/FindZ3.cmake @@ -1,9 +1,51 @@ if (USE_Z3) - find_path(Z3_INCLUDE_DIR NAMES z3++.h PATH_SUFFIXES z3) - find_library(Z3_LIBRARY NAMES z3 ) + # 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 DEFAULT_MSG Z3_LIBRARY 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) + if(Z3_EXECUTABLE) + execute_process (COMMAND ${Z3_EXECUTABLE} -version + OUTPUT_VARIABLE libz3_version_str + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1" + Z3_VERSION_STRING "${libz3_version_str}") + unset(libz3_version_str) + else() + message(WARNING "Could not determine the version of z3, since the z3 executable was not found.") + set(Z3_VERSION_STRING "0.0.0") + endif() + endif() + mark_as_advanced(Z3_VERSION_STRING z3_DIR) + + 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) endif() -# TODO: Create IMPORTED library for Z3. diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index ea3218efc..3e72a8be2 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -15,11 +15,18 @@ set(JSONCPP_INCLUDE_DIR "${prefix}/include") # versions used in the CI runs. if(EMSCRIPTEN) # Do not include all flags in CMAKE_CXX_FLAGS for emscripten, - # but only use -std=c++11. Using all flags causes build failures + # but only use -std=c++17. Using all flags causes build failures # at the moment. - set(JSONCPP_CXX_FLAGS -std=c++11) + set(JSONCPP_CXX_FLAGS -std=c++17) else() - set(JSONCPP_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + # jsoncpp uses implicit casts for comparing integer and + # floating point numbers. This causes clang-10 (used by ossfuzz builder) + # to error on the implicit conversions. Here, we request jsoncpp + # to unconditionally use static casts for these conversions by defining the + # JSON_USE_INT64_DOUBLE_CONVERSION preprocessor macro. Doing so, + # not only gets rid of the implicit conversion error that clang-10 produces + # but also forces safer behavior in general. + set(JSONCPP_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJSON_USE_INT64_DOUBLE_CONVERSION") endif() set(byproducts "") @@ -35,7 +42,6 @@ ExternalProject_Add(jsoncpp-project URL_HASH SHA256=c49deac9e0933bcb7044f08516861a2d560988540b23de2ac1ad443b219afdb6 CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_INSTALL_LIBDIR=lib # Build static lib but suitable to be included in a shared lib. @@ -51,5 +57,6 @@ ExternalProject_Add(jsoncpp-project add_library(jsoncpp STATIC IMPORTED) file(MAKE_DIRECTORY ${JSONCPP_INCLUDE_DIR}) # Must exist. set_property(TARGET jsoncpp PROPERTY IMPORTED_LOCATION ${JSONCPP_LIBRARY}) +set_property(TARGET jsoncpp PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES ${JSONCPP_INCLUDE_DIR}) set_property(TARGET jsoncpp PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${JSONCPP_INCLUDE_DIR}) add_dependencies(jsoncpp jsoncpp-project) diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 187c7cfb8..89e3295cf 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -2,6 +2,9 @@ #define ETH_PROJECT_VERSION "@SOLC_PROJECT_VERSION@" #define LITY_PROJECT_VERSION "@LITY_PROJECT_VERSION@" +#define LITY_PROJECT_VERSION_MAJOR @LITY_PROJECT_VERSION_MAJOR@ +#define LITY_PROJECT_VERSION_MINOR @LITY_PROJECT_VERSION_MINOR@ +#define LITY_PROJECT_VERSION_PATCH @LITY_PROJECT_VERSION_PATCH@ #define SOL_COMMIT_HASH "@SOL_COMMIT_HASH@" #define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@" #define ETH_BUILD_OS "@ETH_BUILD_OS@" diff --git a/cmake/templates/license.h.in b/cmake/templates/license.h.in index 4f22d8f4d..65276d8b4 100644 --- a/cmake/templates/license.h.in +++ b/cmake/templates/license.h.in @@ -67,7 +67,7 @@ jsoncpp: license you like. scanner/token: - The libsolidity/parsing/{scanner,token}.{h,cpp} files are dervied from + The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are derived from code originating from the V8 project licensed under the following terms: Copyright 2006-2012, the V8 project authors. All rights reserved. @@ -97,6 +97,22 @@ scanner/token: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +evmc: + The code in test/evmc is licensed under the Apache License version 2: + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + All other code is licensed under GPL version 3: )"}; diff --git a/cmake/toolchains/cxx20.cmake b/cmake/toolchains/cxx20.cmake new file mode 100644 index 000000000..ad34e5749 --- /dev/null +++ b/cmake/toolchains/cxx20.cmake @@ -0,0 +1,4 @@ +# Require C++20. +set(CMAKE_CXX_STANDARD 20) # This requires at least CMake 3.12 to understand this C++20 flag +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/default.cmake b/cmake/toolchains/default.cmake new file mode 100644 index 000000000..07fc80e8e --- /dev/null +++ b/cmake/toolchains/default.cmake @@ -0,0 +1,4 @@ +# Require C++17. +set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag. +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/emscripten.cmake b/cmake/toolchains/emscripten.cmake new file mode 100644 index 000000000..6c29074f3 --- /dev/null +++ b/cmake/toolchains/emscripten.cmake @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +include("$ENV{EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake") diff --git a/cmake/toolchains/libfuzzer.cmake b/cmake/toolchains/libfuzzer.cmake new file mode 100644 index 000000000..354e6d09b --- /dev/null +++ b/cmake/toolchains/libfuzzer.cmake @@ -0,0 +1,11 @@ +# Inherit default options +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +# Enable Z3, disable CVC4 +set(USE_Z3 ON CACHE BOOL "Enable Z3" FORCE) +set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) +# Build fuzzing binaries +set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) +# Use libfuzzer as the fuzzing back-end +set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE) +# clang/libfuzzer specific flags for ASan instrumentation +set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE) diff --git a/cmake/toolchains/ossfuzz.cmake b/cmake/toolchains/ossfuzz.cmake new file mode 100644 index 000000000..113b6b453 --- /dev/null +++ b/cmake/toolchains/ossfuzz.cmake @@ -0,0 +1,11 @@ +# Inherit default options +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +# Disable CVC4. +set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) +# Enable fuzzers +set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE) +set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE STRING "Use fuzzer back-end defined by environment variable" FORCE) +# Link statically against boost libraries +set(BOOST_FOUND ON CACHE BOOL "" FORCE) +set(Boost_USE_STATIC_LIBS ON CACHE BOOL "Link against static Boost libraries" FORCE) +set(Boost_USE_STATIC_RUNTIME ON CACHE BOOL "Link against static Boost runtime library" FORCE) diff --git a/codecov.yml b/codecov.yml index f20980ef5..c0b72761e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,6 +3,10 @@ codecov: coverage: range: 70...100 status: + patch: + default: + target: "50%" + paths: "!test/" project: default: target: auto diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index 48112cd97..46ddc9abe 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -171,7 +171,7 @@ Command Line and JSON Interfaces the first 36 hex characters of the keccak256 hash of the fully qualified library name, surrounded by ``$...$``. Previously, just the fully qualified library name was used. - This recudes the chances of collisions, especially when long paths are used. + This reduces the chances of collisions, especially when long paths are used. Binary files now also contain a list of mappings from these placeholders to the fully qualified names. @@ -308,7 +308,7 @@ This will no longer compile with Solidity v0.5.0. However, you can define a comp :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity >=0.5.0 <0.7.0; interface OldContract { function someOldFunction(uint8 a) external; function anotherOldFunction() external returns (bool); @@ -325,7 +325,7 @@ Given the interface defined above, you can now easily use the already deployed p :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity >=0.5.0 <0.7.0; interface OldContract { function someOldFunction(uint8 a) external; @@ -345,7 +345,7 @@ commandline compiler for linking): :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity >=0.5.0 <0.7.0; library OldLibrary { function someFunction(uint8 a) public returns(bool); @@ -430,7 +430,7 @@ New version: :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity >=0.5.0 <0.7.0; contract OtherContract { uint x; diff --git a/docs/bugs.json b/docs/bugs.json index 28c0fe623..6c34eb44e 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,8 +1,131 @@ [ + { + "name": "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers", + "summary": "Reading from calldata structs that contain dynamically encoded, but statically-sized members can result in incorrect values.", + "description": "When a calldata struct contains a dynamically encoded, but statically-sized member, the offsets for all subsequent struct members are calculated incorrectly. All reads from such members will result in invalid values. Only calldata structs are affected, i.e. this occurs in external functions with such structs as argument. Using affected structs in storage or memory or as arguments to public functions on the other hand works correctly.", + "introduced": "0.5.6", + "fixed": "0.5.11", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, + { + "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.", + "description": "During construction of a contract, constructor parameters are copied from the code section to memory for decoding. The amount of bytes to copy was calculated incorrectly in case all parameters are statically-sized but contain dynamically-sized arrays as struct members or inner arrays. Such types are only available if ABIEncoderV2 is activated.", + "introduced": "0.4.16", + "fixed": "0.5.9", + "severity": "very low", + "conditions": { + "ABIEncoderV2": true + } + }, + { + "name": "UninitializedFunctionPointerInConstructor", + "summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", + "description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.", + "introduced": "0.5.0", + "fixed": "0.5.8", + "severity": "very low" + }, + { + "name": "UninitializedFunctionPointerInConstructor_0.4.x", + "summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.", + "description": "Uninitialized internal function pointers point to a special piece of code that causes a revert when called. Jump target positions are different during construction and after deployment, but the code for setting this special jump target only considered the situation after deployment.", + "introduced": "0.4.5", + "fixed": "0.4.26", + "severity": "very low" + }, + { + "name": "IncorrectEventSignatureInLibraries", + "summary": "Contract types used in events in libraries cause an incorrect event signature hash", + "description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.", + "introduced": "0.5.0", + "fixed": "0.5.8", + "severity": "very low" + }, + { + "name": "IncorrectEventSignatureInLibraries_0.4.x", + "summary": "Contract types used in events in libraries cause an incorrect event signature hash", + "description": "Instead of using the type `address` in the hashed signature, the actual contract name was used, leading to a wrong hash in the logs.", + "introduced": "0.3.0", + "fixed": "0.4.26", + "severity": "very low" + }, + { + "name": "ABIEncoderV2PackedStorage", + "summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.", + "description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.5.0", + "fixed": "0.5.7", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, + { + "name": "ABIEncoderV2PackedStorage_0.4.x", + "summary": "Storage structs and arrays with types shorter than 32 bytes can cause data corruption if encoded directly from storage using the experimental ABIEncoderV2.", + "description": "Elements of structs and arrays that are shorter than 32 bytes are not properly decoded from storage when encoded directly (i.e. not via a memory type) using ABIEncoderV2. This can cause corruption in the values themselves but can also overwrite other parts of the encoded data.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.4.19", + "fixed": "0.4.26", + "severity": "low", + "conditions": { + "ABIEncoderV2": true + } + }, + { + "name": "IncorrectByteInstructionOptimization", + "summary": "The optimizer incorrectly handles byte opcodes whose second argument is 31 or a constant expression that evaluates to 31. This can result in unexpected values.", + "description": "The optimizer incorrectly handles byte opcodes that use the constant 31 as second argument. This can happen when performing index access on bytesNN types with a compile-time constant value (not index) of 31 or when using the byte opcode in inline assembly.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.5.5", + "fixed": "0.5.7", + "severity": "very low", + "conditions": { + "optimizer": true + } + }, + { + "name": "DoubleShiftSizeOverflow", + "summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.", + "description": "Nested logical shift operations whose total shift size is 2**256 or more are incorrectly optimized. This only applies to shifts by numbers of bits that are compile-time constant expressions.", + "link": "https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/", + "introduced": "0.5.5", + "fixed": "0.5.6", + "severity": "low", + "conditions": { + "optimizer": true, + "evmVersion": ">=constantinople" + } + }, { "name": "ExpExponentCleanup", "summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.", "description": "Higher order bits in the exponent are not properly cleaned before the EXP opcode is applied if the type of the exponent expression is smaller than 256 bits and not smaller than the type of the base. In that case, the result might be larger than expected if the exponent is assumed to lie within the value range of the type. Literal numbers as exponents are unaffected as are exponents or bases of type uint256.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "fixed": "0.4.25", "severity": "medium/high", "check": {"regex-source": "[^/]\\*\\* *[^/0-9 ]"} @@ -11,6 +134,7 @@ "name": "EventStructWrongData", "summary": "Using structs in events logged wrong data.", "description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "introduced": "0.4.17", "fixed": "0.4.25", "severity": "very low", @@ -20,6 +144,7 @@ "name": "NestedArrayFunctionCallDecoder", "summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.", "description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.", + "link": "https://blog.ethereum.org/2018/09/13/solidity-bugfix-release/", "introduced": "0.1.4", "fixed": "0.4.22", "severity": "medium", @@ -43,7 +168,7 @@ { "name": "DelegateCallReturnValue", "summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.", - "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successuful.", + "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successful.", "introduced": "0.3.0", "fixed": "0.4.15", "severity": "low" diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 2631b2867..d79aca112 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -211,6 +211,7 @@ }, "0.3.0": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -231,6 +232,7 @@ }, "0.3.1": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -250,6 +252,7 @@ }, "0.3.2": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -269,6 +272,7 @@ }, "0.3.3": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -287,6 +291,7 @@ }, "0.3.4": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -305,6 +310,7 @@ }, "0.3.5": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -323,6 +329,7 @@ }, "0.3.6": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -339,6 +346,7 @@ }, "0.4.0": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -355,6 +363,7 @@ }, "0.4.1": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -371,6 +380,9 @@ }, "0.4.10": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -383,6 +395,9 @@ }, "0.4.11": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -394,6 +409,9 @@ }, "0.4.12": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -404,6 +422,9 @@ }, "0.4.13": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -414,6 +435,9 @@ }, "0.4.14": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -423,6 +447,9 @@ }, "0.4.15": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector" @@ -431,6 +458,11 @@ }, "0.4.16": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector" @@ -439,6 +471,11 @@ }, "0.4.17": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder", @@ -448,6 +485,11 @@ }, "0.4.18": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -456,6 +498,12 @@ }, "0.4.19": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -464,6 +512,7 @@ }, "0.4.2": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -479,6 +528,12 @@ }, "0.4.20": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -487,6 +542,12 @@ }, "0.4.21": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "NestedArrayFunctionCallDecoder" @@ -495,6 +556,12 @@ }, "0.4.22": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData", "OneOfTwoConstructorsSkipped" @@ -503,6 +570,12 @@ }, "0.4.23": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData" ], @@ -510,17 +583,39 @@ }, "0.4.24": { "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x", "ExpExponentCleanup", "EventStructWrongData" ], "released": "2018-05-16" }, "0.4.25": { - "bugs": [], + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", + "ABIEncoderV2PackedStorage_0.4.x" + ], "released": "2018-09-12" }, + "0.4.26": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2" + ], + "released": "2019-04-29" + }, "0.4.3": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -535,6 +630,7 @@ }, "0.4.4": { "bugs": [ + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -548,6 +644,8 @@ }, "0.4.5": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -562,6 +660,8 @@ }, "0.4.6": { "bugs": [ + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -575,6 +675,9 @@ }, "0.4.7": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -587,6 +690,9 @@ }, "0.4.8": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -599,6 +705,9 @@ }, "0.4.9": { "bugs": [ + "SignedArrayStorageCopy", + "UninitializedFunctionPointerInConstructor_0.4.x", + "IncorrectEventSignatureInLibraries_0.4.x", "ExpExponentCleanup", "NestedArrayFunctionCallDecoder", "ZeroFunctionSelector", @@ -610,7 +719,126 @@ "released": "2017-01-31" }, "0.5.0": { - "bugs": [], + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage" + ], "released": "2018-11-13" + }, + "0.5.1": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage" + ], + "released": "2018-12-03" + }, + "0.5.10": { + "bugs": [ + "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers" + ], + "released": "2019-06-25" + }, + "0.5.11": { + "bugs": [], + "released": "2019-08-12" + }, + "0.5.12": { + "bugs": [], + "released": "2019-10-01" + }, + "0.5.2": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage" + ], + "released": "2018-12-19" + }, + "0.5.3": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage" + ], + "released": "2019-01-22" + }, + "0.5.4": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage" + ], + "released": "2019-02-12" + }, + "0.5.5": { + "bugs": [ + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage", + "IncorrectByteInstructionOptimization", + "DoubleShiftSizeOverflow" + ], + "released": "2019-03-05" + }, + "0.5.6": { + "bugs": [ + "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers", + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries", + "ABIEncoderV2PackedStorage", + "IncorrectByteInstructionOptimization" + ], + "released": "2019-03-13" + }, + "0.5.7": { + "bugs": [ + "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers", + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2", + "UninitializedFunctionPointerInConstructor", + "IncorrectEventSignatureInLibraries" + ], + "released": "2019-03-26" + }, + "0.5.8": { + "bugs": [ + "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers", + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement", + "DynamicConstructorArgumentsClippedABIV2" + ], + "released": "2019-04-30" + }, + "0.5.9": { + "bugs": [ + "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers", + "SignedArrayStorageCopy", + "ABIEncoderV2StorageArrayWithMultiSlotElement" + ], + "released": "2019-05-28" } } \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 7e4461197..98cfc678e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. @@ -15,6 +16,7 @@ import os import sys +import re def setup(sphinx): thisdir = os.path.dirname(os.path.realpath(__file__)) @@ -25,13 +27,9 @@ def setup(sphinx): # -- Project information ----------------------------------------------------- project = 'Lity' -copyright = '2018, CyberMiles' +copyright = '2019, CyberMiles' author = 'CyberMiles' -# The short X.Y version -version = '' -# The full version, including alpha/beta/rc tags -release = '' # -- General configuration --------------------------------------------------- @@ -61,6 +59,19 @@ def setup(sphinx): # The master toctree document. master_doc = 'index' +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +with open('../CMakeLists.txt', 'r') as f: + version = re.search('PROJECT_VERSION "([^"]+)"', f.read()).group(1) +# The full version, including alpha/beta/rc tags. +if os.path.isfile('../prerelease.txt') != True or os.path.getsize('../prerelease.txt') == 0: + release = version +else: + # This is a prerelease version + release = version + '-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # @@ -71,7 +82,7 @@ def setup(sphinx): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'contracts', 'types', 'examples' 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/contracts/abstract-contracts.rst b/docs/contracts/abstract-contracts.rst new file mode 100644 index 000000000..924805467 --- /dev/null +++ b/docs/contracts/abstract-contracts.rst @@ -0,0 +1,43 @@ +.. index:: ! contract;abstract, ! abstract contract + +.. _abstract-contract: + +****************** +Abstract Contracts +****************** + +Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``):: + + pragma solidity >=0.4.0 <0.7.0; + + contract Feline { + function utterance() public returns (bytes32); + } + +Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: + + pragma solidity >=0.4.0 <0.7.0; + + contract Feline { + function utterance() public returns (bytes32); + } + + contract Cat is Feline { + function utterance() public returns (bytes32) { return "miaow"; } + } + +If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. + +Note that a function without implementation is different from a :ref:`Function Type ` even though their syntax looks very similar. + +Example of function without implementation (a function declaration):: + + function foo(address) external returns (address); + +Example of a Function Type (a variable declaration, where the variable is of type ``function``):: + + function(address) external returns (address) foo; + +Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and +facilitating patterns like the `Template method `_ and removing code duplication. +Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method". diff --git a/docs/contracts/constant-state-variables.rst b/docs/contracts/constant-state-variables.rst new file mode 100644 index 000000000..2b4b7ff5b --- /dev/null +++ b/docs/contracts/constant-state-variables.rst @@ -0,0 +1,35 @@ +.. index:: ! constant + +************************ +Constant State Variables +************************ + +State variables can be declared as ``constant``. In this case, they have to be +assigned from an expression which is a constant at compile time. Any expression +that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or +``block.number``) or +execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions +that might have a side-effect on memory allocation are allowed, but those that +might have a side-effect on other memory objects are not. The built-in functions +``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod`` +are allowed (even though, with the exception of ``keccak256``, they do call external contracts). + +The reason behind allowing side-effects on the memory allocator is that it +should be possible to construct complex objects like e.g. lookup-tables. +This feature is not yet fully usable. + +The compiler does not reserve a storage slot for these variables, and every occurrence is +replaced by the respective constant expression (which might be computed to a single value by the optimizer). + +Not all types for constants are implemented at this time. The only supported types are +value types and strings. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract C { + uint constant x = 32**22 + 8; + string constant text = "abc"; + bytes32 constant myHash = keccak256("abc"); + } diff --git a/docs/contracts/creating-contracts.rst b/docs/contracts/creating-contracts.rst new file mode 100644 index 000000000..b6f5aee50 --- /dev/null +++ b/docs/contracts/creating-contracts.rst @@ -0,0 +1,119 @@ +.. index:: ! contract;creation, constructor + +****************** +Creating Contracts +****************** + +Contracts can be created "from outside" via Ethereum transactions or from within Solidity contracts. + +IDEs, such as `Remix `_, make the creation process seamless using UI elements. + +Creating contracts programmatically on Ethereum is best done via using the JavaScript API `web3.js `_. +It has a function called `web3.eth.Contract `_ +to facilitate contract creation. + +When a contract is created, its :ref:`constructor ` (a function declared with the ``constructor`` keyword) is executed once. + +A constructor is optional. Only one constructor is allowed, which means +overloading is not supported. + +After the constructor has executed, the final code of the contract is deployed to the +blockchain. This code includes all public and external functions and all functions +that are reachable from there through function calls. The deployed code does not +include the constructor code or internal functions only called from the constructor. + +.. index:: constructor;arguments + +Internally, constructor arguments are passed :ref:`ABI encoded ` after the code of +the contract itself, but you do not have to care about this if you use ``web3.js``. + +If a contract wants to create another contract, the source code +(and the binary) of the created contract has to be known to the creator. +This means that cyclic creation dependencies are impossible. + +:: + + pragma solidity >=0.4.22 <0.7.0; + + + contract OwnedToken { + // `TokenCreator` is a contract type that is defined below. + // It is fine to reference it as long as it is not used + // to create a new contract. + TokenCreator creator; + address owner; + bytes32 name; + + // This is the constructor which registers the + // creator and the assigned name. + constructor(bytes32 _name) public { + // State variables are accessed via their name + // and not via e.g. `this.owner`. Functions can + // be accessed directly or through `this.f`, + // but the latter provides an external view + // to the function. Especially in the constructor, + // you should not access functions externally, + // because the function does not exist yet. + // See the next section for details. + owner = msg.sender; + + // We do an explicit type conversion from `address` + // to `TokenCreator` and assume that the type of + // the calling contract is `TokenCreator`, there is + // no real way to check that. + creator = TokenCreator(msg.sender); + name = _name; + } + + function changeName(bytes32 newName) public { + // Only the creator can alter the name -- + // the comparison is possible since contracts + // are explicitly convertible to addresses. + if (msg.sender == address(creator)) + name = newName; + } + + function transfer(address newOwner) public { + // Only the current owner can transfer the token. + if (msg.sender != owner) return; + + // We ask the creator contract if the transfer + // should proceed by using a function of the + // `TokenCreator` contract defined below. If + // the call fails (e.g. due to out-of-gas), + // the execution also fails here. + if (creator.isTokenTransferOK(owner, newOwner)) + owner = newOwner; + } + } + + + contract TokenCreator { + function createToken(bytes32 name) + public + returns (OwnedToken tokenAddress) + { + // Create a new `Token` contract and return its address. + // From the JavaScript side, the return type is + // `address`, as this is the closest type available in + // the ABI. + return new OwnedToken(name); + } + + function changeName(OwnedToken tokenAddress, bytes32 name) public { + // Again, the external type of `tokenAddress` is + // simply `address`. + tokenAddress.changeName(name); + } + + // Perform checks to determine if transferring a token to the + // `OwnedToken` contract should proceed + function isTokenTransferOK(address currentOwner, address newOwner) + public + pure + returns (bool ok) + { + // Check an arbitrary condition to see if transfer should proceed + return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f; + } + } diff --git a/docs/contracts/events.rst b/docs/contracts/events.rst new file mode 100644 index 000000000..c780da8a9 --- /dev/null +++ b/docs/contracts/events.rst @@ -0,0 +1,163 @@ +.. index:: ! event + +.. _events: + +****** +Events +****** + +Solidity events give an abstraction on top of the EVM's logging functionality. +Applications can subscribe and listen to these events through the RPC interface of an Ethereum client. + +Events are inheritable members of contracts. When you call them, they cause the +arguments to be stored in the transaction's log - a special data structure +in the blockchain. These logs are associated with the address of the contract, +are incorporated into the blockchain, and stay there as long as a block is +accessible (forever as of the Frontier and Homestead releases, but this might +change with Serenity). The Log and its event data is not accessible from within +contracts (not even from the contract that created them). + +It is possible to request a simple payment verification (SPV) for logs, so if +an external entity supplies a contract with such a verification, it can check +that the log actually exists inside the blockchain. You have to supply block headers +because the contract can only see the last 256 block hashes. + +You can add the attribute ``indexed`` to up to three parameters which adds them +to a special data structure known as :ref:`"topics" ` instead of +the data part of the log. If you use arrays (including ``string`` and ``bytes``) +as indexed arguments, its Keccak-256 hash is stored as a topic instead, this is +because a topic can only hold a single word (32 bytes). + +All parameters without the ``indexed`` attribute are :ref:`ABI-encoded ` +into the data part of the log. + +Topics allow you to search for events, for example when filtering a sequence of +blocks for certain events. You can also filter events by the address of the +contract that emitted the event. + +For example, the code below uses the web3.js ``subscribe("logs")`` +`method `_ to filter +logs that match a topic with a certain address value: + +.. code-block:: javascript + + var options = { + fromBlock: 0, + address: web3.eth.defaultAccount, + topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null] + }; + web3.eth.subscribe('logs', options, function (error, result) { + if (!error) + console.log(result); + }) + .on("data", function (log) { + console.log(log); + }) + .on("changed", function (log) { + }); + + +The hash of the signature of the event is one of the topics, except if you +declared the event with the ``anonymous`` specifier. This means that it is +not possible to filter for specific anonymous events by name, you can +only filter by the contract address. The advantage of anonymous events +is that they are cheaper to deploy and call. + +:: + + pragma solidity >=0.4.21 <0.7.0; + + contract ClientReceipt { + event Deposit( + address indexed _from, + bytes32 indexed _id, + uint _value + ); + + function deposit(bytes32 _id) public payable { + // Events are emitted using `emit`, followed by + // the name of the event and the arguments + // (if any) in parentheses. Any such invocation + // (even deeply nested) can be detected from + // the JavaScript API by filtering for `Deposit`. + emit Deposit(msg.sender, _id, msg.value); + } + } + +The use in the JavaScript API is as follows: + +:: + + var abi = /* abi as generated by the compiler */; + var ClientReceipt = web3.eth.contract(abi); + var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */); + + var event = clientReceipt.Deposit(); + + // watch for changes + event.watch(function(error, result){ + // result contains non-indexed arguments and topics + // given to the `Deposit` call. + if (!error) + console.log(result); + }); + + + // Or pass a callback to start watching immediately + var event = clientReceipt.Deposit(function(error, result) { + if (!error) + console.log(result); + }); + +The output of the above looks like the following (trimmed): + +.. code-block:: json + + { + "returnValues": { + "_from": "0x1111…FFFFCCCC", + "_id": "0x50…sd5adb20", + "_value": "0x420042" + }, + "raw": { + "data": "0x7f…91385", + "topics": ["0xfd4…b4ead7", "0x7f…1a91385"] + } + } + +.. index:: ! log + +Low-Level Interface to Logs +=========================== + +It is also possible to access the low-level interface to the logging +mechanism via the functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4``. +Each function ``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first +argument will be used for the data part of the log and the others +as topics. The event call above can be performed in the same way as + +:: + + pragma solidity >=0.4.10 <0.7.0; + + contract C { + function f() public payable { + uint256 _id = 0x420042; + log3( + bytes32(msg.value), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id) + ); + } + } + +where the long hexadecimal number is equal to +``keccak256("Deposit(address,bytes32,uint256)")``, the signature of the event. + +Additional Resources for Understanding Events +============================================== + +- `Javascript documentation `_ +- `Example usage of events `_ +- `How to access them in js `_ diff --git a/docs/contracts/function-modifiers.rst b/docs/contracts/function-modifiers.rst new file mode 100644 index 000000000..fd24a8cb5 --- /dev/null +++ b/docs/contracts/function-modifiers.rst @@ -0,0 +1,111 @@ +.. index:: ! function;modifier + +.. _modifiers: + +****************** +Function Modifiers +****************** + +Modifiers can be used to easily change the behaviour of functions. For example, +they can automatically check a condition prior to executing the function. Modifiers are +inheritable properties of contracts and may be overridden by derived contracts. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + + // This contract only defines a modifier but does not use + // it: it will be used in derived contracts. + // The function body is inserted where the special symbol + // `_;` in the definition of a modifier appears. + // This means that if the owner calls this function, the + // function is executed and otherwise, an exception is + // thrown. + modifier onlyOwner { + require( + msg.sender == owner, + "Only owner can call this function." + ); + _; + } + } + + contract mortal is owned { + // This contract inherits the `onlyOwner` modifier from + // `owned` and applies it to the `close` function, which + // causes that calls to `close` only have an effect if + // they are made by the stored owner. + function close() public onlyOwner { + selfdestruct(owner); + } + } + + contract priced { + // Modifiers can receive arguments: + modifier costs(uint price) { + if (msg.value >= price) { + _; + } + } + } + + contract Register is priced, owned { + mapping (address => bool) registeredAddresses; + uint price; + + constructor(uint initialPrice) public { price = initialPrice; } + + // It is important to also provide the + // `payable` keyword here, otherwise the function will + // automatically reject all Ether sent to it. + function register() public payable costs(price) { + registeredAddresses[msg.sender] = true; + } + + function changePrice(uint _price) public onlyOwner { + price = _price; + } + } + + contract Mutex { + bool locked; + modifier noReentrancy() { + require( + !locked, + "Reentrant call." + ); + locked = true; + _; + locked = false; + } + + /// This function is protected by a mutex, which means that + /// reentrant calls from within `msg.sender.call` cannot call `f` again. + /// The `return 7` statement assigns 7 to the return value but still + /// executes the statement `locked = false` in the modifier. + function f() public noReentrancy returns (uint) { + (bool success,) = msg.sender.call(""); + require(success); + return 7; + } + } + +Multiple modifiers are applied to a function by specifying them in a +whitespace-separated list and are evaluated in the order presented. + +.. warning:: + In an earlier version of Solidity, ``return`` statements in functions + having modifiers behaved differently. + +Explicit returns from a modifier or function body only leave the current +modifier or function body. Return variables are assigned and +control flow continues after the "_" in the preceding modifier. + +Arbitrary expressions are allowed for modifier arguments and in this context, +all symbols visible from the function are visible in the modifier. Symbols +introduced in the modifier are not visible in the function (as they might +change by overriding). diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst new file mode 100644 index 000000000..e8d9961e7 --- /dev/null +++ b/docs/contracts/functions.rst @@ -0,0 +1,398 @@ +.. index:: ! functions + +.. _functions: + +********* +Functions +********* + +.. _function-parameters-return-variables: + +Function Parameters and Return Variables +======================================== + +As in JavaScript, functions may take parameters as input. Unlike in JavaScript +and C, functions may also return an arbitrary number of values as output. + +Function Parameters +------------------- + +Function parameters are declared the same way as variables, and the name of +unused parameters can be omitted. + +For example, if you want your contract to accept one kind of external call +with two integers, you would use something like:: + + pragma solidity >=0.4.16 <0.7.0; + + contract Simple { + uint sum; + function taker(uint _a, uint _b) public { + sum = _a + _b; + } + } + +Function parameters can be used as any other local variable and they can also be assigned to. + +.. note:: + + An :ref:`external function` cannot accept a + multi-dimensional array as an input + parameter. This functionality is possible if you enable the new + experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file. + + An :ref:`internal function` can accept a + multi-dimensional array without enabling the feature. + +.. index:: return array, return string, array, string, array of strings, dynamic array, variably sized array, return struct, struct + +Return Variables +---------------- + +Function return variables are declared with the same syntax after the +``returns`` keyword. + +For example, suppose you want to return two results: the sum and the product of +two integers passed as function parameters, then you use something like:: + + pragma solidity >=0.4.16 <0.7.0; + + contract Simple { + function arithmetic(uint _a, uint _b) + public + pure + returns (uint o_sum, uint o_product) + { + o_sum = _a + _b; + o_product = _a * _b; + } + } + +The names of return variables can be omitted. +Return variables can be used as any other local variable and they +are initialized with their :ref:`default value ` and have that value unless explicitly set. + +You can either explicitly assign to return variables and +then leave the function using ``return;``, +or you can provide return values +(either a single or :ref:`multiple ones`) directly with the ``return`` +statement:: + + pragma solidity >=0.4.16 <0.7.0; + + contract Simple { + function arithmetic(uint _a, uint _b) + public + pure + returns (uint o_sum, uint o_product) + { + return (_a + _b, _a * _b); + } + } + +This form is equivalent to first assigning values to the +return variables and then using ``return;`` to leave the function. + +.. note:: + You cannot return some types from non-internal functions, notably + multi-dimensional dynamic arrays and structs. If you enable the + new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental + ABIEncoderV2;`` to your source file then more types are available, but + ``mapping`` types are still limited to inside a single contract and you + cannot transfer them. + +.. _multi-return: + +Returning Multiple Values +------------------------- + +When a function has multiple return types, the statement ``return (v0, v1, ..., vn)`` can be used to return multiple values. +The number of components must be the same as the number of return types. + +.. index:: ! view function, function;view + +.. _view-functions: + +View Functions +============== + +Functions can be declared ``view`` in which case they promise not to modify the state. + +.. note:: + If the compiler's EVM target is Byzantium or newer (default) the opcode + ``STATICCALL`` is used for ``view`` functions which enforces the state + to stay unmodified as part of the EVM execution. For library ``view`` functions + ``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. + This means library ``view`` functions do not have run-time checks that prevent state + modifications. This should not impact security negatively because library code is + usually known at compile-time and the static checker performs compile-time checks. + +The following statements are considered modifying the state: + +#. Writing to state variables. +#. :ref:`Emitting events `. +#. :ref:`Creating other contracts `. +#. Using ``selfdestruct``. +#. Sending Ether via calls. +#. Calling any function not marked ``view`` or ``pure``. +#. Using low-level calls. +#. Using inline assembly that contains certain opcodes. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract C { + function f(uint a, uint b) public view returns (uint) { + return a * (b + 42) + now; + } + } + +.. note:: + ``constant`` on functions used to be an alias to ``view``, but this was dropped in version 0.5.0. + +.. note:: + Getter methods are automatically marked ``view``. + +.. note:: + Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode + for ``view`` functions. + This enabled state modifications in ``view`` functions through the use of + invalid explicit type conversions. + By using ``STATICCALL`` for ``view`` functions, modifications to the + state are prevented on the level of the EVM. + +.. index:: ! pure function, function;pure + +.. _pure-functions: + +Pure Functions +============== + +Functions can be declared ``pure`` in which case they promise not to read from or modify the state. + +.. note:: + If the compiler's EVM target is Byzantium or newer (default) the opcode ``STATICCALL`` is used, + which does not guarantee that the state is not read, but at least that it is not modified. + +In addition to the list of state modifying statements explained above, the following are considered reading from the state: + +#. Reading from state variables. +#. Accessing ``address(this).balance`` or ``
.balance``. +#. Accessing any of the members of ``block``, ``tx``, ``msg`` (with the exception of ``msg.sig`` and ``msg.data``). +#. Calling any function not marked ``pure``. +#. Using inline assembly that contains certain opcodes. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract C { + function f(uint a, uint b) public pure returns (uint) { + return a * (b + 42); + } + } + +Pure functions are able to use the `revert()` and `require()` functions to revert +potential state changes when an :ref:`error occurs `. + +Reverting a state change is not considered a "state modification", as only changes to the +state made previously in code that did not have the ``view`` or ``pure`` restriction +are reverted and that code has the option to catch the ``revert`` and not pass it on. + +This behaviour is also in line with the ``STATICCALL`` opcode. + +.. warning:: + It is not possible to prevent functions from reading the state at the level + of the EVM, it is only possible to prevent them from writing to the state + (i.e. only ``view`` can be enforced at the EVM level, ``pure`` can not). + +.. note:: + Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode + for ``pure`` functions. + This enabled state modifications in ``pure`` functions through the use of + invalid explicit type conversions. + By using ``STATICCALL`` for ``pure`` functions, modifications to the + state are prevented on the level of the EVM. + +.. note:: + Prior to version 0.4.17 the compiler did not enforce that ``pure`` is not reading the state. + It is a compile-time type check, which can be circumvented doing invalid explicit conversions + between contract types, because the compiler can verify that the type of the contract does + not do state-changing operations, but it cannot check that the contract that will be called + at runtime is actually of that type. + +.. index:: ! fallback function, function;fallback + +.. _fallback-function: + +Fallback Function +================= + +A contract can have exactly one unnamed function. This function cannot have +arguments, cannot return anything and has to have ``external`` visibility. +It is executed on a call to the contract if none of the other +functions match the given function identifier (or if no data was supplied at +all). + +Furthermore, this function is executed whenever the contract receives plain +Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function +must be marked ``payable``. If no such function exists, the contract cannot receive +Ether through regular transactions and throws an exception. + +In the worst case, the fallback function can only rely on 2300 gas being +available (for example when `send` or `transfer` is used), leaving little +room to perform other operations except basic logging. The following operations +will consume more gas than the 2300 gas stipend: + +- Writing to storage +- Creating a contract +- Calling an external function which consumes a large amount of gas +- Sending Ether + +Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. + +.. 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 + function only to receive ether, you should add a check + like ``require(msg.data.length == 0)`` to prevent invalid calls. + +.. warning:: + Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) + but do not define a fallback function + throw an exception, sending back the Ether (this was different + before Solidity v0.4.0). So if you want your contract to receive Ether, + you have to implement a payable fallback function. + +.. warning:: + 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). + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract Test { + // This function is called for all messages sent to + // this contract (there is no other function). + // Sending Ether to this contract will cause an exception, + // because the fallback function does not have the `payable` + // modifier. + function() external { x = 1; } + uint x; + } + + + // This contract keeps all Ether sent to it with no way + // to get it back. + contract Sink { + function() external payable { } + } + + contract Caller { + function callTest(Test test) public returns (bool) { + (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); + require(success); + // results in test.x becoming == 1. + + // address(test) will not allow to call ``send`` directly, since ``test`` has no payable + // fallback function. It has to be converted to the ``address payable`` type via an + // intermediate conversion to ``uint160`` to even allow calling ``send`` on it. + address payable testPayable = address(uint160(address(test))); + + // If someone sends ether to that contract, + // the transfer will fail, i.e. this returns false here. + return testPayable.send(2 ether); + } + } + +.. index:: ! overload + +.. _overload-function: + +Function Overloading +==================== + +A contract can have multiple functions of the same name but with different parameter +types. +This process is called "overloading" and also applies to inherited functions. +The following example shows overloading of the function +``f`` in the scope of contract ``A``. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract A { + function f(uint _in) public pure returns (uint out) { + out = _in; + } + + function f(uint _in, bool _really) public pure returns (uint out) { + if (_really) + out = _in; + } + } + +Overloaded functions are also present in the external interface. It is an error if two +externally visible functions differ by their Solidity types but not by their external types. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + // This will not compile + contract A { + function f(B _in) public pure returns (B out) { + out = _in; + } + + function f(address _in) public pure returns (address out) { + out = _in; + } + } + + contract B { + } + + +Both ``f`` function overloads above end up accepting the address type for the ABI although +they are considered different inside Solidity. + +Overload resolution and Argument matching +----------------------------------------- + +Overloaded functions are selected by matching the function declarations in the current scope +to the arguments supplied in the function call. Functions are selected as overload candidates +if all arguments can be implicitly converted to the expected types. If there is not exactly one +candidate, resolution fails. + +.. note:: + Return parameters are not taken into account for overload resolution. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract A { + function f(uint8 _in) public pure returns (uint8 out) { + out = _in; + } + + function f(uint256 _in) public pure returns (uint256 out) { + out = _in; + } + } + +Calling ``f(50)`` would create a type error since ``50`` can be implicitly converted both to ``uint8`` +and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly +converted to ``uint8``. diff --git a/docs/contracts/inheritance.rst b/docs/contracts/inheritance.rst new file mode 100644 index 000000000..28df381ee --- /dev/null +++ b/docs/contracts/inheritance.rst @@ -0,0 +1,344 @@ +.. index:: ! inheritance, ! base class, ! contract;base, ! deriving + +*********** +Inheritance +*********** + +Solidity supports multiple inheritance including polymorphism. + +All function calls are virtual, which means that the most derived function +is called, except when the contract name is explicitly given or the +``super`` keyword is used. + +When a contract inherits from other contracts, only a single +contract is created on the blockchain, and the code from all the base contracts +is compiled into the created contract. This means that all internal calls +to functions of base contracts also just use internal function calls +(``super.f(..)`` will use JUMP and not a message call). + +The general inheritance system is very similar to +`Python's `_, +especially concerning multiple inheritance, but there are also +some :ref:`differences `. + +Details are given in the following example. + +:: + + pragma solidity >=0.5.0 <0.7.0; + + + contract Owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + + // Use `is` to derive from another contract. Derived + // contracts can access all non-private members including + // internal functions and state variables. These cannot be + // accessed externally via `this`, though. + contract Mortal is Owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + + // These abstract contracts are only provided to make the + // interface known to the compiler. Note the function + // without body. If a contract does not implement all + // functions it can only be used as an interface. + contract Config { + function lookup(uint id) public returns (address adr); + } + + + contract NameReg { + function register(bytes32 name) public; + function unregister() public; + } + + + // Multiple inheritance is possible. Note that `owned` is + // also a base class of `mortal`, yet there is only a single + // instance of `owned` (as for virtual inheritance in C++). + contract Named is Owned, Mortal { + constructor(bytes32 name) public { + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); + NameReg(config.lookup(1)).register(name); + } + + // Functions can be overridden by another function with the same name and + // the same number/types of inputs. If the overriding function has different + // types of output parameters, that causes an error. + // Both local and message-based function calls take these overrides + // into account. + function kill() public { + if (msg.sender == owner) { + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); + NameReg(config.lookup(1)).unregister(); + // It is still possible to call a specific + // overridden function. + Mortal.kill(); + } + } + } + + + // If a constructor takes an argument, it needs to be + // provided in the header (or modifier-invocation-style at + // the constructor of the derived contract (see below)). + contract PriceFeed is Owned, Mortal, Named("GoldFeed") { + function updateInfo(uint newInfo) public { + if (msg.sender == owner) info = newInfo; + } + + function get() public view returns(uint r) { return info; } + + uint info; + } + +Note that above, we call ``mortal.kill()`` to "forward" the +destruction request. The way this is done is problematic, as +seen in the following example:: + + pragma solidity >=0.4.22 <0.7.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + contract mortal is owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + contract Base1 is mortal { + function kill() public { /* do cleanup 1 */ mortal.kill(); } + } + + contract Base2 is mortal { + function kill() public { /* do cleanup 2 */ mortal.kill(); } + } + + contract Final is Base1, Base2 { + } + +A call to ``Final.kill()`` will call ``Base2.kill`` as the most +derived override, but this function will bypass +``Base1.kill``, basically because it does not even know about +``Base1``. The way around this is to use ``super``:: + + pragma solidity >=0.4.22 <0.7.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + contract mortal is owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + contract Base1 is mortal { + function kill() public { /* do cleanup 1 */ super.kill(); } + } + + + contract Base2 is mortal { + function kill() public { /* do cleanup 2 */ super.kill(); } + } + + contract Final is Base1, Base2 { + } + +If ``Base2`` calls a function of ``super``, it does not simply +call this function on one of its base contracts. Rather, it +calls this function on the next base contract in the final +inheritance graph, so it will call ``Base1.kill()`` (note that +the final inheritance sequence is -- starting with the most +derived contract: Final, Base2, Base1, mortal, owned). +The actual function that is called when using super is +not known in the context of the class where it is used, +although its type is known. This is similar for ordinary +virtual method lookup. + +.. index:: ! constructor + +.. _constructor: + +Constructors +============ + +A constructor is an optional function declared with the ``constructor`` keyword +which is executed upon contract creation, and where you can run contract +initialisation code. + +Before the constructor code is executed, state variables are initialised to +their specified value if you initialise them inline, or zero if you do not. + +After the constructor has run, the final code of the contract is deployed +to the blockchain. The deployment of +the code costs additional gas linear to the length of the code. +This code includes all functions that are part of the public interface +and all functions that are reachable from there through function calls. +It does not include the constructor code or internal functions that are +only called from the constructor. + +Constructor functions can be either ``public`` or ``internal``. If there is no +constructor, the contract will assume the default constructor, which is +equivalent to ``constructor() public {}``. For example: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + contract A { + uint public a; + + constructor(uint _a) internal { + a = _a; + } + } + + contract B is A(1) { + constructor() public {} + } + +A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract `. + +.. warning :: + Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. + This syntax was deprecated and is not allowed anymore in version 0.5.0. + + +.. index:: ! base;constructor + +Arguments for Base Constructors +=============================== + +The constructors of all the base contracts will be called following the +linearization rules explained below. If the base constructors have arguments, +derived contracts need to specify all of them. This can be done in two ways:: + + pragma solidity >=0.4.22 <0.7.0; + + contract Base { + uint x; + constructor(uint _x) public { x = _x; } + } + + // Either directly specify in the inheritance list... + contract Derived1 is Base(7) { + constructor() public {} + } + + // or through a "modifier" of the derived constructor. + contract Derived2 is Base { + constructor(uint _y) Base(_y * _y) public {} + } + +One way is directly in the inheritance list (``is Base(7)``). The other is in +the way a modifier is invoked as part of +the derived constructor (``Base(_y * _y)``). The first way to +do it is more convenient if the constructor argument is a +constant and defines the behaviour of the contract or +describes it. The second way has to be used if the +constructor arguments of the base depend on those of the +derived contract. Arguments have to be given either in the +inheritance list or in modifier-style in the derived constructor. +Specifying arguments in both places is an error. + +If a derived contract does not specify the arguments to all of its base +contracts' constructors, it will be abstract. + +.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization + +.. _multi-inheritance: + +Multiple Inheritance and Linearization +====================================== + +Languages that allow multiple inheritance have to deal with +several problems. One is the `Diamond Problem `_. +Solidity is similar to Python in that it uses "`C3 Linearization `_" +to force a specific order in the directed acyclic graph (DAG) of base classes. This +results in the desirable property of monotonicity but +disallows some inheritance graphs. Especially, the order in +which the base classes are given in the ``is`` directive is +important: You have to list the direct base contracts +in the order from "most base-like" to "most derived". +Note that this order is the reverse of the one used in Python. + +Another simplifying way to explain this is that when a function is called that +is defined multiple times in different contracts, the given bases +are searched from right to left (left to right in Python) in a depth-first manner, +stopping at the first match. If a base contract has already been searched, it is skipped. + +In the following code, Solidity will give the +error "Linearization of inheritance graph impossible". + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract X {} + contract A is X {} + // This will not compile + contract C is A, X {} + +The reason for this is that ``C`` requests ``X`` to override ``A`` +(by specifying ``A, X`` in this order), but ``A`` itself +requests to override ``X``, which is a contradiction that +cannot be resolved. + +One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract's constructor. For example: + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract Base1 { + constructor() public {} + } + + contract Base2 { + constructor() public {} + } + + // Constructors are executed in the following order: + // 1 - Base1 + // 2 - Base2 + // 3 - Derived1 + contract Derived1 is Base1, Base2 { + constructor() public Base1() Base2() {} + } + + // Constructors are executed in the following order: + // 1 - Base2 + // 2 - Base1 + // 3 - Derived2 + contract Derived2 is Base2, Base1 { + constructor() public Base2() Base1() {} + } + + // Constructors are still executed in the following order: + // 1 - Base2 + // 2 - Base1 + // 3 - Derived3 + contract Derived3 is Base2, Base1 { + constructor() public Base1() Base2() {} + } + + +Inheriting Different Kinds of Members of the Same Name +====================================================== + +When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. +This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. +As an exception, a state variable getter can override a public function. diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst new file mode 100644 index 000000000..91e7d5947 --- /dev/null +++ b/docs/contracts/interfaces.rst @@ -0,0 +1,41 @@ +.. index:: ! contract;interface, ! interface contract + +.. _interfaces: + +********** +Interfaces +********** + +Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: + +- They cannot inherit other contracts or interfaces. +- All declared functions must be external. +- They cannot declare a constructor. +- They cannot declare state variables. + +Some of these restrictions might be lifted in the future. + +Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and +an interface should be possible without any information loss. + +Interfaces are denoted by their own keyword: + +:: + + pragma solidity >=0.5.0 <0.7.0; + + interface Token { + enum TokenType { Fungible, NonFungible } + struct Coin { string obverse; string reverse; } + function transfer(address recipient, uint amount) external; + } + +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/libraries.rst b/docs/contracts/libraries.rst new file mode 100644 index 000000000..7421e278c --- /dev/null +++ b/docs/contracts/libraries.rst @@ -0,0 +1,232 @@ +.. index:: ! library, callcode, delegatecall + +.. _libraries: + +********* +Libraries +********* + +Libraries are similar to contracts, but their purpose is that they are deployed +only once at a specific address and their code is reused using the ``DELEGATECALL`` +(``CALLCODE`` until Homestead) +feature of the EVM. This means that if library functions are called, their code +is executed in the context of the calling contract, i.e. ``this`` points to the +calling contract, and especially the storage from the calling contract can be +accessed. As a library is an isolated piece of source code, it can only access +state variables of the calling contract if they are explicitly supplied (it +would have no way to name them, otherwise). Library functions can only be +called directly (i.e. without the use of ``DELEGATECALL``) if they do not modify +the state (i.e. if they are ``view`` or ``pure`` functions), +because libraries are assumed to be stateless. In particular, it is +not possible to destroy a library. + +.. note:: + Until version 0.4.20, it was possible to destroy libraries by + circumventing Solidity's type system. Starting from that version, + libraries contain a :ref:`mechanism` that + disallows state-modifying functions + to be called directly (i.e. without ``DELEGATECALL``). + +Libraries can be seen as implicit base contracts of the contracts that use them. +They will not be explicitly visible in the inheritance hierarchy, but calls +to library functions look just like calls to functions of explicit base +contracts (``L.f()`` if ``L`` is the name of the library). Furthermore, +``internal`` functions of libraries are visible in all contracts, just as +if the library were a base contract. Of course, calls to internal functions +use the internal calling convention, which means that all internal types +can be passed and types :ref:`stored in memory ` will be passed by reference and not copied. +To realize this in the EVM, code of internal library functions +and all functions called from therein will at compile time be pulled into the calling +contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. + +.. index:: using for, set + +The following example illustrates how to use libraries (but manual method +be sure to check out :ref:`using for ` for a +more advanced example to implement a set). + +:: + + pragma solidity >=0.4.22 <0.7.0; + + + library Set { + // We define a new struct datatype that will be used to + // hold its data in the calling contract. + struct Data { mapping(uint => bool) flags; } + + // Note that the first parameter is of type "storage + // reference" and thus only its storage address and not + // its contents is passed as part of the call. This is a + // special feature of library functions. It is idiomatic + // to call the first parameter `self`, if the function can + // be seen as a method of that object. + 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 contains(Data storage self, uint value) + public + view + returns (bool) + { + return self.flags[value]; + } + } + + + contract C { + Set.Data knownValues; + + function register(uint value) public { + // The library functions can be called without a + // specific instance of the library, since the + // "instance" will be the current contract. + require(Set.insert(knownValues, value)); + } + // In this contract, we can also directly access knownValues.flags, if we want. + } + +Of course, you do not have to follow this way to use +libraries: they can also be used without defining struct +data types. Functions also work without any storage +reference parameters, and they can have multiple storage reference +parameters and in any position. + +The calls to ``Set.contains``, ``Set.insert`` and ``Set.remove`` +are all compiled as calls (``DELEGATECALL``) to an external +contract/library. If you use libraries, be aware that an +actual external function call is performed. +``msg.sender``, ``msg.value`` and ``this`` will retain their values +in this call, though (prior to Homestead, because of the use of ``CALLCODE``, ``msg.sender`` and +``msg.value`` changed, though). + +The following example shows how to use :ref:`types stored in memory ` and +internal functions in libraries in order to implement +custom types without the overhead of external function calls: + +:: + + pragma solidity >=0.4.16 <0.7.0; + + library BigInt { + struct bigint { + uint[] limbs; + } + + function fromUint(uint x) internal pure returns (bigint memory r) { + r.limbs = new uint[](1); + r.limbs[0] = x; + } + + function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) { + r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); + uint carry = 0; + for (uint i = 0; i < r.limbs.length; ++i) { + uint a = limb(_a, i); + uint b = limb(_b, i); + r.limbs[i] = a + b + carry; + if (a + b < a || (a + b == uint(-1) && carry > 0)) + carry = 1; + else + carry = 0; + } + if (carry > 0) { + // too bad, we have to add a limb + uint[] memory newLimbs = new uint[](r.limbs.length + 1); + uint i; + for (i = 0; i < r.limbs.length; ++i) + newLimbs[i] = r.limbs[i]; + newLimbs[i] = carry; + r.limbs = newLimbs; + } + } + + function limb(bigint memory _a, uint _limb) internal pure returns (uint) { + return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; + } + + function max(uint a, uint b) private pure returns (uint) { + return a > b ? a : b; + } + } + + contract C { + using BigInt for BigInt.bigint; + + function f() public pure { + BigInt.bigint memory x = BigInt.fromUint(7); + BigInt.bigint memory y = BigInt.fromUint(uint(-1)); + BigInt.bigint memory z = x.add(y); + assert(z.limb(1) > 0); + } + } + +As the compiler cannot know where the library will be +deployed at, these addresses have to be filled into the +final bytecode by a linker +(see :ref:`commandline-compiler` for how to use the +commandline compiler for linking). If the addresses are not +given as arguments to the compiler, the compiled hex code +will contain placeholders of the form ``__Set______`` (where +``Set`` is the name of the library). The address can be filled +manually by replacing all those 40 symbols by the hex +encoding of the address of the library contract. + +.. note:: + Manually linking libraries on the generated bytecode is discouraged, because + it is restricted to 36 characters. + You should ask the compiler to link the libraries at the time + a contract is compiled by either using + the ``--libraries`` option of ``solc`` or the ``libraries`` key if you use + the standard-JSON interface to the compiler. + +Restrictions for libraries in comparison to contracts: + +- No state variables +- Cannot inherit nor be inherited +- Cannot receive Ether + +(These might be lifted at a later point.) + +.. _call-protection: + +Call Protection For Libraries +============================= + +As mentioned in the introduction, if a library's code is executed +using a ``CALL`` instead of a ``DELEGATECALL`` or ``CALLCODE``, +it will revert unless a ``view`` or ``pure`` function is called. + +The EVM does not provide a direct way for a contract to detect +whether it was called using ``CALL`` or not, but a contract +can use the ``ADDRESS`` opcode to find out "where" it is +currently running. The generated code compares this address +to the address used at construction time to determine the mode +of calling. + +More specifically, the runtime code of a library always starts +with a push instruction, which is a zero of 20 bytes at +compilation time. When the deploy code runs, this constant +is replaced in memory by the current address and this +modified code is stored in the contract. At runtime, +this causes the deploy time address to be the first +constant to be pushed onto the stack and the dispatcher +code compares the current address against this constant +for any non-view and non-pure function. diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst new file mode 100644 index 000000000..ed19dd046 --- /dev/null +++ b/docs/contracts/using-for.rst @@ -0,0 +1,121 @@ +.. index:: ! using for, library + +.. _using-for: + +********* +Using For +********* + +The directive ``using A for B;`` can be used to attach library +functions (from the library ``A``) to any type (``B``). +These functions will receive the object they are called on +as their first parameter (like the ``self`` variable in Python). + +The effect of ``using A for *;`` is that the functions from +the library ``A`` are attached to *any* type. + +In both situations, *all* functions in the library are attached, +even those where the type of the first parameter does not +match the type of the object. The type is checked at the +point the function is called and function overload +resolution is performed. + +The ``using A for B;`` directive is active only within the current +contract, including within all of its functions, and has no effect +outside of the contract in which it is used. The directive +may only be used inside a contract, not inside any of its functions. + +By including a library, its data types including library functions are +available without having to add further code. + +Let us rewrite the set example from the +:ref:`libraries` in this way:: + + 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; } + + 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 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; + + function register(uint value) public { + // Here, all variables of type Set.Data have + // corresponding member functions. + // The following function call is identical to + // `Set.insert(knownValues, value)` + require(knownValues.insert(value)); + } + } + +It is also possible to extend elementary types in that way:: + + pragma solidity >=0.4.16 <0.7.0; + + library Search { + function indexOf(uint[] storage self, uint value) + public + view + returns (uint) + { + for (uint i = 0; i < self.length; i++) + if (self[i] == value) return i; + return uint(-1); + } + } + + contract C { + using Search for uint[]; + uint[] data; + + function append(uint value) public { + data.push(value); + } + + function replace(uint _old, uint _new) public { + // This performs the library function call + uint index = data.indexOf(_old); + if (index == uint(-1)) + data.push(_new); + else + data[index] = _new; + } + } + +Note that all library calls are actual EVM function calls. This means that +if you pass memory or value types, a copy will be performed, even of the +``self`` variable. The only situation where no copy will be performed +is when storage reference variables are used. diff --git a/docs/contracts/visibility-and-getters.rst b/docs/contracts/visibility-and-getters.rst new file mode 100644 index 000000000..7bba31e7c --- /dev/null +++ b/docs/contracts/visibility-and-getters.rst @@ -0,0 +1,198 @@ +.. index:: ! visibility, external, public, private, internal + +.. _visibility-and-getters: + +********************** +Visibility and Getters +********************** + +Since Solidity knows two kinds of function calls (internal +ones that do not create an actual EVM call (also called +a "message call") and external +ones that do), there are four types of visibilities for +functions and state variables. + +Functions have to be specified as being ``external``, +``public``, ``internal`` or ``private``. +For state variables, ``external`` is not possible. + +``external``: + External functions are part of the contract interface, + which means they can be called from other contracts and + via transactions. An external function ``f`` cannot be called + internally (i.e. ``f()`` does not work, but ``this.f()`` works). + External functions are sometimes more efficient when + they receive large arrays of data. + +``public``: + Public functions are part of the contract interface + and can be either called internally or via + messages. For public state variables, an automatic getter + function (see below) is generated. + +``internal``: + Those functions and state variables can only be + accessed internally (i.e. from within the current contract + or contracts deriving from it), without using ``this``. + +``private``: + Private functions and state variables are only + visible for the contract they are defined in and not in + derived contracts. + +.. note:: + Everything that is inside a contract is visible to + all observers external to the blockchain. Making something ``private`` + only prevents other contracts from reading or modifying + the information, but it will still be visible to the + whole world outside of the blockchain. + +The visibility specifier is given after the type for +state variables and between parameter list and +return parameter list for functions. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract C { + function f(uint a) private pure returns (uint b) { return a + 1; } + function setData(uint a) internal { data = a; } + uint public data; + } + +In the following example, ``D``, can call ``c.getData()`` to retrieve the value of +``data`` in state storage, but is not able to call ``f``. Contract ``E`` is derived from +``C`` and, thus, can call ``compute``. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract C { + uint private data; + + function f(uint a) private pure returns(uint b) { return a + 1; } + function setData(uint a) public { data = a; } + function getData() public view returns(uint) { return data; } + function compute(uint a, uint b) internal pure returns (uint) { return a + b; } + } + + // This will not compile + contract D { + function readData() public { + C c = new C(); + uint local = c.f(7); // error: member `f` is not visible + c.setData(3); + local = c.getData(); + local = c.compute(3, 5); // error: member `compute` is not visible + } + } + + contract E is C { + function g() public { + C c = new C(); + uint val = compute(3, 5); // access to internal member (from derived to parent contract) + } + } + +.. index:: ! getter;function, ! function;getter +.. _getter-functions: + +Getter Functions +================ + +The compiler automatically creates getter functions for +all **public** state variables. For the contract given below, the compiler will +generate a function called ``data`` that does not take any +arguments and returns a ``uint``, the value of the state +variable ``data``. State variables can be initialized +when they are declared. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract C { + uint public data = 42; + } + + contract Caller { + C c = new C(); + function f() public view returns (uint) { + return c.data(); + } + } + +The getter functions have external visibility. If the +symbol is accessed internally (i.e. without ``this.``), +it evaluates to a state variable. If it is accessed externally +(i.e. with ``this.``), it evaluates to a function. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract C { + uint public data; + function x() public returns (uint) { + data = 3; // internal access + return this.data(); // external access + } + } + +If you have a ``public`` state variable of array type, then you can only retrieve +single elements of the array via the generated getter function. This mechanism +exists to avoid high gas costs when returning an entire array. You can use +arguments to specify which individual element to return, for example +``data(0)``. If you want to return an entire array in one call, then you need +to write a function, for example: + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract arrayExample { + // public state variable + uint[] public myArray; + + // Getter function generated by the compiler + /* + function myArray(uint i) returns (uint) { + return myArray[i]; + } + */ + + // function that returns entire array + function getArray() returns (uint[] memory) { + return myArray; + } + } + +Now you can use ``getArray()`` to retrieve the entire array, instead of +``myArray(i)``, which returns a single element per call. + +The next example is more complex: + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract Complex { + struct Data { + uint a; + bytes3 b; + mapping (uint => uint) map; + } + mapping (uint => mapping(bool => Data[])) public data; + } + +It generates a function of the following form. The mapping in the struct is omitted +because there is no good way to provide the key for the mapping: + +:: + + function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) { + a = data[arg1][arg2][arg3].a; + b = data[arg1][arg2][arg3].b; + } diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst new file mode 100644 index 000000000..0b13621ec --- /dev/null +++ b/docs/examples/blind-auction.rst @@ -0,0 +1,339 @@ +.. index:: auction;blind, auction;open, blind auction, open auction + +************* +Blind Auction +************* + +In this section, we will show how easy it is to create a +completely blind auction contract on Ethereum. +We will start with an open auction where everyone +can see the bids that are made and then extend this +contract into a blind auction where it is not +possible to see the actual bid until the bidding +period ends. + +.. _simple_auction: + +Simple Open Auction +=================== + +The general idea of the following simple auction contract +is that everyone can send their bids during +a bidding period. The bids already include sending +money / ether in order to bind the bidders to their +bid. If the highest bid is raised, the previously +highest bidder gets her money back. +After the end of the bidding period, the +contract has to be called manually for the +beneficiary to receive their money - contracts cannot +activate themselves. + +:: + + pragma solidity >=0.4.22 <0.7.0; + + contract SimpleAuction { + // Parameters of the auction. Times are either + // absolute unix timestamps (seconds since 1970-01-01) + // or time periods in seconds. + address payable public beneficiary; + uint public auctionEndTime; + + // Current state of the auction. + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + // Set to true at the end, disallows any change. + // By default initialized to `false`. + bool ended; + + // Events that will be emitted on changes. + event HighestBidIncreased(address bidder, uint amount); + event AuctionEnded(address winner, uint amount); + + // The following is a so-called natspec comment, + // recognizable by the three slashes. + // It will be shown when the user is asked to + // confirm a transaction. + + /// Create a simple auction with `_biddingTime` + /// seconds bidding time on behalf of the + /// beneficiary address `_beneficiary`. + constructor( + uint _biddingTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + auctionEndTime = now + _biddingTime; + } + + /// Bid on the auction with the value sent + /// together with this transaction. + /// The value will only be refunded if the + /// auction is not won. + function bid() public payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + require( + now <= auctionEndTime, + "Auction already ended." + ); + + // If the bid is not higher, send the + // money back. + require( + msg.value > highestBid, + "There already is a higher bid." + ); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); + } + + /// Withdraw a bid that was overbid. + function withdraw() public returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + if (!msg.sender.send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() public { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + require(now >= auctionEndTime, "Auction not yet ended."); + require(!ended, "auctionEnd has already been called."); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); + } + } + +Blind Auction +============= + +The previous open auction is extended to a blind auction +in the following. The advantage of a blind auction is +that there is no time pressure towards the end of +the bidding period. Creating a blind auction on a +transparent computing platform might sound like a +contradiction, but cryptography comes to the rescue. + +During the **bidding period**, a bidder does not +actually send her bid, but only a hashed version of it. +Since it is currently considered practically impossible +to find two (sufficiently long) values whose hash +values are equal, the bidder commits to the bid by that. +After the end of the bidding period, the bidders have +to reveal their bids: They send their values +unencrypted and the contract checks that the hash value +is the same as the one provided during the bidding period. + +Another challenge is how to make the auction +**binding and blind** at the same time: The only way to +prevent the bidder from just not sending the money +after they won the auction is to make her send it +together with the bid. Since value transfers cannot +be blinded in Ethereum, anyone can see the value. + +The following contract solves this problem by +accepting any value that is larger than the highest +bid. Since this can of course only be checked during +the reveal phase, some bids might be **invalid**, and +this is on purpose (it even provides an explicit +flag to place invalid bids with high value transfers): +Bidders can confuse competition by placing several +high or low invalid bids. + + +:: + + pragma solidity >0.4.23 <0.7.0; + + contract BlindAuction { + struct Bid { + bytes32 blindedBid; + uint deposit; + } + + address payable public beneficiary; + uint public biddingEnd; + uint public revealEnd; + bool public ended; + + mapping(address => Bid[]) public bids; + + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + event AuctionEnded(address winner, uint highestBid); + + /// Modifiers are a convenient way to validate inputs to + /// functions. `onlyBefore` is applied to `bid` below: + /// The new function body is the modifier's body where + /// `_` is replaced by the old function body. + modifier onlyBefore(uint _time) { require(now < _time); _; } + modifier onlyAfter(uint _time) { require(now > _time); _; } + + constructor( + uint _biddingTime, + uint _revealTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + biddingEnd = now + _biddingTime; + revealEnd = biddingEnd + _revealTime; + } + + /// Place a blinded bid with `_blindedBid` = + /// keccak256(abi.encodePacked(value, fake, secret)). + /// The sent ether is only refunded if the bid is correctly + /// revealed in the revealing phase. The bid is valid if the + /// ether sent together with the bid is at least "value" and + /// "fake" is not true. Setting "fake" to true and sending + /// not the exact amount are ways to hide the real bid but + /// still make the required deposit. The same address can + /// place multiple bids. + function bid(bytes32 _blindedBid) + public + payable + onlyBefore(biddingEnd) + { + bids[msg.sender].push(Bid({ + blindedBid: _blindedBid, + deposit: msg.value + })); + } + + /// Reveal your blinded bids. You will get a refund for all + /// correctly blinded invalid bids and for all bids except for + /// the totally highest. + function reveal( + uint[] memory _values, + bool[] memory _fake, + bytes32[] memory _secret + ) + public + onlyAfter(biddingEnd) + onlyBefore(revealEnd) + { + uint length = bids[msg.sender].length; + require(_values.length == length); + require(_fake.length == length); + require(_secret.length == length); + + uint refund; + for (uint i = 0; i < length; i++) { + Bid storage bidToCheck = bids[msg.sender][i]; + (uint value, bool fake, bytes32 secret) = + (_values[i], _fake[i], _secret[i]); + if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) { + // Bid was not actually revealed. + // Do not refund deposit. + continue; + } + refund += bidToCheck.deposit; + if (!fake && bidToCheck.deposit >= value) { + if (placeBid(msg.sender, value)) + refund -= value; + } + // Make it impossible for the sender to re-claim + // the same deposit. + bidToCheck.blindedBid = bytes32(0); + } + msg.sender.transfer(refund); + } + + // This is an "internal" function which means that it + // can only be called from the contract itself (or from + // derived contracts). + function placeBid(address bidder, uint value) internal + returns (bool success) + { + if (value <= highestBid) { + return false; + } + if (highestBidder != address(0)) { + // Refund the previously highest bidder. + pendingReturns[highestBidder] += highestBid; + } + highestBid = value; + highestBidder = bidder; + return true; + } + + /// Withdraw a bid that was overbid. + function withdraw() public { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `transfer` returns (see the remark above about + // conditions -> effects -> interaction). + pendingReturns[msg.sender] = 0; + + msg.sender.transfer(amount); + } + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() + public + onlyAfter(revealEnd) + { + require(!ended); + emit AuctionEnded(highestBidder, highestBid); + ended = true; + beneficiary.transfer(highestBid); + } + } \ No newline at end of file diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst new file mode 100644 index 000000000..a428463bd --- /dev/null +++ b/docs/examples/micropayment.rst @@ -0,0 +1,430 @@ +******************** +Micropayment Channel +******************** + +In this section we will learn how to build an example implementation +of a payment channel. It uses cryptographic signatures to make +repeated transfers of Ether between the same parties secure, instantaneous, and +without transaction fees. For the example, we need to understand how to +sign and verify signatures, and setup the payment channel. + +Creating and verifying signatures +================================= + +Imagine Alice wants to send a quantity of Ether to Bob, i.e. +Alice is the sender and the Bob is the recipient. + +Alice only needs to send cryptographically signed messages off-chain +(e.g. via email) to Bob and it is similar to writing checks. + +Alice and Bob use signatures to authorise transactions, which is possible with smart contracts on Ethereum. +Alice will build a simple smart contract that lets her transmit Ether, but instead of calling a function herself +to initiate a payment, she will let Bob do that, and therefore pay the transaction fee. + +The contract will work as follows: + + 1. Alice deploys the ``ReceiverPays`` contract, attaching enough Ether to cover the payments that will be made. + 2. Alice authorises a payment by signing a message with their private key. + 3. Alice sends the cryptographically signed message to Bob. The message does not need to be kept secret + (explained later), and the mechanism for sending it does not matter. + 4. Bob claims their payment by presenting the signed message to the smart contract, it verifies the + authenticity of the message and then releases the funds. + +Creating the signature +---------------------- + +Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. +In this tutorial, we will sign messages in the browser using `web3.js `_ and `MetaMask `_, using the method described in `EIP-762 `_, +as it provides a number of other security benefits. + +:: + + /// Hashing first makes things easier + var hash = web3.utils.sha3("message to sign"); + web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); }); + +.. note:: + The ``web3.eth.personal.sign`` prepends the length of the message to the signed data. Since we hash first, the message will always be exactly 32 bytes long, and thus this length prefix is always the same. + +What to Sign +------------ + +For a contract that fulfils payments, the signed message must include: + + 1. The recipient's address. + 2. The amount to be transferred. + 3. Protection against replay attacks. + +A replay attack is when a signed message is reused to claim authorization for +a second action. +To avoid replay attacks we use the same as in Ethereum transactions +themselves, a so-called nonce, which is the number of transactions sent by an +account. +The smart contract checks if a nonce is used multiple times. + +Another type of replay attack can occur when the owner deploys a ``ReceiverPays`` smart contract, makes some payments, and then destroys the contract. Later, they decide to deploy the ``RecipientPays`` smart contract again, but the new contract does not know the nonces used in the previous deployment, so the attacker can use the old messages again. + +Alice can protect against this attack by including the contract's address in the message, and only messages containing the contract's address itself will be accepted. You can find an example of this in the first two lines of the ``claimPayment()`` function of the full contract at the end of this section. + +Packing arguments +----------------- + +Now that we have identified what information to include in the signed message, +we are ready to put the message together, hash it, and sign it. For simplicity, +we concatenate the data. The `ethereumjs-abi `_ +library provides a function called ``soliditySHA3`` that mimics the behaviour of +Solidity's ``keccak256`` function applied to arguments encoded using ``abi.encodePacked``. +Here is a JavaScript function that creates the proper signature for the ``ReceiverPays`` example: + +:: + + // recipient is the address that should be paid. + // amount, in wei, specifies how much ether should be sent. + // nonce can be any unique number to prevent replay attacks + // contractAddress is used to prevent cross-contract replay attacks + function signPayment(recipient, amount, nonce, contractAddress, callback) { + var hash = "0x" + abi.soliditySHA3( + ["address", "uint256", "uint256", "address"], + [recipient, amount, nonce, contractAddress] + ).toString("hex"); + + web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback); + } + +Recovering the Message Signer in Solidity +----------------------------------------- + +In general, ECDSA signatures consist of two parameters, ``r`` and ``s``. Signatures in Ethereum include a third parameter called ``v``, that you can use to verify which account's private key was used to sign the message, and the transaction's sender. Solidity provides a built-in function `ecrecover `_ that accepts a message along with the ``r``, ``s`` and ``v`` parameters and returns the address that was used to sign the message. + +Extracting the Signature Parameters +----------------------------------- + +Signatures produced by web3.js are the concatenation of ``r``, ``s`` and ``v``, so the first step is to split these parameters apart. You can do this on the client-side, but doing it inside the smart contract means you only need to send one signature parameter rather than three. Splitting apart a byte array into component parts is a mess, so we use `inline assembly `_ to do the job in the ``splitSignature`` function (the third function in the full contract at the end of this section). + +Computing the Message Hash +-------------------------- + +The smart contract needs to know exactly what parameters were signed, and so it +must recreate the message from the parameters and use that for signature verification. +The functions ``prefixed`` and ``recoverSigner`` do this in the ``claimPayment`` function. + +The full contract +----------------- + +:: + + pragma solidity >=0.4.24 <0.7.0; + + contract ReceiverPays { + address owner = msg.sender; + + mapping(uint256 => bool) usedNonces; + + constructor() public payable {} + + function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) public { + require(!usedNonces[nonce]); + usedNonces[nonce] = true; + + // this recreates the message that was signed on the client + bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this))); + + require(recoverSigner(message, signature) == owner); + + msg.sender.transfer(amount); + } + + /// destroy the contract and reclaim the leftover funds. + function kill() public { + require(msg.sender == owner); + selfdestruct(msg.sender); + } + + /// signature methods. + function splitSignature(bytes memory sig) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + require(sig.length == 65); + + assembly { + // first 32 bytes, after the length prefix. + r := mload(add(sig, 32)) + // second 32 bytes. + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes). + v := byte(0, mload(add(sig, 96))) + } + + return (v, r, s); + } + + function recoverSigner(bytes32 message, bytes memory sig) + internal + pure + returns (address) + { + (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); + + return ecrecover(message, v, r, s); + } + + /// builds a prefixed hash to mimic the behavior of eth_sign. + function prefixed(bytes32 hash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + } + + +Writing a Simple Payment Channel +================================ + +Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees. + +What is a Payment Channel? +-------------------------- + +Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps: + + 1. Alice funds a smart contract with Ether. This "opens" the payment channel. + 2. Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment. + 3. Bob "closes" the payment channel, withdrawing their portion of the Ether and sending the remainder back to the sender. + +.. note:: + Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers. + +Bob is guaranteed to receive their funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover their funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, or for a longer relationship, such as paying an employee an hourly wage, a payment could last for months or years. + +Opening the Payment Channel +--------------------------- + +To open the payment channel, Alice deploys the smart contract, attaching the Ether to be escrowed and specifying the intended recipient and a maximum duration for the channel to exist. This is the function ``SimplePaymentChannel`` in the contract, at the end of this section. + +Making Payments +--------------- + +Alice makes payments by sending signed messages to Bob. +This step is performed entirely outside of the Ethereum network. +Messages are cryptographically signed by the sender and then transmitted directly to the recipient. + +Each message includes the following information: + + * The smart contract's address, used to prevent cross-contract replay attacks. + * The total amount of Ether that is owed the recipient so far. + +A payment channel is closed just once, at the end of a series of transfers. +Because of this, only one of the messages sent is redeemed. This is why +each message specifies a cumulative total amount of Ether owed, rather than the +amount of the individual micropayment. The recipient will naturally choose to +redeem the most recent message because that is the one with the highest total. +The nonce per-message is not needed anymore, because the smart contract only honors a single message. The address of the smart contract is still used +to prevent a message intended for one payment channel from being used for a different channel. + +Here is the modified JavaScript code to cryptographically sign a message from the previous section: + +:: + + function constructPaymentMessage(contractAddress, amount) { + return abi.soliditySHA3( + ["address", "uint256"], + [contractAddress, amount] + ); + } + + function signMessage(message, callback) { + web3.eth.personal.sign( + "0x" + message.toString("hex"), + web3.eth.defaultAccount, + callback + ); + } + + // contractAddress is used to prevent cross-contract replay attacks. + // amount, in wei, specifies how much Ether should be sent. + + function signPayment(contractAddress, amount, callback) { + var message = constructPaymentMessage(contractAddress, amount); + signMessage(message, callback); + } + + +Closing the Payment Channel +--------------------------- + +When Bob is ready to receive their funds, it is time to +close the payment channel by calling a ``close`` function on the smart contract. +Closing the channel pays the recipient the Ether they are owed and destroys the contract, sending any remaining Ether back to Alice. To close the channel, Bob needs to provide a message signed by Alice. + +The smart contract must verify that the message contains a valid signature from the sender. +The process for doing this verification is the same as the process the recipient uses. +The Solidity functions ``isValidSignature`` and ``recoverSigner`` work just like their +JavaScript counterparts in the previous section, with the latter function borrowed from the ``ReceiverPays`` contract. + +Only the payment channel recipient can call the ``close`` function, +who naturally passes the most recent payment message because that message +carries the highest total owed. If the sender were allowed to call this function, +they could provide a message with a lower amount and cheat the recipient out of what they are owed. + +The function verifies the signed message matches the given parameters. +If everything checks out, the recipient is sent their portion of the Ether, +and the sender is sent the rest via a ``selfdestruct``. +You can see the ``close`` function in the full contract. + +Channel Expiration +------------------- + +Bob can close the payment channel at any time, but if they fail to do so, +Alice needs a way to recover their escrowed funds. An *expiration* time was set +at the time of contract deployment. Once that time is reached, Alice can call +``claimTimeout`` to recover their funds. You can see the ``claimTimeout`` function in the full contract. + +After this function is called, Bob can no longer receive any Ether, +so it is important that Bob closes the channel before the expiration is reached. + +The full contract +----------------- + +:: + + pragma solidity >=0.4.24 <0.7.0; + + contract SimplePaymentChannel { + address payable public sender; // The account sending payments. + address payable public recipient; // The account receiving the payments. + uint256 public expiration; // Timeout in case the recipient never closes. + + constructor (address payable _recipient, uint256 duration) + public + payable + { + sender = msg.sender; + recipient = _recipient; + expiration = now + duration; + } + + function isValidSignature(uint256 amount, bytes memory signature) + internal + view + returns (bool) + { + bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount))); + + // check that the signature is from the payment sender + return recoverSigner(message, signature) == sender; + } + + /// the recipient can close the channel at any time by presenting a + /// signed amount from the sender. the recipient will be sent that amount, + /// and the remainder will go back to the sender + function close(uint256 amount, bytes memory signature) public { + require(msg.sender == recipient); + require(isValidSignature(amount, signature)); + + recipient.transfer(amount); + selfdestruct(sender); + } + + /// the sender can extend the expiration at any time + function extend(uint256 newExpiration) public { + require(msg.sender == sender); + require(newExpiration > expiration); + + expiration = newExpiration; + } + + /// if the timeout is reached without the recipient closing the channel, + /// then the Ether is released back to the sender. + function claimTimeout() public { + require(now >= expiration); + selfdestruct(sender); + } + + /// All functions below this are just taken from the chapter + /// 'creating and verifying signatures' chapter. + + function splitSignature(bytes memory sig) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + require(sig.length == 65); + + assembly { + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + + return (v, r, s); + } + + function recoverSigner(bytes32 message, bytes memory sig) + internal + pure + returns (address) + { + (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); + + return ecrecover(message, v, r, s); + } + + /// builds a prefixed hash to mimic the behavior of eth_sign. + function prefixed(bytes32 hash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + } + + +.. note:: + The function ``splitSignature`` does not use all security + checks. A real implementation should use a more rigorously tested library, + such as openzepplin's `version `_ of this code. + +Verifying Payments +------------------ + +Unlike in the previous section, messages in a payment channel aren't +redeemed right away. The recipient keeps track of the latest message and +redeems it when it's time to close the payment channel. This means it's +critical that the recipient perform their own verification of each message. +Otherwise there is no guarantee that the recipient will be able to get paid +in the end. + +The recipient should verify each message using the following process: + + 1. Verify that the contact address in the message matches the payment channel. + 2. Verify that the new total is the expected amount. + 3. Verify that the new total does not exceed the amount of Ether escrowed. + 4. Verify that the signature is valid and comes from the payment channel sender. + +We'll use the `ethereumjs-util `_ +library to write this verification. The final step can be done a number of ways, +and we use JavaScript. The following code borrows the `constructMessage` function from the signing **JavaScript code** above: + +:: + + // this mimics the prefixing behavior of the eth_sign JSON-RPC method. + function prefixed(hash) { + return ethereumjs.ABI.soliditySHA3( + ["string", "bytes32"], + ["\x19Ethereum Signed Message:\n32", hash] + ); + } + + function recoverSigner(message, signature) { + var split = ethereumjs.Util.fromRpcSig(signature); + var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s); + var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex"); + return signer; + } + + function isValidSignature(contractAddress, amount, signature, expectedSigner) { + var message = prefixed(constructPaymentMessage(contractAddress, amount)); + var signer = recoverSigner(message, signature); + return signer.toLowerCase() == + ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase(); + } diff --git a/docs/examples/modular.rst b/docs/examples/modular.rst new file mode 100644 index 000000000..928d473bc --- /dev/null +++ b/docs/examples/modular.rst @@ -0,0 +1,65 @@ +.. index:: contract;modular, modular contract + +***************** +Modular Contracts +***************** + +A modular approach to building your contracts helps you reduce the complexity +and improve the readability which will help to identify bugs and vulnerabilities +during development and code review. +If you specify and control the behaviour or each module in isolation, the +interactions you have to consider are only those between the module specifications +and not every other moving part of the contract. +In the example below, the contract uses the ``move`` method +of the ``Balances`` :ref:`library ` to check that balances sent between +addresses match what you expect. In this way, the ``Balances`` library +provides an isolated component that properly tracks balances of accounts. +It is easy to verify that the ``Balances`` library never produces negative balances or overflows +and the sum of all balances is an invariant across the lifetime of the contract. + +:: + + pragma solidity >=0.4.22 <0.7.0; + + library Balances { + function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal { + require(balances[from] >= amount); + require(balances[to] + amount >= balances[to]); + balances[from] -= amount; + balances[to] += amount; + } + } + + contract Token { + mapping(address => uint256) balances; + using Balances for *; + mapping(address => mapping (address => uint256)) allowed; + + event Transfer(address from, address to, uint amount); + event Approval(address owner, address spender, uint amount); + + function balanceOf(address tokenOwner) public view returns (uint balance) { + return balances[tokenOwner]; + } + function transfer(address to, uint amount) public returns (bool success) { + balances.move(msg.sender, to, amount); + emit Transfer(msg.sender, to, amount); + return true; + + } + + function transferFrom(address from, address to, uint amount) public returns (bool success) { + require(allowed[from][msg.sender] >= amount); + allowed[from][msg.sender] -= amount; + balances.move(from, to, amount); + emit Transfer(from, to, amount); + return true; + } + + function approve(address spender, uint tokens) public returns (bool success) { + require(allowed[msg.sender][spender] == 0, ""); + allowed[msg.sender][spender] = tokens; + emit Approval(msg.sender, spender, tokens); + return true; + } + } diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst new file mode 100644 index 000000000..c4e684ce1 --- /dev/null +++ b/docs/examples/safe-remote.rst @@ -0,0 +1,108 @@ +.. index:: purchase, remote purchase, escrow + +******************** +Safe Remote Purchase +******************** + +:: + + pragma solidity >=0.4.22 <0.7.0; + + contract Purchase { + uint public value; + 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. + // Division will truncate if it is an odd number. + // Check via multiplication that it wasn't an odd number. + constructor() public payable { + seller = msg.sender; + value = msg.value / 2; + require((2 * value) == msg.value, "Value has to be even."); + } + + modifier condition(bool _condition) { + require(_condition); + _; + } + + modifier onlyBuyer() { + require( + msg.sender == buyer, + "Only buyer can call this." + ); + _; + } + + modifier onlySeller() { + require( + msg.sender == seller, + "Only seller can call this." + ); + _; + } + + modifier inState(State _state) { + require( + state == _state, + "Invalid state." + ); + _; + } + + event Aborted(); + event PurchaseConfirmed(); + event ItemReceived(); + + /// Abort the purchase and reclaim the ether. + /// Can only be called by the seller before + /// the contract is locked. + function abort() + public + onlySeller + inState(State.Created) + { + emit Aborted(); + state = State.Inactive; + seller.transfer(address(this).balance); + } + + /// Confirm the purchase as buyer. + /// Transaction has to include `2 * value` ether. + /// The ether will be locked until confirmReceived + /// is called. + function confirmPurchase() + public + inState(State.Created) + condition(msg.value == (2 * value)) + payable + { + emit PurchaseConfirmed(); + buyer = msg.sender; + state = State.Locked; + } + + /// Confirm that you (the buyer) received the item. + /// This will release the locked ether. + function confirmReceived() + public + onlyBuyer + inState(State.Locked) + { + emit ItemReceived(); + // It is important to change the state first because + // otherwise, the contracts called using `send` below + // can call in again here. + state = State.Inactive; + + // NOTE: This actually allows both the buyer and the seller to + // block the refund - the withdraw pattern should be used. + + buyer.transfer(value); + seller.transfer(address(this).balance); + } + } \ No newline at end of file diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst new file mode 100644 index 000000000..e4b6da200 --- /dev/null +++ b/docs/examples/voting.rst @@ -0,0 +1,191 @@ +.. index:: voting, ballot + +.. _voting: + +****** +Voting +****** + +The following contract is quite complex, but showcases +a lot of Solidity's features. It implements a voting +contract. Of course, the main problems of electronic +voting is how to assign voting rights to the correct +persons and how to prevent manipulation. We will not +solve all problems here, but at least we will show +how delegated voting can be done so that vote counting +is **automatic and completely transparent** at the +same time. + +The idea is to create one contract per ballot, +providing a short name for each option. +Then the creator of the contract who serves as +chairperson will give the right to vote to each +address individually. + +The persons behind the addresses can then choose +to either vote themselves or to delegate their +vote to a person they trust. + +At the end of the voting time, ``winningProposal()`` +will return the proposal with the largest number +of votes. + +:: + + pragma solidity >=0.4.22 <0.7.0; + + /// @title Voting with delegation. + contract Ballot { + // This declares a new complex type which will + // be used for variables later. + // It will represent a single voter. + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + // This is a type for a single proposal. + struct Proposal { + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + // This declares a state variable that + // stores a `Voter` struct for each possible address. + mapping(address => Voter) public voters; + + // A dynamically-sized array of `Proposal` structs. + Proposal[] public proposals; + + /// Create a new ballot to choose one of `proposalNames`. + constructor(bytes32[] memory proposalNames) public { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + // For each of the provided proposal names, + // create a new proposal object and add it + // to the end of the array. + for (uint i = 0; i < proposalNames.length; i++) { + // `Proposal({...})` creates a temporary + // Proposal object and `proposals.push(...)` + // appends it to the end of `proposals`. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + // Give `voter` the right to vote on this ballot. + // May only be called by `chairperson`. + function giveRightToVote(address voter) public { + // If the first argument of `require` evaluates + // to `false`, execution terminates and all + // changes to the state and to Ether balances + // are reverted. + // This used to consume all gas in old EVM versions, but + // not anymore. + // It is often a good idea to use `require` to check if + // functions are called correctly. + // As a second argument, you can also provide an + // explanation about what went wrong. + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /// Delegate your vote to the voter `to`. + function delegate(address to) public { + // assigns reference + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + + require(to != msg.sender, "Self-delegation is disallowed."); + + // Forward the delegation as long as + // `to` also delegated. + // In general, such loops are very dangerous, + // because if they run too long, they might + // need more gas than is available in a block. + // In this case, the delegation will not be executed, + // but in other situations, such loops might + // cause a contract to get "stuck" completely. + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + + // Since `sender` is a reference, this + // modifies `voters[msg.sender].voted` + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /// Give your vote (including votes delegated to you) + /// to proposal `proposals[proposal].name`. + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If `proposal` is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /// @dev Computes the winning proposal taking all + /// previous votes into account. + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + // Calls winningProposal() function to get the index + // of the winner contained in the proposals array and then + // returns the name of the winner + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } + } + + +Possible Improvements +===================== + +Currently, many transactions are needed to assign the rights +to vote to all participants. Can you think of a better way? diff --git a/docs/lll.rst b/docs/lll.rst index d9409bf84..16be829e6 100644 --- a/docs/lll.rst +++ b/docs/lll.rst @@ -9,6 +9,13 @@ LLL is a low-level language for the EVM with an s-expressions syntax. The Solidity repository contains an LLL compiler, which shares the assembler subsystem with Solidity. However, apart from maintaining that it still compiles, no other improvements are made to it. +It is not built unless specifically requested: + +.. code-block:: bash + + $ cmake -DLLL=ON .. + $ cmake --build . + .. warning:: The LLL codebase is deprecated and will be removed from the Solidity repository in the future. diff --git a/docs/natspec-format.rst b/docs/natspec-format.rst new file mode 100644 index 000000000..5c9d98736 --- /dev/null +++ b/docs/natspec-format.rst @@ -0,0 +1,202 @@ +.. _natspec: + +############## +NatSpec Format +############## + +Solidity contracts can use a special form of comments to provide rich +documentation for functions, return variables and more. This special form is +named the Ethereum Natural Language Specification Format (NatSpec). + +This documentation is segmented into developer-focused messages and end-user-facing +messages. These messages may be shown to the end user (the human) at the +time that they will interact with the contract (i.e. sign a transaction). + +It is recommended that Solidity contracts are fully annotated using NatSpec for +all public interfaces (everything in the ABI). + +NatSpec includes the formatting for comments that the smart contract author will +use, and which are understood by the Solidity compiler. Also detailed below is +output of the Solidity compiler, which extracts these comments into a machine-readable +format. + +.. _header-doc-example: + +Documentation Example +===================== + +Documentation is inserted above each ``class``, ``interface`` and +``function`` using the doxygen notation format. + +- For Solidity you may choose ``///`` for single or multi-line + comments, or ``/**`` and ending with ``*/``. + +- For Vyper, use ``"""`` indented to the inner contents with bare + comments. See `Vyper + 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. + + 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 + + pragma solidity ^0.5.6; + + /// @title A simulator for trees + /// @author Larry A. Gardner + /// @notice You can use this contract for only the most basic simulation + /// @dev All function calls are currently implemented without side effects + contract Tree { + /// @author Mary A. Botanist + /// @notice Calculate tree age in years, rounded up, for live trees + /// @dev The Alexandr N. Tetearing algorithm could increase precision + /// @param rings The number of rings from dendrochronological sample + /// @return age in years, rounded up for partial years + function age(uint256 rings) external pure returns (uint256) { + return rings + 1; + } + } + +.. _header-tags: + +Tags +==== + +All tags are optional. The following table explains the purpose of each +NatSpec tag and where it may be used. As a special case, if no tags are +used then the Solidity compiler will interpret a `///` or `/**` comment +in the same way as if it were tagged with `@notice`. + +=========== =============================================================================== ============================= +Tag Context +=========== =============================================================================== ============================= +``@title`` A title that should describe the contract/interface contract, interface +``@author`` The name of the author contract, interface, function +``@notice`` Explain to an end user what this does contract, interface, function +``@dev`` Explain to a developer any extra details contract, interface, function +``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function +``@return`` Documents the return type of a contract's function function +=========== =============================================================================== ============================= + +If your function returns multiple values, like ``(int quotient, int remainder)`` +then use multiple ``@return`` statements in the same format as the +``@param`` statements. + +.. _header-dynamic: + +Dynamic expressions +------------------- + +The Solidity compiler will pass through NatSpec documentation from your Solidity +source code to the JSON output as described in this guide. The consumer of this +JSON output, for example the end-user client software, may present this to the end-user directly or it may apply some pre-processing. + +For example, some client software will render: + +.. code:: solidity + + /// @notice This function will multiply `a` by 7 + +to the end-user as: + +.. code:: text + + This function will multiply 10 by 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 +`the radspec project `__. + +.. _header-inheritance: + +Inheritance Notes +----------------- + +Currently it is undefined whether a contract with a function having no +NatSpec will inherit the NatSpec of a parent contract/interface for that +same function. + +.. _header-output: + +Documentation Output +==================== + +When parsed by the compiler, documentation such as the one from the +above example will produce two different JSON files. One is meant to be +consumed by the end user as a notice when a function is executed and the +other to be used by the developer. + +If the above contract is saved as ``ex1.sol`` then you can generate the +documentation using: + +.. code:: + + solc --userdoc --devdoc ex1.sol + +And the output is below. + +.. _header-user-doc: + +User Documentation +------------------ + +The above documentation will produce the following user documentation +JSON file as output: + +.. code:: + + { + "methods" : + { + "age(uint256)" : + { + "notice" : "Calculate tree age in years, rounded up, for live trees" + } + }, + "notice" : "You can use this contract for only the most basic simulation" + } + +Note that the key by which to find the methods is the function's +canonical signature as defined in the `Contract +ABI `__ and not simply the function's +name. + +.. _header-developer-doc: + +Developer Documentation +----------------------- + +Apart from the user documentation file, a developer documentation JSON +file should also be produced and should look like this: + +.. code:: + + { + "author" : "Larry A. Gardner", + "details" : "All function calls are currently implemented without side effects", + "methods" : + { + "age(uint256)" : + { + "author" : "Mary A. Botanist", + "details" : "The Alexandr N. Tetearing algorithm could increase precision", + "params" : + { + "rings" : "The number of rings from dendrochronological sample" + }, + "return" : "age in years, rounded up for partial years" + } + }, + "title" : "A simulator for trees" + } diff --git a/docs/resources.rst b/docs/resources.rst index e9e656922..a3a89113a 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -21,18 +21,23 @@ Solidity Integrations * Generic: - * `EthFiddle `_ - Solidity IDE in the Browser. Write and share your solidity code. Uses server-side components. + Solidity IDE in the Browser. Write and share your Solidity code. Uses server-side components. * `Remix `_ Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. + * `Solhint `_ + Solidity linter that provides security, style guide and best practice rules for smart contract validation. + + * `Solidity IDE `_ + Browser-based IDE with integrated compiler, Ganache and local file system support. + * `Solium `_ Linter to identify and fix style and security issues in Solidity. - * `Solhint `_ - Solidity linter that provides security, style guide and best practice rules for smart contract validation. + * `Superblocks Lab `_ + Browser-based IDE. Built-in browser-based VM and Metamask integration (one click deployment to Testnet/Mainnet). * Atom: @@ -119,6 +124,9 @@ Solidity Tools * `EVM Lab `_ Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer with gas cost display. +* `Universal Mutator `_ + A tool for mutation generation, with configurable rules and support for Solidity and Vyper. + .. note:: Information like variable names, comments, and source code formatting is lost in the compilation process and it is not possible to completely recover the original source code. Decompiling smart contracts to view the original source code might not be possible, or the end result that useful. diff --git a/docs/types.rst b/docs/types.rst index d4f7f224e..8a81db60c 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -1,6 +1,5 @@ ===== Types -===== .. _types-fixed-point-numbers: diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst new file mode 100644 index 000000000..51e373405 --- /dev/null +++ b/docs/types/conversion.rst @@ -0,0 +1,130 @@ +.. index:: ! type;conversion, ! cast + +.. _types-conversion-elementary-types: + +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). +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 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 and expect! + +Take the following example that converts a negative ``int`` to a ``uint``: + +:: + + int y = -3; + uint x = uint(y); + +At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex +characters), which is -3 in the two's complement representation of 256 bits. + +If an integer is explicitly converted to a smaller type, higher-order bits are +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). +The result of the conversion will compare equal to the original integer:: + + uint16 a = 0x1234; + uint32 b = uint32(a); // b will be 0x00001234 now + assert(a == b); + +Fixed-size bytes types behave differently during conversions. They can be thought of as +sequences of individual bytes and converting to a smaller type will cut off the +sequence:: + + bytes2 a = 0x1234; + bytes1 b = bytes1(a); // b will be 0x12 + +If a fixed-size bytes type is explicitly converted to a larger type, it is padded on +the right. Accessing the byte at a fixed index will result in the same value before and +after the conversion (if the index is still in range):: + + bytes2 a = 0x1234; + bytes4 b = bytes4(a); // b will be 0x12340000 + assert(a[0] == b[0]); + assert(a[1] == b[1]); + +Since integers and fixed-size byte arrays behave differently when truncating or +padding, explicit conversions between integers and fixed-size byte arrays are only allowed, +if both have the same size. If you want to convert between integers and fixed-size byte arrays of +different size, you have to use intermediate conversions that make the desired truncation and padding +rules explicit:: + + bytes2 a = 0x1234; + uint32 b = uint16(a); // b will be 0x00001234 + uint32 c = uint32(bytes4(a)); // c will be 0x12340000 + uint8 d = uint8(uint16(a)); // d will be 0x34 + uint8 e = uint8(bytes1(a)); // e will be 0x12 + +.. _types-conversion-literals: + +Conversions between Literals and Elementary Types +================================================= + +Integer Types +------------- + +Decimal and hexadecimal number literals can be implicitly converted to any integer type +that is large enough to represent it without truncation:: + + uint8 a = 12; // fine + uint32 b = 1234; // fine + uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456 + +Fixed-Size Byte Arrays +---------------------- + +Decimal number literals cannot be implicitly converted to fixed-size byte arrays. Hexadecimal +number literals can be, but only if the number of hex digits exactly fits the size of the bytes +type. As an exception both decimal and hexadecimal literals which have a value of zero can be +converted to any fixed-size bytes type:: + + bytes2 a = 54321; // not allowed + bytes2 b = 0x12; // not allowed + bytes2 c = 0x123; // not allowed + bytes2 d = 0x1234; // fine + bytes2 e = 0x0012; // fine + bytes4 f = 0; // fine + bytes4 g = 0x0; // fine + +String literals and hex string literals can be implicitly converted to fixed-size byte arrays, +if their number of characters matches the size of the bytes type:: + + bytes2 a = hex"1234"; // fine + bytes2 b = "xy"; // fine + bytes2 c = hex"12"; // not allowed + bytes2 d = hex"123"; // not allowed + bytes2 e = "x"; // not allowed + bytes2 f = "xyz"; // not allowed + +Addresses +--------- + +As described in :ref:`address_literals`, hex literals of the correct size that pass the checksum +test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type. + +Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``. diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst new file mode 100644 index 000000000..a7d607786 --- /dev/null +++ b/docs/types/mapping-types.rst @@ -0,0 +1,59 @@ +.. index:: !mapping +.. _mapping-types: + +Mapping Types +============= + +You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``. +The ``_KeyType`` can be any elementary type. This means it can be any of +the built-in value types plus ``bytes`` and ``string``. User-defined +or complex types like contract types, enums, mappings, structs and any array type +apart from ``bytes`` and ``string`` are not allowed. +``_ValueType`` can be any type, including mappings. + +You can think of mappings as `hash tables `_, which are virtually initialised +such that every possible key exists and is mapped to a value whose +byte-representation is all zeros, a type's :ref:`default value `. The similarity ends there, the key data is not stored in a +mapping, only its ``keccak256`` hash is used to look up the value. + +Because of this, mappings do not have a length or a concept of a key or +value being set, and therefore cannot be erased without extra information +regarding the assigned keys (see :ref:`clearing-mappings`). + +Mappings can only have a data location of ``storage`` and thus +are allowed for state variables, as storage reference types +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 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``. +If ``_ValueType`` is an array or a mapping, the getter has one parameter for +each ``_KeyType``, recursively. For example with a mapping: + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract MappingExample { + mapping(address => uint) public balances; + + function update(uint newBalance) public { + balances[msg.sender] = newBalance; + } + } + + contract MappingUser { + function f() public returns (uint) { + MappingExample m = new MappingExample(); + m.update(100); + return m.balances(address(this)); + } + } + + +.. note:: + Mappings are not iterable, but it is possible to implement a data structure + on top of them. For an example, see `iterable mapping `_. diff --git a/docs/types/operators.rst b/docs/types/operators.rst new file mode 100644 index 000000000..afbe2a742 --- /dev/null +++ b/docs/types/operators.rst @@ -0,0 +1,49 @@ +.. index:: assignment, ! delete, lvalue + +Operators Involving LValues +=========================== + +If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands: + +``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change. + +.. _delete: + +delete +------ + +``delete a`` assigns the initial value for the type to ``a``. I.e. for integers it is +equivalent to ``a = 0``, but it can also be used on arrays, where it assigns a dynamic +array of length zero or a static array of the same length with all elements set to their +initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and leaves +all other elements and the length of the array untouched. This especially means that it leaves +a gap in the array. If you plan to remove items, a mapping is probably a better choice. + +For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat: + +``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If ``a`` is a mapping, then ``delete a[x]`` will delete the value stored at ``x``. + +It is important to note that ``delete a`` really behaves like an assignment to ``a``, i.e. it stores a new object in ``a``. +This distinction is visible when ``a`` is reference variable: It will only reset ``a`` itself, not the +value it referred to previously. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract DeleteExample { + uint data; + uint[] dataArray; + + function f() public { + uint x = data; + delete x; // sets x to 0, does not affect data + delete data; // sets data to 0, does not affect x + uint[] storage y = dataArray; + delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also + // y is affected which is an alias to the storage object + // On the other hand: "delete y" is not valid, as assignments to local variables + // referencing storage objects can only be made from existing storage objects. + assert(y.length == 0); + } + } diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst new file mode 100644 index 000000000..de1e5f543 --- /dev/null +++ b/docs/types/reference-types.rst @@ -0,0 +1,417 @@ +.. index:: ! type;reference, ! reference type, storage, memory, location, array, struct + +.. _reference-types: + +Reference Types +=============== + +Values of reference type can be modified through multiple different names. +Contrast this with value types where you get an independent copy whenever +a variable of value type is used. Because of that, reference types have to be handled +more carefully than value types. Currently, reference types comprise structs, +arrays and mappings. If you use a reference type, you always have to explicitly +provide the data area where the type is stored: ``memory`` (whose lifetime is limited +to a function call), ``storage`` (the location where the state variables are stored) +or ``calldata`` (special data location that contains the function arguments, +only available for external function call parameters). + +An assignment or type conversion that changes the data location will always incur an automatic copy operation, +while assignments inside the same data location only copy in some cases for storage types. + +.. _data-location: + +Data location +------------- + +Every reference type, i.e. *arrays* and *structs*, has an additional +annotation, the "data location", about where it is stored. There are three data locations: +``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract +functions and is required for this type of parameter. Calldata is a non-modifiable, +non-persistent area where function arguments are stored, and behaves mostly like memory. + + +.. note:: + Prior to version 0.5.0 the data location could be omitted, and would default to different locations + depending on the kind of variable, function type, etc., but all complex types must now give an explicit + data location. + +.. _data-location-assignment: + +Data location and assignment behaviour +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Data locations are not only relevant for persistency of data, but also for the semantics of assignments: + +* Assignments between ``storage`` and ``memory`` (or from ``calldata``) always create an independent copy. +* Assignments from ``memory`` to ``memory`` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data. +* Assignments from ``storage`` to a local storage variable also only assign a reference. +* All other assignments to ``storage`` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference. + +:: + + pragma solidity >=0.4.0 <0.7.0; + + contract C { + uint[] x; // the data location of x is storage + + // the data location of memoryArray is memory + function f(uint[] memory memoryArray) public { + x = memoryArray; // works, copies the whole array to storage + uint[] storage y = x; // works, assigns a pointer, data location of y is storage + y[7]; // fine, returns the 8th element + y.length = 2; // fine, modifies x through y + delete x; // fine, clears the array, also modifies y + // The following does not work; it would need to create a new temporary / + // unnamed array in storage, but storage is "statically" allocated: + // y = memoryArray; + // This does not work either, since it would "reset" the pointer, but there + // is no sensible location it could point to. + // delete y; + g(x); // calls g, handing over a reference to x + h(x); // calls h and creates an independent, temporary copy in memory + } + + function g(uint[] storage) internal pure {} + function h(uint[] memory) public pure {} + } + +.. index:: ! array + +.. _arrays: + +Arrays +------ + +Arrays can have a compile-time fixed size, or they can have a dynamic size. + +The type of an array of fixed size ``k`` and element type ``T`` is written as ``T[k]``, +and an array of dynamic size as ``T[]``. + +For example, an array of 5 dynamic arrays of ``uint`` is written as +``uint[][5]``. The notation is reversed compared to some other languages. In +Solidity, ``X[3]`` is always an array containing three elements of type ``X``, +even if ``X`` is itself an array. This is not the case in other languages such +as C. + +Indices are zero-based, and access is in the opposite direction of the +declaration. + +For example, if you have a variable ``uint[][5] x memory``, you access the +second ``uint`` in the third dynamic array using ``x[2][1]``, and to access the +third dynamic array, use ``x[2]``. Again, +if you have an array ``T[5] a`` for a type ``T`` that can also be an array, +then ``a[2]`` always has type ``T``. + +Array elements can be of any type, including mapping or struct. The general +restrictions for types apply, in that mappings can only be stored in the +``storage`` data location and publicly-visible functions need parameters that are :ref:`ABI types `. + +It is possible to mark state variable arrays ``public`` and have Solidity create a :ref:`getter `. +The numeric index becomes a required parameter for the getter. + +Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member ` to change the size (see below for caveats). +method or increase the ``.length`` :ref:`member ` to add elements. + +``bytes`` and ``strings`` as Arrays +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Variables of type ``bytes`` and ``string`` are special arrays. A ``bytes`` is similar to ``byte[]``, +but it is packed tightly in calldata and memory. ``string`` is equal to ``bytes`` but does not allow +length or index access. + +Solidity does not have string manipulation functions, but there are +third-party string libraries. You can also compare two strings by their keccak256-hash using +``keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))`` and concatenate two strings using ``abi.encodePacked(s1, s2)``. + +You should use ``bytes`` over ``byte[]`` because it is cheaper, since ``byte[]`` adds 31 padding bytes between the elements. As a general rule, +use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length +string (UTF-8) data. If you can limit the length to a certain number of bytes, +always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. + +.. note:: + If you want to access the byte-representation of a string ``s``, use + ``bytes(s).length`` / ``bytes(s)[7] = 'x';``. Keep in mind + that you are accessing the low-level bytes of the UTF-8 representation, + and not the individual characters. + +.. index:: ! array;allocating, new + +Allocating Memory Arrays +^^^^^^^^^^^^^^^^^^^^^^^^ + +You must use the ``new`` keyword to create arrays with a runtime-dependent length in memory. +As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to +the ``.length`` member). You either have to calculate the required size in advance +or create a new memory array and copy every element. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract C { + function f(uint len) public pure { + uint[] memory a = new uint[](7); + bytes memory b = new bytes(len); + assert(a.length == 7); + assert(b.length == len); + a[6] = 8; + } + } + +.. index:: ! array;literals, ! inline;arrays + +Array Literals +^^^^^^^^^^^^^^ + +An array literal is a comma-separated list of one or more expressions, enclosed +in square brackets (``[...]``). For example ``[1, a, f(3)]``. There must be a +common type all elements can be implicitly converted to. This is the elementary +type of the array. + +Array literals are always statically-sized memory arrays. + +In the example below, the type of ``[1, 2, 3]`` is +``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if you want the result to be a ``uint[3] memory`` type, you need to convert the first element to ``uint``. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract C { + function f() public pure { + g([uint(1), 2, 3]); + } + function g(uint[3] memory) public pure { + // ... + } + } + +Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: + +:: + + pragma solidity >=0.4.0 <0.7.0; + + // This will not compile. + contract C { + function f() public { + // The next line creates a type error because uint[3] memory + // cannot be converted to uint[] memory. + uint[] memory x = [uint(1), 3, 4]; + } + } + +It is planned to remove this restriction in the future, but it creates some +complications because of how arrays are passed in the ABI. + +.. index:: ! array;length, length, push, pop, !array;push, !array;pop + +.. _array-members: + +Array Members +^^^^^^^^^^^^^ + +**length**: + Arrays have a ``length`` member that contains their number of elements. + The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. + For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. + Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. + Increasing the length adds new zero-initialised elements to the array. + Reducing the length performs an implicit :ref:`delete` on each of the + removed elements. If you try to resize a non-dynamic array that isn't in + storage, you receive a ``Value must be an lvalue`` error. +**push**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. +**pop**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete` on the removed element. + +.. warning:: + If you use ``.length--`` on an empty array, it causes an underflow and + thus sets the length to ``2**256-1``. + +.. note:: + Increasing the length of a storage array has constant gas costs because + storage is assumed to be zero-initialised, while decreasing + the length has at least linear cost (but in most cases worse than linear), + because it includes explicitly clearing the removed + elements similar to calling :ref:`delete` on them. + +.. note:: + It is not yet possible to use arrays of arrays in external functions + (but they are supported in public functions). + +.. note:: + In EVM versions before Byzantium, it was not possible to access + dynamic arrays return from function calls. If you call functions + that return dynamic arrays, make sure to use an EVM that is set to + Byzantium mode. + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract ArrayContract { + uint[2**20] m_aLotOfIntegers; + // Note that the following is not a pair of dynamic arrays but a + // dynamic array of pairs (i.e. of fixed size arrays of length two). + // Because of that, T[] is always a dynamic array of T, even if T + // itself is an array. + // Data location for all state variables is storage. + bool[2][] m_pairsOfFlags; + + // newPairs is stored in memory - the only possibility + // for public contract function arguments + function setAllFlagPairs(bool[2][] memory newPairs) public { + // assignment to a storage array performs a copy of ``newPairs`` and + // replaces the complete array ``m_pairsOfFlags``. + m_pairsOfFlags = newPairs; + } + + struct StructType { + uint[] contents; + uint moreInfo; + } + StructType s; + + function f(uint[] memory c) public { + // stores a reference to ``s`` in ``g`` + StructType storage g = s; + // also changes ``s.moreInfo``. + g.moreInfo = 2; + // assigns a copy because ``g.contents`` + // is not a local variable, but a member of + // a local variable. + g.contents = c; + } + + function setFlagPair(uint index, bool flagA, bool flagB) public { + // access to a non-existing index will throw an exception + m_pairsOfFlags[index][0] = flagA; + m_pairsOfFlags[index][1] = flagB; + } + + function changeFlagArraySize(uint newSize) public { + // if the new size is smaller, removed array elements will be cleared + m_pairsOfFlags.length = newSize; + } + + function clear() public { + // these clear the arrays completely + delete m_pairsOfFlags; + delete m_aLotOfIntegers; + // identical effect here + m_pairsOfFlags.length = 0; + } + + bytes m_byteData; + + function byteArrays(bytes memory data) public { + // byte arrays ("bytes") are different as they are stored without padding, + // but can be treated identical to "uint8[]" + m_byteData = data; + m_byteData.length += 7; + m_byteData[3] = 0x08; + delete m_byteData[2]; + } + + function addFlag(bool[2] memory flag) public returns (uint) { + return m_pairsOfFlags.push(flag); + } + + function createMemoryArray(uint size) public pure returns (bytes memory) { + // Dynamic memory arrays are created using `new`: + uint[2][] memory arrayOfPairs = new uint[2][](size); + + // Inline arrays are always statically-sized and if you only + // use literals, you have to provide at least one type. + arrayOfPairs[0] = [uint(1), 2]; + + // Create a dynamic byte array: + bytes memory b = new bytes(200); + for (uint i = 0; i < b.length; i++) + b[i] = byte(uint8(i)); + return b; + } + } + + +.. index:: ! struct, ! type;struct + +.. _structs: + +Structs +------- + +Solidity provides a way to define new types in the form of structs, which is +shown in the following example: + +:: + + pragma solidity >=0.4.11 <0.7.0; + + contract CrowdFunding { + // Defines a new type with two fields. + struct Funder { + address addr; + uint amount; + } + + struct Campaign { + address payable beneficiary; + uint fundingGoal; + uint numFunders; + uint amount; + mapping (uint => Funder) funders; + } + + uint numCampaigns; + mapping (uint => Campaign) campaigns; + + function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { + campaignID = numCampaigns++; // campaignID is return variable + // Creates new struct in memory and copies it to storage. + // We leave out the mapping type, because it is not valid in memory. + // If structs are copied (even from storage to storage), + // types that are not valid outside of storage (ex. mappings and array of mappings) + // are always omitted, because they cannot be enumerated. + campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); + } + + function contribute(uint campaignID) public payable { + Campaign storage c = campaigns[campaignID]; + // Creates a new temporary memory struct, initialised with the given values + // and copies it over to storage. + // Note that you can also use Funder(msg.sender, msg.value) to initialise. + c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); + c.amount += msg.value; + } + + function checkGoalReached(uint campaignID) public returns (bool reached) { + Campaign storage c = campaigns[campaignID]; + if (c.amount < c.fundingGoal) + return false; + uint amount = c.amount; + c.amount = 0; + c.beneficiary.transfer(amount); + return true; + } + } + +The contract does not provide the full functionality of a crowdfunding +contract, but it contains the basic concepts necessary to understand structs. +Struct types can be used inside mappings and arrays and they can itself +contain mappings and arrays. + +It is not possible for a struct to contain a member of its own type, +although the struct itself can be the value type of a mapping member +or it can contain a dynamically-sized array of its type. +This restriction is necessary, as the size of the struct has to be finite. + +Note how in all the functions, a struct type is assigned to a local variable +with data location ``storage``. +This does not copy the struct but only stores a reference so that assignments to +members of the local variable actually write to the state. + +Of course, you can also directly access the members of the struct without +assigning it to a local variable, as in +``campaigns[campaignID].amount = 0``. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst new file mode 100644 index 000000000..053160701 --- /dev/null +++ b/docs/types/value-types.rst @@ -0,0 +1,742 @@ +.. index:: ! value type, ! type;value +.. _value-types: + +Value Types +=========== + +The following types are also called value types because variables of these +types will always be passed by value, i.e. they are always copied when they +are used as function arguments or in assignments. + +.. index:: ! bool, ! true, ! false + +Booleans +-------- + +``bool``: The possible values are constants ``true`` and ``false``. + +Operators: + +* ``!`` (logical negation) +* ``&&`` (logical conjunction, "and") +* ``||`` (logical disjunction, "or") +* ``==`` (equality) +* ``!=`` (inequality) + +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 +-------- + +``int`` / ``uint``: Signed and unsigned integers of various sizes. Keywords ``uint8`` to ``uint256`` in steps of ``8`` (unsigned of 8 up to 256 bits) and ``int8`` to ``int256``. ``uint`` and ``int`` are aliases for ``uint256`` and ``int256``, respectively. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) +* Shift operators: ``<<`` (left shift), ``>>`` (right shift) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (exponentiation) + +.. warning:: + + Integers in Solidity are restricted to a certain range. For example, with ``uint32``, this is ``0`` up to ``2**32 - 1``. + If the result of some operation on those numbers does not fit inside this range, it is truncated. These truncations can have + serious consequences that you should :ref:`be aware of and mitigate against`. + +Comparisons +^^^^^^^^^^^ + +The value of a comparison is the one obtained by comparing the integer value. + +Bit operations +^^^^^^^^^^^^^^ + +Bit operations are performed on the two's complement representation of the number. +This means that, for example ``~int256(0) == int256(-1)``. + +Shifts +^^^^^^ + +The result of a shift operation has the type of the left operand, truncating the result to match the type. + +- For positive and negative ``x`` values, ``x << y`` is equivalent to ``x * 2**y``. +- For positive ``x`` values, ``x >> y`` is equivalent to ``x / 2**y``. +- For negative ``x`` values, ``x >> y`` is equivalent to ``(x + 1) / 2**y - 1`` (which is the same as dividing ``x`` by ``2**y`` while rounding down towards negative infinity). +- In all cases, shifting by a negative ``y`` throws a runtime exception. + +.. warning:: + Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``, + i.e., right shifts used rounding up (towards zero) instead of rounding down (towards negative infinity). + +Addition, Subtraction and Multiplication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Addition, subtraction and multiplication have the usual semantics. +They wrap in two's complement representation, meaning that +for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows +into account when designing safe smart contracts. + +The expression ``-x`` is equivalent to ``(T(0) - x)`` where +``T`` is the type of ``x``. This means that ``-x`` will not be negative +if the type of ``x`` is an unsigned integer type. Also, ``-x`` can be +positive if ``x`` is negative. There is another caveat also resulting +from two's complement representation:: + + int x = -2**255; + assert(-x == x); + +This means that even if a number is negative, you cannot assume that +its negation will be positive. + + +Division +^^^^^^^^ + +Since the type of the result of an operation is always the type of one of +the operands, division on integers always results in an integer. +In Solidity, division rounds towards zero. This mean that ``int256(-5) / int256(2) == int256(-2)``. + +Note that in contrast, division on :ref:`literals` results in fractional values +of arbitrary precision. + +.. note:: + Division by zero causes a failing assert. + +Modulo +^^^^^^ + +The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a`` +by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo +results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`` holds for negative ``a``: + + * ``int256(5) % int256(2) == int256(1)`` + * ``int256(5) % int256(-2) == int256(1)`` + * ``int256(-5) % int256(2) == int256(-1)`` + * ``int256(-5) % int256(-2) == int256(-1)`` + +.. note:: + Modulo with zero causes a failing assert. + +Exponentiation +^^^^^^^^^^^^^^ + +Exponentiation is only available for unsigned types. Please take care that the types +you are using are large enough to hold the result and prepare for potential wrapping behaviour. + +.. note:: + Note that ``0**0`` is defined by the EVM as ``1``. + +.. index:: ! ufixed, ! fixed, ! fixed point number + +Fixed Point Numbers +------------------- + +.. warning:: + Fixed point numbers are not fully supported by Solidity yet. They can be declared, but + cannot be assigned to or from. + +``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represents the number of bits taken by +the type and ``N`` represents how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. +``ufixed`` and ``fixed`` are aliases for ``ufixed128x18`` and ``fixed128x18``, respectively. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo) + +.. note:: + The main difference between floating point (``float`` and ``double`` in many languages, more precisely IEEE 754 numbers) and fixed point numbers is + that the number of bits used for the integer and the fractional part (the part after the decimal dot) is flexible in the former, while it is strictly + defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define + where the decimal point is. + +.. index:: address, balance, send, call, callcode, delegatecall, staticcall, transfer + +.. _address: + +Address +------- + +The address type comes in two flavours, which are largely identical: + + - ``address``: Holds a 20 byte value (size of an Ethereum address). + - ``address payable``: Same as ``address``, but with the additional members ``transfer`` and ``send``. + +The idea behind this distinction is that ``address payable`` is an address you can send Ether to, +while a plain ``address`` cannot be sent Ether. + +Type conversions: + +Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are +not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``). + +:ref:`Address literals` can be implicitly converted to ``address payable``. + +Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following +caveat: +Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)`` +has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function. +If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``. +In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type. + +.. note:: + It might very well be that you do not need to care about the distinction between ``address`` + and ``address payable`` and just use ``address`` everywhere. For example, + if you are using the :ref:`withdrawal pattern`, you can (and should) store the + address itself as ``address``, because you invoke the ``transfer`` function on + ``msg.sender``, which is an ``address payable``. + +Operators: + +* ``<=``, ``<``, ``==``, ``!=``, ``>=`` and ``>`` + +.. warning:: + If you convert a type that uses a larger byte size to an ``address``, for example ``bytes32``, then the ``address`` is truncated. + To reduce conversion ambiguity version 0.4.24 and higher of the compiler force you make the truncation explicit in the conversion. + Take for example the 32-byte value ``0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC``. + + You can use ``address(uint160(bytes20(b)))``, which results in ``0x111122223333444455556666777788889999aAaa``, + or you can use ``address(uint160(uint256(b)))``, which results in ``0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc``. + +.. note:: + The distinction between ``address`` and ``address payable`` was introduced with version 0.5.0. + Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to + ``address`` or to ``address payable``, if they have a payable fallback function. + +.. _members-of-addresses: + +Members of Addresses +^^^^^^^^^^^^^^^^^^^^ + +For a quick reference of all members of address, see :ref:`address_related`. + +* ``balance`` and ``transfer`` + +It is possible to query the balance of an address using the property ``balance`` +and to send Ether (in units of wei) to a payable address using the ``transfer`` function: + +:: + + address payable x = address(0x123); + address myAddress = address(this); + if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); + +The ``transfer`` function fails if the balance of the current contract is not large enough +or if the Ether transfer is rejected by the receiving account. The ``transfer`` function +reverts on failure. + +.. note:: + If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + +* ``send`` + +Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. + +.. warning:: + There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 + (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order + to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: + use a pattern where the recipient withdraws the money. + +* ``call``, ``delegatecall`` and ``staticcall`` + +In order to interface with contracts that do not adhere to the ABI, +or to get more direct control over the encoding, +the functions ``call``, ``delegatecall`` and ``staticcall`` are provided. +They all take a single ``bytes memory`` parameter and +return the success condition (as a ``bool``) and the returned data +(``bytes memory``). +The functions ``abi.encode``, ``abi.encodePacked``, ``abi.encodeWithSelector`` +and ``abi.encodeWithSignature`` can be used to encode structured data. + +Example:: + + bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); + (bool success, bytes memory returnData) = address(nameReg).call(payload); + require(success); + +.. warning:: + All these functions are low-level functions and should be used with care. + Specifically, any unknown contract might be malicious and if you call it, you + hand over control to that contract which could in turn call back into + your contract, so be prepared for changes to your state variables + when the call returns. The regular way to interact with other contracts + is to call a function on a contract object (``x.f()``). + +.. note:: + Previous versions of Solidity allowed these functions to receive + arbitrary arguments and would also handle a first argument of type + ``bytes4`` differently. These edge cases were removed in version 0.5.0. + +It is possible to adjust the supplied gas with the ``.gas()`` modifier:: + + address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName")); + +Similarly, the supplied Ether value can be controlled too:: + + address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); + +Lastly, these modifiers can be combined. Their order does not matter:: + + address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); + +In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. + +.. note:: + Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values. This function was removed in version 0.5.0. + +Since byzantium ``staticcall`` can be used as well. This is basically the same as ``call``, but will revert if the called function modifies the state in any way. + +All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. + +The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. + +.. note:: + All contracts can be converted to ``address`` type, so it is possible to query the balance of the + current contract using ``address(this).balance``. + +.. index:: ! contract type, ! type; contract + +.. _contract_types: + +Contract Types +-------------- + +Every :ref:`contract` defines its own type. +You can implicitly convert contracts to contracts they inherit from. +Contracts can be explicitly converted to and from the ``address`` type. + +Explicit conversion to and from the ``address payable`` type +is only possible if the contract type has a payable fallback function. +The conversion is still performed using ``address(x)`` and not +using ``address payable(x)``. You can find more information in the section about +the :ref:`address type
`. + +.. note:: + Before version 0.5.0, contracts directly derived from the address type + and there was no distinction between ``address`` and ``address payable``. + +If you declare a local variable of contract type (`MyContract c`), you can call +functions on that contract. Take care to assign it from somewhere that is the +same contract type. + +You can also instantiate contracts (which means they are newly created). You +can find more details in the :ref:`'Contracts via new'` +section. + +The data representation of a contract is identical to that of the ``address`` +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 any state variables marked as ``public``. + +For a contract ``C`` you can use ``type(C)`` to access +:ref:`type information` about the contract. + +.. index:: byte array, bytes32 + +Fixed-size byte arrays +---------------------- + +The value types ``bytes1``, ``bytes2``, ``bytes3``, ..., ``bytes32`` +hold a sequence of bytes from one to up to 32. +``byte`` is an alias for ``bytes1``. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) +* Shift operators: ``<<`` (left shift), ``>>`` (right shift) +* Index access: If ``x`` is of type ``bytesI``, then ``x[k]`` for ``0 <= k < I`` returns the ``k`` th byte (read-only). + +The shifting operator works with any integer type as right operand (but +returns the type of the left operand), which denotes the number of bits to shift by. +Shifting by a negative amount causes a runtime exception. + +Members: + +* ``.length`` yields the fixed length of the byte array (read-only). + +.. note:: + The type ``byte[]`` is an array of bytes, but due to padding rules, it wastes + 31 bytes of space for each element (except in storage). It is better to use the ``bytes`` + type instead. + +Dynamically-sized byte array +---------------------------- + +``bytes``: + Dynamically-sized byte array, see :ref:`arrays`. Not a value-type! +``string``: + Dynamically-sized UTF-8-encoded string, see :ref:`arrays`. Not a value-type! + +.. index:: address, literal;address + +.. _address_literals: + +Address Literals +---------------- + +Hexadecimal literals that pass the address checksum test, for example +``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address payable`` type. +Hexadecimal literals that are between 39 and 41 digits +long and do not pass the checksum test produce +an error. You can prepend (for integer types) or append (for bytesNN types) zeros to remove the error. + +.. note:: + The mixed-case address checksum format is defined in `EIP-55 `_. + +.. index:: literal, literal;rational + +.. _rational_literals: + +Rational and Integer Literals +----------------------------- + +Integer literals are formed from a sequence of numbers in the range 0-9. +They are interpreted as decimals. For example, ``69`` means sixty nine. +Octal literals do not exist in Solidity and leading zeros are invalid. + +Decimal fraction literals are formed by a ``.`` with at least one number on +one side. Examples include ``1.``, ``.1`` and ``1.3``. + +Scientific notation is also supported, where the base can have fractions, while the exponent cannot. +Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. + +Underscores can be used to separate the digits of a numeric literal to aid readability. +For example, decimal ``123_000``, hexadecimal ``0x2eff_abde``, scientific decimal notation ``1_2e345_678`` are all valid. +Underscores are only allowed between two digits and only one consecutive underscore is allowed. +There is no additional semantic meaning added to a number literal containing underscores, +the underscores are ignored. + +Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by +using them together with a non-literal expression or by explicit conversion). +This means that computations do not overflow and divisions do not truncate +in number literal expressions. + +For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type ``uint8``) +although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results +in the integer ``4`` (although non-integers were used in between). + +Any operator that can be applied to integers can also be applied to number literal expressions as +long as the operands are integers. If any of the two is fractional, bit operations are disallowed +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. + Moreover, all number literal expressions (i.e. the expressions that + contain only number literals and operators) belong to number literal + types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both + belong to the same number literal type for the rational number three. + + +.. note:: + Number literal expressions are converted into a non-literal type as soon as they are used with non-literal + expressions. Disregarding types, the value of the expression assigned to ``b`` + below evaluates to an integer. Because ``a`` is of type ``uint128``, the + expression ``2.5 + a`` has to have a proper type, though. Since there is no common type + for the type of ``2.5`` and ``uint128``, the Solidity compiler does not accept + this code. + +:: + + uint128 a = 1; + uint128 b = 2.5 + a + 0.5; + +.. index:: literal, literal;string, string +.. _string_literals: + +String Literals and Types +------------------------- + +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. + +For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type. + +String literals support the following escape characters: + + - ``\`` (escapes an actual newline) + - ``\\`` (backslash) + - ``\'`` (single quote) + - ``\"`` (double quote) + - ``\b`` (backspace) + - ``\f`` (form feed) + - ``\n`` (newline) + - ``\r`` (carriage return) + - ``\t`` (tab) + - ``\v`` (vertical tab) + - ``\xNN`` (hex escape, see below) + - ``\uNNNN`` (unicode escape, see below) + +``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. + +The string in the following example has a length of ten bytes. +It starts with a newline byte, followed by a double quote, a single +quote a backslash character and then (without separator) the +character sequence ``abcdef``. + +:: + + "\n\"\'\\abc\ + def" + +Any unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to +terminate the string literal. Newline only terminates the string literal if it is not preceded by a ``\``. + +.. index:: literal, bytes + +Hexadecimal Literals +-------------------- + +Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. + +Hexadecimal literals behave like :ref:`string literals ` and have the same convertibility restrictions. + +.. index:: enum + +.. _enums: + +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 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``. + + +:: + + pragma solidity >=0.4.16 <0.7.0; + + contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + ActionChoices choice; + ActionChoices constant defaultChoice = ActionChoices.GoStraight; + + function setGoStraight() public { + choice = ActionChoices.GoStraight; + } + + // Since enum types are not part of the ABI, the signature of "getChoice" + // will automatically be changed to "getChoice() returns (uint8)" + // for all matters external to Solidity. The integer type used is just + // large enough to hold all enum values, i.e. if you have more than 256 values, + // `uint16` will be used and so on. + function getChoice() public view returns (ActionChoices) { + return choice; + } + + function getDefaultChoice() public pure returns (uint) { + return uint(defaultChoice); + } + } + +.. index:: ! function type, ! type; function + +.. _function_types: + +Function Types +-------------- + +Function types are the types of functions. Variables of function type +can be assigned from functions and function parameters of function type +can be used to pass functions to and return functions from function calls. +Function types come in two flavours - *internal* and *external* functions: + +Internal functions can only be called inside the current contract (more specifically, +inside the current code unit, which also includes internal library functions +and inherited functions) because they cannot be executed outside of the +context of the current contract. Calling an internal function is realized +by jumping to its entry label, just like when calling a function of the current +contract internally. + +External functions consist of an address and a function signature and they can +be passed via and returned from external function calls. + +Function types are notated as follows:: + + function () {internal|external} [pure|view|payable] [returns ()] + +In contrast to the parameter types, the return types cannot be empty - if the +function type should not return anything, the whole ``returns ()`` +part has to be omitted. + +By default, function types are internal, so the ``internal`` keyword can be +omitted. Note that this only applies to function types. Visibility has +to be specified explicitly for functions defined in contracts, they +do not have a default. + +Conversions: + +A value of external function type can be explicitly converted to ``address`` +resulting in the address of the contract of the function. + +A function type ``A`` is implicitly convertible to a function type ``B`` if and only if +their parameter types are identical, their return types are identical, +their internal/external property is identical and the state mutability of ``A`` +is not more restrictive than the state mutability of ``B``. In particular: + + - ``pure`` functions can be converted to ``view`` and ``non-payable`` functions + - ``view`` functions can be converted to ``non-payable`` functions + - ``payable`` functions can be converted to ``non-payable`` functions + +No other conversions between function types are possible. + +The rule about ``payable`` and ``non-payable`` might be a little +confusing, but in essence, if a function is ``payable``, this means that it +also accepts a payment of zero Ether, so it also is ``non-payable``. +On the other hand, a ``non-payable`` function will reject Ether sent to it, +so ``non-payable`` functions cannot be converted to ``payable`` functions. + +If a function type variable is not initialised, calling it results +in a failed assertion. The same happens if you call a function after using ``delete`` +on it. + +If external function types are used outside of the context of Solidity, +they are treated as the ``function`` type, which encodes the address +followed by the function identifier together in a single ``bytes24`` type. + +Note that public functions of the current contract can be used both as an +internal and as an external function. To use ``f`` as an internal function, +just use ``f``, if you want to use its external form, use ``this.f``. + +Members: + +Public (or external) functions have the following members: + +* ``.selector`` returns the :ref:`ABI function selector ` +* ``.gas(uint)`` returns a callable function object which, when called, will send the specified amount of gas to the target function. See :ref:`External Function Calls ` for more information. +* ``.value(uint)`` returns a callable function object which, when called, will send the specified amount of wei to the target function. See :ref:`External Function Calls ` for more information. + +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)(); + } + } + +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]); + } + } + + 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; + } + } + } + + + 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; + } + } + +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[] 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 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:: + Lambda or inline functions are planned but not yet supported. diff --git a/docs/yul.rst b/docs/yul.rst index 9e9fac8ec..ae8eb1f9e 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -6,21 +6,33 @@ Yul .. index:: ! assembly, ! asm, ! evmasm, ! yul, julia, iulia -Yul (previously also called JULIA or IULIA) is an intermediate language that can -compile to various different backends -(EVM 1.0, EVM 1.5 and eWASM are planned). -Because of that, it is designed to be a usable common denominator of all three -platforms. -It can already be used for "inline assembly" inside Solidity and -future versions of the Solidity compiler will even use Yul as intermediate -language. It should also be easy to build high-level optimizer stages for Yul. +Yul (previously also called JULIA or IULIA) is an intermediate language that can be +compiled to bytecode for different backends. -.. note:: +Support for EVM 1.0, EVM 1.5 and eWASM is planned, and it is designed to be a usable common denominator of all three +platforms. It can already be used for "inline assembly" inside Solidity and future versions of the Solidity compiler +will use Yul as an intermediate language. Yul is a good target for high-level optimisation stages that can benefit all target platforms equally. + +With the "inline assembly" flavour, Yul can be used as a language setting +for the :ref:`standard-json interface `: + +:: - Note that the flavour used for "inline assembly" does not have types - (everything is ``u256``) and the built-in functions are identical - to the EVM opcodes. Please resort to the inline assembly documentation - for details. + { + "language": "Yul", + "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, + "settings": { + "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, + "optimizer": { "enabled": true, "details": { "yul": true } } + } + } + +And on the command line interface with the ``--strict-assembly`` parameter. + +.. warning:: + + Yul is in active development and bytecode generation is fully implemented only for untyped Yul (everything is ``u256``) + and with EVM 1.0 as target, :ref:`EVM opcodes ` are used as built-in functions. The core components of Yul are functions, blocks, variables, literals, for-loops, if-statements, switch-statements, expressions and assignments to variables. @@ -35,20 +47,21 @@ if the backend changes. For a list of mandatory built-in functions, see the sect The following example program assumes that the EVM opcodes ``mul``, ``div`` and ``mod`` are available either natively or as functions and computes exponentiation. +As per the warning above, the following code is untyped and can be compiled using ``solc --strict-assembly``. .. code:: { - function power(base:u256, exponent:u256) -> result:u256 + function power(base, exponent) -> result { switch exponent - case 0:u256 { result := 1:u256 } - case 1:u256 { result := base } + case 0 { result := 1 } + case 1 { result := base } default { - result := power(mul(base, base), div(exponent, 2:u256)) - switch mod(exponent, 2:u256) - case 1:u256 { result := mul(base, result) } + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + case 1 { result := mul(base, result) } } } } @@ -60,10 +73,10 @@ and ``add`` to be available. .. code:: { - function power(base:u256, exponent:u256) -> result:u256 + function power(base, exponent) -> result { - result := 1:u256 - for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) } + result := 1 + for { let i := 0 } lt(i, exponent) { i := add(i, 1) } { result := mul(result, base) } @@ -111,7 +124,7 @@ Grammar:: 'break' | 'continue' FunctionCall = Identifier '(' ( Expression ( ',' Expression )* )? ')' - Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]* + Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]* IdentifierList = Identifier ( ',' Identifier)* TypeName = Identifier | BuiltinTypeName BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) @@ -130,9 +143,10 @@ Restrictions on the Grammar --------------------------- Switches must have at least one case (including the default case). -If all possible values of the expression is covered, the default case should -not be allowed (i.e. a switch with a ``bool`` expression and having both a -true and false case should not allow a default case). +If all possible values of the expression are covered, a default case should +not be allowed (i.e. a switch with a ``bool`` expression that has both a +true and a false case should not allow a default case). All case values need to +have the same type. Every expression evaluates to zero or more values. Identifiers and Literals evaluate to exactly @@ -154,6 +168,7 @@ The ``continue`` and ``break`` statements can only be used inside loop bodies and have to be in the same function as the loop (or both have to be at the top level). The condition part of the for-loop has to evaluate to exactly one value. +Functions cannot be defined inside for loop init blocks. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. @@ -171,7 +186,7 @@ As an exception, identifiers defined in the "init" part of the for-loop (the first block) are visible in all other parts of the for-loop (but not outside of the loop). Identifiers declared in the other parts of the for loop respect the regular -syntatical scoping rules. +syntactical scoping rules. The parameters and return parameters of functions are visible in the function body and their names cannot overlap. @@ -229,14 +244,14 @@ We will use a destructuring notation for the AST nodes. G1, L1, mode E(G, L, FunctionDefinition) = G, L, regular - E(G, L, : VariableDeclaration) = - E(G, L, : Assignment) - E(G, L, : VariableDeclaration) = - let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n + E(G, L, : VariableDeclaration) = + E(G, L, : Assignment) + E(G, L, : VariableDeclaration) = + let L1 be a copy of L where L1[$var_i] = 0 for i = 1, ..., n G, L1, regular - E(G, L, : Assignment) = + E(G, L, : Assignment) = let G1, L1, v1, ..., vn = E(G, L, rhs) - let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n + let L2 be a copy of L1 where L2[$var_i] = vi for i = 1, ..., n G, L2, regular E(G, L, : ForLoop) = if n >= 1: @@ -333,83 +348,83 @@ The following functions must be available: +---------------------------------------------------------------------------------------------------------------+ | *Logic* | +---------------------------------------------+-----------------------------------------------------------------+ -| not(x:bool) -> z:bool | logical not | +| not(x:bool) ‑> z:bool | logical not | +---------------------------------------------+-----------------------------------------------------------------+ -| and(x:bool, y:bool) -> z:bool | logical and | +| and(x:bool, y:bool) ‑> z:bool | logical and | +---------------------------------------------+-----------------------------------------------------------------+ -| or(x:bool, y:bool) -> z:bool | logical or | +| or(x:bool, y:bool) ‑> z:bool | logical or | +---------------------------------------------+-----------------------------------------------------------------+ -| xor(x:bool, y:bool) -> z:bool | xor | +| xor(x:bool, y:bool) ‑> z:bool | xor | +---------------------------------------------+-----------------------------------------------------------------+ | *Arithmetic* | +---------------------------------------------+-----------------------------------------------------------------+ -| addu256(x:u256, y:u256) -> z:u256 | x + y | +| addu256(x:u256, y:u256) ‑> z:u256 | x + y | +---------------------------------------------+-----------------------------------------------------------------+ -| subu256(x:u256, y:u256) -> z:u256 | x - y | +| subu256(x:u256, y:u256) ‑> z:u256 | x - y | +---------------------------------------------+-----------------------------------------------------------------+ -| mulu256(x:u256, y:u256) -> z:u256 | x * y | +| mulu256(x:u256, y:u256) ‑> z:u256 | x * y | +---------------------------------------------+-----------------------------------------------------------------+ -| divu256(x:u256, y:u256) -> z:u256 | x / y | +| divu256(x:u256, y:u256) ‑> z:u256 | x / y | +---------------------------------------------+-----------------------------------------------------------------+ -| divs256(x:s256, y:s256) -> z:s256 | x / y, for signed numbers in two's complement | +| divs256(x:s256, y:s256) ‑> z:s256 | x / y, for signed numbers in two's complement | +---------------------------------------------+-----------------------------------------------------------------+ -| modu256(x:u256, y:u256) -> z:u256 | x % y | +| modu256(x:u256, y:u256) ‑> z:u256 | x % y | +---------------------------------------------+-----------------------------------------------------------------+ -| mods256(x:s256, y:s256) -> z:s256 | x % y, for signed numbers in two's complement | +| mods256(x:s256, y:s256) ‑> z:s256 | x % y, for signed numbers in two's complement | +---------------------------------------------+-----------------------------------------------------------------+ -| signextendu256(i:u256, x:u256) -> z:u256 | sign extend from (i*8+7)th bit counting from least significant | +| signextendu256(i:u256, x:u256) ‑> z:u256 | sign extend from (i*8+7)th bit counting from least significant | +---------------------------------------------+-----------------------------------------------------------------+ -| expu256(x:u256, y:u256) -> z:u256 | x to the power of y | +| expu256(x:u256, y:u256) ‑> z:u256 | x to the power of y | +---------------------------------------------+-----------------------------------------------------------------+ -| addmodu256(x:u256, y:u256, m:u256) -> z:u256| (x + y) % m with arbitrary precision arithmetic | +| addmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x + y) % m with arbitrary precision arithmetic | +---------------------------------------------+-----------------------------------------------------------------+ -| mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetic | +| mulmodu256(x:u256, y:u256, m:u256) ‑> z:u256| (x * y) % m with arbitrary precision arithmetic | +---------------------------------------------+-----------------------------------------------------------------+ -| ltu256(x:u256, y:u256) -> z:bool | true if x < y, false otherwise | +| ltu256(x:u256, y:u256) ‑> z:bool | true if x < y, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| gtu256(x:u256, y:u256) -> z:bool | true if x > y, false otherwise | +| gtu256(x:u256, y:u256) ‑> z:bool | true if x > y, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| sltu256(x:s256, y:s256) -> z:bool | true if x < y, false otherwise | +| lts256(x:s256, y:s256) ‑> z:bool | true if x < y, false otherwise | | | (for signed numbers in two's complement) | +---------------------------------------------+-----------------------------------------------------------------+ -| sgtu256(x:s256, y:s256) -> z:bool | true if x > y, false otherwise | +| gts256(x:s256, y:s256) ‑> z:bool | true if x > y, false otherwise | | | (for signed numbers in two's complement) | +---------------------------------------------+-----------------------------------------------------------------+ -| equ256(x:u256, y:u256) -> z:bool | true if x == y, false otherwise | +| equ256(x:u256, y:u256) ‑> z:bool | true if x == y, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| iszerou256(x:u256) -> z:bool | true if x == 0, false otherwise | +| iszerou256(x:u256) ‑> z:bool | true if x == 0, false otherwise | +---------------------------------------------+-----------------------------------------------------------------+ -| notu256(x:u256) -> z:u256 | ~x, every bit of x is negated | +| notu256(x:u256) ‑> z:u256 | ~x, every bit of x is negated | +---------------------------------------------+-----------------------------------------------------------------+ -| andu256(x:u256, y:u256) -> z:u256 | bitwise and of x and y | +| andu256(x:u256, y:u256) ‑> z:u256 | bitwise and of x and y | +---------------------------------------------+-----------------------------------------------------------------+ -| oru256(x:u256, y:u256) -> z:u256 | bitwise or of x and y | +| oru256(x:u256, y:u256) ‑> z:u256 | bitwise or of x and y | +---------------------------------------------+-----------------------------------------------------------------+ -| xoru256(x:u256, y:u256) -> z:u256 | bitwise xor of x and y | +| xoru256(x:u256, y:u256) ‑> z:u256 | bitwise xor of x and y | +---------------------------------------------+-----------------------------------------------------------------+ -| shlu256(x:u256, y:u256) -> z:u256 | logical left shift of x by y | +| shlu256(x:u256, y:u256) ‑> z:u256 | logical left shift of x by y | +---------------------------------------------+-----------------------------------------------------------------+ -| shru256(x:u256, y:u256) -> z:u256 | logical right shift of x by y | +| shru256(x:u256, y:u256) ‑> z:u256 | logical right shift of x by y | +---------------------------------------------+-----------------------------------------------------------------+ -| saru256(x:u256, y:u256) -> z:u256 | arithmetic right shift of x by y | +| sars256(x:s256, y:u256) ‑> z:u256 | arithmetic right shift of x by y | +---------------------------------------------+-----------------------------------------------------------------+ -| byte(n:u256, x:u256) -> v:u256 | nth byte of x, where the most significant byte is the 0th byte | +| byte(n:u256, x:u256) ‑> v:u256 | nth byte of x, where the most significant byte is the 0th byte | | | Cannot this be just replaced by and256(shr256(n, x), 0xff) and | | | let it be optimised out by the EVM backend? | +---------------------------------------------+-----------------------------------------------------------------+ | *Memory and storage* | +---------------------------------------------+-----------------------------------------------------------------+ -| mload(p:u256) -> v:u256 | mem[p..(p+32)) | +| mload(p:u256) ‑> v:u256 | mem[p..(p+32)) | +---------------------------------------------+-----------------------------------------------------------------+ | mstore(p:u256, v:u256) | mem[p..(p+32)) := v | +---------------------------------------------+-----------------------------------------------------------------+ | mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte | +---------------------------------------------+-----------------------------------------------------------------+ -| sload(p:u256) -> v:u256 | storage[p] | +| sload(p:u256) ‑> v:u256 | storage[p] | +---------------------------------------------+-----------------------------------------------------------------+ | sstore(p:u256, v:u256) | storage[p] := v | +---------------------------------------------+-----------------------------------------------------------------+ -| msize() -> size:u256 | size of memory, i.e. largest accessed memory index, albeit due | +| msize() ‑> size:u256 | size of memory, i.e. largest accessed memory index, albeit due | | | due to the memory extension function, which extends by words, | | | this will always be a multiple of 32 bytes | +---------------------------------------------+-----------------------------------------------------------------+ @@ -427,15 +442,15 @@ The following functions must be available: | call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) | | insize:u256, out:u256, | providing g gas and v wei and output area | | outsize:u256) | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) | -| -> r:u256 | and 1 on success | +| ‑> r:u256 | and 1 on success | +---------------------------------------------+-----------------------------------------------------------------+ | callcode(g:u256, a:u256, v:u256, in:u256, | identical to ``call`` but only use the code from a | | insize:u256, out:u256, | and stay in the context of the | -| outsize:u256) -> r:u256 | current contract otherwise | +| outsize:u256) ‑> r:u256 | current contract otherwise | +---------------------------------------------+-----------------------------------------------------------------+ | delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, | | insize:u256, out:u256, | but also keep ``caller`` | -| outsize:u256) -> r:u256 | and ``callvalue`` | +| outsize:u256) ‑> r:u256 | and ``callvalue`` | +---------------------------------------------+-----------------------------------------------------------------+ | abort() | abort (equals to invalid instruction on EVM) | +---------------------------------------------+-----------------------------------------------------------------+ @@ -459,43 +474,43 @@ The following functions must be available: +---------------------------------------------+-----------------------------------------------------------------+ | *State queries* | +---------------------------------------------+-----------------------------------------------------------------+ -| blockcoinbase() -> address:u256 | current mining beneficiary | +| blockcoinbase() ‑> address:u256 | current mining beneficiary | +---------------------------------------------+-----------------------------------------------------------------+ -| blockdifficulty() -> difficulty:u256 | difficulty of the current block | +| blockdifficulty() ‑> difficulty:u256 | difficulty of the current block | +---------------------------------------------+-----------------------------------------------------------------+ -| blockgaslimit() -> limit:u256 | block gas limit of the current block | +| blockgaslimit() ‑> limit:u256 | block gas limit of the current block | +---------------------------------------------+-----------------------------------------------------------------+ -| blockhash(b:u256) -> hash:u256 | hash of block nr b - only for last 256 blocks excluding current | +| blockhash(b:u256) ‑> hash:u256 | hash of block nr b - only for last 256 blocks excluding current | +---------------------------------------------+-----------------------------------------------------------------+ -| blocknumber() -> block:u256 | current block number | +| blocknumber() ‑> block:u256 | current block number | +---------------------------------------------+-----------------------------------------------------------------+ -| blocktimestamp() -> timestamp:u256 | timestamp of the current block in seconds since the epoch | +| blocktimestamp() ‑> timestamp:u256 | timestamp of the current block in seconds since the epoch | +---------------------------------------------+-----------------------------------------------------------------+ -| txorigin() -> address:u256 | transaction sender | +| txorigin() ‑> address:u256 | transaction sender | +---------------------------------------------+-----------------------------------------------------------------+ -| txgasprice() -> price:u256 | gas price of the transaction | +| txgasprice() ‑> price:u256 | gas price of the transaction | +---------------------------------------------+-----------------------------------------------------------------+ -| gasleft() -> gas:u256 | gas still available to execution | +| gasleft() ‑> gas:u256 | gas still available to execution | +---------------------------------------------+-----------------------------------------------------------------+ -| balance(a:u256) -> v:u256 | wei balance at address a | +| balance(a:u256) ‑> v:u256 | wei balance at address a | +---------------------------------------------+-----------------------------------------------------------------+ -| this() -> address:u256 | address of the current contract / execution context | +| this() ‑> address:u256 | address of the current contract / execution context | +---------------------------------------------+-----------------------------------------------------------------+ -| caller() -> address:u256 | call sender (excluding delegatecall) | +| caller() ‑> address:u256 | call sender (excluding delegatecall) | +---------------------------------------------+-----------------------------------------------------------------+ -| callvalue() -> v:u256 | wei sent together with the current call | +| callvalue() ‑> v:u256 | wei sent together with the current call | +---------------------------------------------+-----------------------------------------------------------------+ -| calldataload(p:u256) -> v:u256 | call data starting from position p (32 bytes) | +| calldataload(p:u256) ‑> v:u256 | call data starting from position p (32 bytes) | +---------------------------------------------+-----------------------------------------------------------------+ -| calldatasize() -> v:u256 | size of call data in bytes | +| calldatasize() ‑> v:u256 | size of call data in bytes | +---------------------------------------------+-----------------------------------------------------------------+ | calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t | +---------------------------------------------+-----------------------------------------------------------------+ -| codesize() -> size:u256 | size of the code of the current contract / execution context | +| codesize() ‑> size:u256 | size of the code of the current contract / execution context | +---------------------------------------------+-----------------------------------------------------------------+ | codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t | +---------------------------------------------+-----------------------------------------------------------------+ -| extcodesize(a:u256) -> size:u256 | size of the code at address a | +| extcodesize(a:u256) ‑> size:u256 | size of the code at address a | +---------------------------------------------+-----------------------------------------------------------------+ | extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a | +---------------------------------------------+-----------------------------------------------------------------+ @@ -507,13 +522,23 @@ The following functions must be available: +---------------------------------------------+-----------------------------------------------------------------+ | discardu256(unused:u256) | discard value | +---------------------------------------------+-----------------------------------------------------------------+ -| splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's | +| splitu256tou64(x:u256) ‑> (x1:u64, x2:u64, | split u256 to four u64's | | x3:u64, x4:u64) | | +---------------------------------------------+-----------------------------------------------------------------+ | combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 | -| x4:u64) -> (x:u256) | | +| x4:u64) ‑> (x:u256) | | ++---------------------------------------------+-----------------------------------------------------------------+ +| keccak256(p:u256, s:u256) ‑> v:u256 | keccak(mem[p...(p+s))) | ++---------------------------------------------+-----------------------------------------------------------------+ +| *Object access* | | +---------------------------------------------+-----------------------------------------------------------------+ -| keccak256(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) | +| datasize(name:string) ‑> size:u256 | size of the data object in bytes, name has to be string literal | ++---------------------------------------------+-----------------------------------------------------------------+ +| dataoffset(name:string) ‑> offset:u256 | offset of the data object inside the data area in bytes, | +| | name has to be string literal | ++---------------------------------------------+-----------------------------------------------------------------+ +| datacopy(dst:u256, src:u256, len:u256) | copy len bytes from the data area starting at offset src bytes | +| | to memory at position dst | +---------------------------------------------+-----------------------------------------------------------------+ Backends @@ -540,12 +565,18 @@ TBD Specification of Yul Object =========================== +Yul objects are used to group named code and data sections. +The functions ``datasize``, ``dataoffset`` and ``datacopy`` +can be used to access these sections from within code. +Hex strings can be used to specify data in hex encoding, +regular strings in native encoding. For code, +``datacopy`` will access its assembled binary representation. + Grammar:: - TopLevelObject = 'object' '{' Code? ( Object | Data )* '}' - Object = 'object' StringLiteral '{' Code? ( Object | Data )* '}' + Object = 'object' StringLiteral '{' Code ( Object | Data )* '}' Code = 'code' Block - Data = 'data' StringLiteral HexLiteral + Data = 'data' StringLiteral ( HexLiteral | StringLiteral ) HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' @@ -558,14 +589,34 @@ An example Yul Object is shown below: // Code consists of a single object. A single "code" node is the code of the object. // Every (other) named object or data section is serialized and // made accessible to the special built-in functions datacopy / dataoffset / datasize - object { + // Access to nested objects can be performed by joining the names using ``.``. + // The current object and sub-objects and data items inside the current object + // are in scope without nested access. + object "Contract1" { code { - let size = datasize("runtime") - let offset = allocate(size) + function allocate(size) -> ptr { + ptr := mload(0x40) + if iszero(ptr) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + + // first create "runtime.Contract2" + let size := datasize("runtime.Contract2") + let offset := allocate(size) // This will turn into a memory->memory copy for eWASM and // a codecopy for EVM - datacopy(dataoffset("runtime"), offset, size) - // this is a constructor and the runtime code is returned + datacopy(offset, dataoffset("runtime.Contract2"), size) + // constructor parameter is a single number 0x1234 + mstore(add(offset, size), 0x1234) + pop(create(offset, add(size, 32), 0)) + + // now return the runtime object (this is + // constructor code) + size := datasize("runtime") + offset := allocate(size) + // This will turn into a memory->memory copy for eWASM and + // a codecopy for EVM + datacopy(offset, dataoffset("runtime"), size) return(offset, size) } @@ -573,16 +624,22 @@ An example Yul Object is shown below: object "runtime" { code { + function allocate(size) -> ptr { + ptr := mload(0x40) + if iszero(ptr) { ptr := 0x60 } + mstore(0x40, add(ptr, size)) + } + // runtime code - let size = datasize("Contract2") - let offset = allocate(size) + let size := datasize("Contract2") + let offset := allocate(size) // This will turn into a memory->memory copy for eWASM and // a codecopy for EVM - datacopy(dataoffset("Contract2"), offset, size) + datacopy(offset, dataoffset("Contract2"), size) // constructor parameter is a single number 0x1234 mstore(add(offset, size), 0x1234) - create(offset, add(size, 32)) + pop(create(offset, add(size, 32), 0)) } // Embedded object. Use case is that the outside is a factory contract, @@ -596,9 +653,9 @@ An example Yul Object is shown below: code { // code here ... } - } + } - data "Table1" hex"4123" + data "Table1" hex"4123" } } } diff --git a/libdevcore/Algorithms.h b/libdevcore/Algorithms.h index 7fe2472d0..b39939928 100644 --- a/libdevcore/Algorithms.h +++ b/libdevcore/Algorithms.h @@ -75,4 +75,48 @@ class CycleDetector V const* m_firstCycleVertex = nullptr; }; +/** + * Generic breadth first search. + * + * Note that V needs to be a comparable value type. If it is not, use a pointer type, + * but note that this might lead to non-deterministic traversal. + * + * Example: Gather all (recursive) children in a graph starting at (and including) ``root``: + * + * Node const* root = ...; + * std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const* _node, auto&& _addChild) { + * // Potentially process ``_node``. + * for (Node const& _child: _node->children()) + * // Potentially filter the children to be visited. + * _addChild(&_child); + * }).visited; + */ +template +struct BreadthFirstSearch +{ + /// Runs the breadth first search. The verticesToTraverse member of the struct needs to be initialized. + /// @param _forEachChild is a callable of the form [...](V const& _node, auto&& _addChild) { ... } + /// that is called for each visited node and is supposed to call _addChild(childNode) for every child + /// node of _node. + template + BreadthFirstSearch& run(ForEachChild&& _forEachChild) + { + while (!verticesToTraverse.empty()) + { + V v = *verticesToTraverse.begin(); + verticesToTraverse.erase(verticesToTraverse.begin()); + visited.insert(v); + + _forEachChild(v, [this](V _vertex) { + if (!visited.count(_vertex)) + verticesToTraverse.emplace(std::move(_vertex)); + }); + } + return *this; + } + + std::set verticesToTraverse; + std::set visited{}; +}; + } diff --git a/libdevcore/AnsiColorized.h b/libdevcore/AnsiColorized.h new file mode 100644 index 000000000..0ee54ab0a --- /dev/null +++ b/libdevcore/AnsiColorized.h @@ -0,0 +1,91 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include +#include + +namespace dev +{ + +namespace formatting +{ + +// control codes +static constexpr char const* RESET = "\033[0m"; +static constexpr char const* INVERSE = "\033[7m"; +static constexpr char const* BOLD = "\033[1m"; +static constexpr char const* BRIGHT = BOLD; + +// standard foreground colors +static constexpr char const* BLACK = "\033[30m"; +static constexpr char const* RED = "\033[31m"; +static constexpr char const* GREEN = "\033[32m"; +static constexpr char const* YELLOW = "\033[33m"; +static constexpr char const* BLUE = "\033[34m"; +static constexpr char const* MAGENTA = "\033[35m"; +static constexpr char const* CYAN = "\033[36m"; +static constexpr char const* WHITE = "\033[37m"; + +// standard background colors +static constexpr char const* BLACK_BACKGROUND = "\033[40m"; +static constexpr char const* RED_BACKGROUND = "\033[41m"; +static constexpr char const* GREEN_BACKGROUND = "\033[42m"; +static constexpr char const* YELLOW_BACKGROUND = "\033[43m"; +static constexpr char const* BLUE_BACKGROUND = "\033[44m"; +static constexpr char const* MAGENTA_BACKGROUND = "\033[45m"; +static constexpr char const* CYAN_BACKGROUND = "\033[46m"; +static constexpr char const* WHITE_BACKGROUND = "\033[47m"; + +// 256-bit-colors (incomplete set) +static constexpr char const* RED_BACKGROUND_256 = "\033[48;5;160m"; +static constexpr char const* ORANGE_BACKGROUND_256 = "\033[48;5;166m"; + +} + +/// AnsiColorized provides a convenience helper to colorize ostream with formatting-reset assured. +class AnsiColorized +{ +public: + AnsiColorized(std::ostream& _os, bool const _enabled, std::vector&& _formatting): + m_stream{_os}, m_enabled{_enabled}, m_codes{std::move(_formatting)} + { + if (m_enabled) + for (auto const& code: m_codes) + m_stream << code; + } + + ~AnsiColorized() + { + if (m_enabled) + m_stream << formatting::RESET; + } + + template + std::ostream& operator<<(T&& _t) + { + return m_stream << std::forward(_t); + } + +private: + std::ostream& m_stream; + bool m_enabled; + std::vector m_codes; +}; + +} diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h index 729ffb05d..4e427fc7e 100644 --- a/libdevcore/Assertions.h +++ b/libdevcore/Assertions.h @@ -24,7 +24,7 @@ #pragma once -#include "Exceptions.h" +#include namespace dev { diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index df42ce1a7..eb341be2d 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -1,8 +1,40 @@ -file(GLOB sources "*.cpp") -file(GLOB headers "*.h") +set(sources + Algorithms.h + AnsiColorized.h + Assertions.h + Common.h + CommonData.cpp + CommonData.h + CommonIO.cpp + CommonIO.h + Exceptions.cpp + Exceptions.h + FixedHash.h + IndentedWriter.cpp + IndentedWriter.h + InvertibleMap.h + IpfsHash.cpp + IpfsHash.h + JSON.cpp + JSON.h + Keccak256.cpp + Keccak256.h + picosha2.h + Result.h + StringUtils.cpp + StringUtils.h + SwarmHash.cpp + SwarmHash.h + UTF8.cpp + UTF8.h + vector_ref.h + Visitor.h + Whiskers.cpp + Whiskers.h +) -add_library(devcore ${sources} ${headers}) -target_link_libraries(devcore PRIVATE jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) +add_library(devcore ${sources}) +target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system) target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) add_dependencies(devcore lity_BuildInfo.h) diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 6208424e4..3ce116da9 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -39,26 +39,13 @@ #include -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif // defined(__GNUC__) - -// See https://github.com/ethereum/libweb3core/commit/90680a8c25bfb48b24371b4abcacde56c181517c -// See https://svn.boost.org/trac/boost/ticket/11328 -// Bob comment - perhaps we should just HARD FAIL here with Boost-1.58.00? -// It is quite old now, and requiring end-users to use a newer Boost release is probably not unreasonable. #include -#if (BOOST_VERSION == 105800) - #include "boost_multiprecision_number_compare_bug_workaround.hpp" -#endif // (BOOST_VERSION == 105800) +#if (BOOST_VERSION < 106500) +#error "Unsupported Boost version. At least 1.65 required." +#endif #include -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif // defined(__GNUC__) - #include #include #include @@ -87,7 +74,7 @@ using strings = std::vector; /// Interprets @a _u as a two's complement signed number and returns the resulting s256. inline s256 u2s(u256 _u) { - static const bigint c_end = bigint(1) << 256; + static bigint const c_end = bigint(1) << 256; if (boost::multiprecision::bit_test(_u, 255)) return s256(-(c_end - _u)); else @@ -97,13 +84,27 @@ inline s256 u2s(u256 _u) /// @returns the two's complement signed representation of the signed number _u. inline u256 s2u(s256 _u) { - static const bigint c_end = bigint(1) << 256; - if (_u >= 0) + static bigint const c_end = bigint(1) << 256; + if (_u >= 0) return u256(_u); - else + else return u256(c_end + _u); } +inline u256 exp256(u256 _base, u256 _exponent) +{ + using boost::multiprecision::limb_type; + u256 result = 1; + while (_exponent) + { + if (boost::multiprecision::bit_test(_exponent, 0)) + result *= _base; + _base *= _base; + _exponent >>= 1; + } + return result; +} + inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes) { std::ostringstream ss; diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 8d2639c96..1b0e97204 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -29,6 +29,28 @@ using namespace std; using namespace dev; +string dev::toHex(bytes const& _data, HexPrefix _prefix, HexCase _case) +{ + std::ostringstream ret; + if (_prefix == HexPrefix::Add) + ret << "0x"; + + int rix = _data.size() - 1; + for (uint8_t c: _data) + { + // switch hex case every four hexchars + auto hexcase = std::nouppercase; + if (_case == HexCase::Upper) + hexcase = std::uppercase; + else if (_case == HexCase::Mixed) + hexcase = (rix-- & 2) == 0 ? std::nouppercase : std::uppercase; + + ret << std::hex << hexcase << std::setfill('0') << std::setw(2) << size_t(c); + } + + return ret.str(); +} + int dev::fromHex(char _i, WhenError _throw) { if (_i >= '0' && _i <= '9') diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index fedd3af2b..bc71c88c4 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -33,6 +33,79 @@ #include #include #include +#include + +/// Operators need to stay in the global namespace. + +/// Concatenate the contents of a container onto a vector +template std::vector& operator+=(std::vector& _a, U const& _b) +{ + for (auto const& i: _b) + _a.push_back(i); + return _a; +} +/// Concatenate the contents of a container onto a vector, move variant. +template std::vector& operator+=(std::vector& _a, U&& _b) +{ + std::move(_b.begin(), _b.end(), std::back_inserter(_a)); + return _a; +} +/// Concatenate the contents of a container onto a set +template std::set& operator+=(std::set& _a, U const& _b) +{ + _a.insert(_b.begin(), _b.end()); + return _a; +} +/// Concatenate the contents of a container onto a set, move variant. +template std::set& operator+=(std::set& _a, U&& _b) +{ + for (auto&& x: _b) + _a.insert(std::move(x)); + return _a; +} +/// Concatenate two vectors of elements. +template +inline std::vector operator+(std::vector const& _a, std::vector const& _b) +{ + std::vector ret(_a); + ret += _b; + return ret; +} +/// Concatenate two vectors of elements, moving them. +template +inline std::vector operator+(std::vector&& _a, std::vector&& _b) +{ + std::vector ret(std::move(_a)); + if (&_a == &_b) + ret += ret; + else + 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; +} +/// Remove one set from another one. +template +inline std::set& operator-=(std::set& _a, std::set const& _b) +{ + for (auto const& x: _b) + _a.erase(x); + return _a; +} namespace dev { @@ -50,20 +123,20 @@ enum class HexPrefix DontAdd = 0, Add = 1, }; + +enum class HexCase +{ + Lower = 0, + Upper = 1, + Mixed = 2, +}; + /// Convert a series of bytes to the corresponding string of hex duplets. /// @param _w specifies the width of the first of the elements. Defaults to two - enough to represent a byte. /// @example toHex("A\x69") == "4169" -template -std::string toHex(T const& _data, int _w = 2, HexPrefix _prefix = HexPrefix::DontAdd) -{ - std::ostringstream ret; - unsigned ii = 0; - for (auto i: _data) - ret << std::hex << std::setfill('0') << std::setw(ii++ ? 2 : _w) << (int)(typename std::make_unsigned::type)i; - return (_prefix == HexPrefix::Add) ? "0x" + ret.str() : ret.str(); -} +std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower); -/// Converts a (printable) ASCII hex character into the correspnding integer value. +/// Converts a (printable) ASCII hex character into the corresponding integer value. /// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5 int fromHex(char _i, WhenError _throw); @@ -153,7 +226,7 @@ inline std::string formatNumber(bigint const& _value) if (_value < 0) return "-" + formatNumber(-_value); if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), 2, HexPrefix::Add); + return toHex(toCompactBigEndian(_value), HexPrefix::Add); else return _value.str(); } @@ -161,7 +234,7 @@ inline std::string formatNumber(bigint const& _value) inline std::string formatNumber(u256 const& _value) { if (_value > 0x1000000) - return toHex(toCompactBigEndian(_value), 2, HexPrefix::Add); + return toHex(toCompactBigEndian(_value), HexPrefix::Add); else return _value.str(); } @@ -175,9 +248,6 @@ inline std::string toCompactHexWithPrefix(u256 val) // Algorithms for string and string-like collections. -/// Escapes a string into the C-string representation. -/// @p _all if true will escape all characters, not just the unprintable ones. -std::string escaped(std::string const& _s, bool _all = true); /// Determine bytes required to encode the given integer value. @returns 0 if @a _i is zero. template inline unsigned bytesRequired(T _i) @@ -187,52 +257,12 @@ inline unsigned bytesRequired(T _i) for (; _i != 0; ++i, _i >>= 8) {} return i; } -/// Concatenate the contents of a container onto a vector -template std::vector& operator+=(std::vector& _a, U const& _b) -{ - for (auto const& i: _b) - _a.push_back(i); - return _a; -} -/// Concatenate the contents of a container onto a vector, move variant. -template std::vector& operator+=(std::vector& _a, U&& _b) -{ - std::move(_b.begin(), _b.end(), std::back_inserter(_a)); - return _a; -} -/// Concatenate the contents of a container onto a set -template std::set& operator+=(std::set& _a, U const& _b) -{ - _a.insert(_b.begin(), _b.end()); - return _a; -} -/// Concatenate two vectors of elements. -template -inline std::vector operator+(std::vector const& _a, std::vector const& _b) -{ - std::vector ret(_a); - ret += _b; - return ret; -} -/// Concatenate two vectors of elements, moving them. -template -inline std::vector operator+(std::vector&& _a, std::vector&& _b) -{ - std::vector ret(std::move(_a)); - if (&_a == &_b) - ret += ret; - else - ret += std::move(_b); - return ret; -} - template bool contains(T const& _t, V const& _v) { return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v); } - /// Function that iterates over a vector, calling a function on each of its /// elements. If that function returns a vector, the element is replaced by /// the returned vector. During the iteration, the original vector is only valid @@ -240,7 +270,7 @@ bool contains(T const& _t, V const& _v) /// place at the end, but already visited elements might be invalidated. /// If nothing is replaced, no copy is performed. template -void iterateReplacing(std::vector& _vector, const F& _f) +void iterateReplacing(std::vector& _vector, F const& _f) { // Concept: _f must be Callable, must accept param T&, must return optional> bool useModified = false; @@ -263,6 +293,59 @@ void iterateReplacing(std::vector& _vector, const F& _f) _vector = std::move(modifiedVector); } + +namespace detail +{ +template +void iterateReplacingWindow(std::vector& _vector, F const& _f, std::index_sequence) +{ + // Concept: _f must be Callable, must accept sizeof...(I) parameters of type T&, must return optional> + bool useModified = false; + std::vector modifiedVector; + size_t i = 0; + for (; i + sizeof...(I) <= _vector.size(); ++i) + { + if (boost::optional> r = _f(_vector[i + I]...)) + { + if (!useModified) + { + std::move(_vector.begin(), _vector.begin() + i, back_inserter(modifiedVector)); + useModified = true; + } + modifiedVector += std::move(*r); + i += sizeof...(I) - 1; + } + else if (useModified) + modifiedVector.emplace_back(std::move(_vector[i])); + } + if (useModified) + { + for (; i < _vector.size(); ++i) + modifiedVector.emplace_back(std::move(_vector[i])); + _vector = std::move(modifiedVector); + } +} + +} + +/// Function that iterates over the vector @param _vector, +/// calling the function @param _f on sequences of @tparam N of its +/// elements. If @param _f returns a vector, these elements are replaced by +/// the returned vector and the iteration continues with the next @tparam N elements. +/// If the function does not return a vector, the iteration continues with an overlapping +/// sequence of @tparam N elements that starts with the second element of the previous +/// iteration. +/// During the iteration, the original vector is only valid +/// on the current element and after that. The actual replacement takes +/// place at the end, but already visited elements might be invalidated. +/// If nothing is replaced, no copy is performed. +template +void iterateReplacingWindow(std::vector& _vector, F const& _f) +{ + // Concept: _f must be Callable, must accept N parameters of type T&, must return optional> + detail::iterateReplacingWindow(_vector, _f, std::make_index_sequence{}); +} + /// @returns true iff @a _str passess the hex address checksum test. /// @param _strict if false, hex strings with only uppercase or only lowercase letters /// are considered valid. @@ -275,4 +358,40 @@ std::string getChecksummedAddress(std::string const& _addr); bool isValidHex(std::string const& _string); bool isValidDecimal(std::string const& _string); +template +bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare) +{ + return std::equal(std::begin(_lhs), std::end(_lhs), std::begin(_rhs), std::end(_rhs), std::forward(_compare)); +} + +inline std::string findAnyOf(std::string const& _haystack, std::vector const& _needles) +{ + for (std::string const& needle: _needles) + if (_haystack.find(needle) != std::string::npos) + return needle; + return ""; +} + + +namespace detail +{ +template +void variadicEmplaceBack(std::vector&) {} +template +void variadicEmplaceBack(std::vector& _vector, A&& _a, Args&&... _args) +{ + _vector.emplace_back(std::forward(_a)); + variadicEmplaceBack(_vector, std::forward(_args)...); +} +} + +template +std::vector make_vector(Args&&... _args) +{ + std::vector result; + result.reserve(sizeof...(_args)); + detail::variadicEmplaceBack(result, std::forward(_args)...); + return result; +} + } diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index cc7305754..fa236b189 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -19,7 +19,11 @@ * @date 2014 */ -#include "CommonIO.h" +#include +#include + +#include + #include #include #include @@ -29,8 +33,6 @@ #include #include #endif -#include -#include "Assertions.h" using namespace std; using namespace dev; @@ -128,26 +130,6 @@ int dev::readStandardInputChar() return cin.get(); } -boost::filesystem::path dev::weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path) -{ - if (boost::filesystem::exists(_path)) - return boost::filesystem::canonical(_path); - else - { - boost::filesystem::path head(_path); - boost::filesystem::path tail; - for (auto it = --_path.end(); !head.empty(); --it) - { - if (boost::filesystem::exists(head)) - break; - tail = (*it) / tail; - head.remove_filename(); - } - head = boost::filesystem::canonical(head); - return head / tail; - } -} - string dev::absolutePath(string const& _path, string const& _reference) { boost::filesystem::path p(_path); diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index b9f941ead..61fa230aa 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -23,10 +23,10 @@ #pragma once +#include +#include #include #include -#include -#include "Common.h" namespace dev { @@ -50,10 +50,6 @@ std::string toString(_T const& _t) return o.str(); } -/// Partial implementation of boost::filesystem::weakly_canonical (available in boost>=1.60). -/// Should be replaced by the boost implementation as soon as support for boost<1.60 can be dropped. -boost::filesystem::path weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path); - /// @returns the absolute path corresponding to @a _path relative to @a _reference. std::string absolutePath(std::string const& _path, std::string const& _reference); diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index cfe72fbf6..943379d7a 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -31,7 +31,7 @@ namespace dev /// Base class for all exceptions. struct Exception: virtual std::exception, virtual boost::exception { - const char* what() const noexcept override; + char const* what() const noexcept override; /// @returns "FileName:LineNumber" referring to the point where the exception was thrown. std::string lineInfo() const; @@ -47,6 +47,7 @@ struct Exception: virtual std::exception, virtual boost::exception DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(BadHexCharacter); DEV_SIMPLE_EXCEPTION(FileError); +DEV_SIMPLE_EXCEPTION(DataTooLong); // error information to be added to exceptions using errinfo_invalidSymbol = boost::error_info; diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 24b898409..9245d7269 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -95,7 +95,7 @@ class FixedHash uint8_t operator[](unsigned _i) const { return m_data[_i]; } /// @returns the hash as a user-readable hex string. - std::string hex() const { return toHex(ref()); } + std::string hex() const { return toHex(asBytes()); } /// @returns a mutable byte vector_ref to the object's data. bytesRef ref() { return bytesRef(m_data.data(), N); } diff --git a/libdevcore/IndentedWriter.cpp b/libdevcore/IndentedWriter.cpp index 96aaf0fa4..1a85957bd 100644 --- a/libdevcore/IndentedWriter.cpp +++ b/libdevcore/IndentedWriter.cpp @@ -36,7 +36,7 @@ string IndentedWriter::format() const void IndentedWriter::newLine() { if (!m_lines.back().contents.empty()) - m_lines.push_back({ string(), m_lines.back().indentation }); + m_lines.emplace_back(Line{string(), m_lines.back().indentation}); } void IndentedWriter::indent() diff --git a/libdevcore/IndentedWriter.h b/libdevcore/IndentedWriter.h index 4ddd87ed3..563c4c96d 100644 --- a/libdevcore/IndentedWriter.h +++ b/libdevcore/IndentedWriter.h @@ -34,8 +34,6 @@ DEV_SIMPLE_EXCEPTION(IndentedWriterError); class IndentedWriter { public: - explicit IndentedWriter(): m_lines(std::vector{{std::string(), 0}}) {} - // Returns the formatted output. std::string format() const; @@ -61,7 +59,7 @@ class IndentedWriter unsigned indentation; }; - std::vector m_lines; + std::vector m_lines{{std::string(), 0}}; }; } 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/IpfsHash.cpp b/libdevcore/IpfsHash.cpp new file mode 100644 index 000000000..add459a2c --- /dev/null +++ b/libdevcore/IpfsHash.cpp @@ -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 . +*/ + +#include + +#include +#include +#include + +using namespace std; +using namespace dev; + +namespace +{ +bytes varintEncoding(size_t _n) +{ + bytes encoded; + while (_n > 0x7f) + { + encoded.emplace_back(uint8_t(0x80 | (_n & 0x7f))); + _n >>= 7; + } + encoded.emplace_back(_n); + return encoded; +} + +string base58Encode(bytes const& _data) +{ + static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"}; + bigint data(toHex(_data, HexPrefix::Add)); + string output; + while (data) + { + output += alphabet[size_t(data % alphabet.size())]; + data /= alphabet.size(); + } + reverse(output.begin(), output.end()); + return output; +} +} + +bytes dev::ipfsHash(string _data) +{ + if (_data.length() >= 1024 * 256) + BOOST_THROW_EXCEPTION( + DataTooLong() << + errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.") + ); + + bytes lengthAsVarint = varintEncoding(_data.size()); + + bytes protobufEncodedData; + // Type: File + protobufEncodedData += bytes{0x08, 0x02}; + if (!_data.empty()) + { + // Data (length delimited bytes) + protobufEncodedData += bytes{0x12}; + protobufEncodedData += lengthAsVarint; + protobufEncodedData += asBytes(std::move(_data)); + } + // filesize: length as varint + protobufEncodedData += bytes{0x18} + lengthAsVarint; + + // PBDag: + // Data: (length delimited bytes) + size_t protobufLength = protobufEncodedData.size(); + bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData); + // TODO Handle "large" files with multiple blocks + + // Multihash: sha2-256, 256 bits + bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData)); + return hash; +} + +string dev::ipfsHashBase58(string _data) +{ + return base58Encode(ipfsHash(std::move(_data))); +} diff --git a/libdevcore/IpfsHash.h b/libdevcore/IpfsHash.h new file mode 100644 index 000000000..e7d92c79b --- /dev/null +++ b/libdevcore/IpfsHash.h @@ -0,0 +1,37 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include + +#include + +namespace dev +{ + +/// Compute the "ipfs hash" of a file with the content @a _data. +/// The output will be the multihash of the UnixFS protobuf encoded data. +/// As hash function it will use sha2-256. +/// The effect is that the hash should be identical to the one produced by +/// the command `ipfs add `. +bytes ipfsHash(std::string _data); + +/// Compute the "ipfs hash" as above, but encoded in base58 as used by ipfs / bitcoin. +std::string ipfsHashBase58(std::string _data); + +} diff --git a/libdevcore/JSON.cpp b/libdevcore/JSON.cpp index 6317cc896..0b08616da 100644 --- a/libdevcore/JSON.cpp +++ b/libdevcore/JSON.cpp @@ -19,7 +19,11 @@ * @date 2018 */ -#include "JSON.h" +#include + +#include + +#include #include #include @@ -42,9 +46,9 @@ namespace class StreamWriterBuilder: public Json::StreamWriterBuilder { public: - explicit StreamWriterBuilder(map const& _settings) + explicit StreamWriterBuilder(map const& _settings) { - for (auto const& iter :_settings) + for (auto const& iter: _settings) this->settings_[iter.first] = iter.second; } }; @@ -87,14 +91,16 @@ bool parse(Json::CharReaderBuilder& _builder, string const& _input, Json::Value& string jsonPrettyPrint(Json::Value const& _input) { - static map settings{{"indentation", " "}}; + static map settings{{"indentation", " "}, {"enableYAMLCompatibility", true}}; static StreamWriterBuilder writerBuilder(settings); - return print(_input, writerBuilder); + string result = print(_input, writerBuilder); + boost::replace_all(result, " \n", "\n"); + return result; } string jsonCompactPrint(Json::Value const& _input) { - static map settings{{"indentation", ""}}; + static map settings{{"indentation", ""}}; static StreamWriterBuilder writerBuilder(settings); return print(_input, writerBuilder); } @@ -111,4 +117,10 @@ bool jsonParse(string const& _input, Json::Value& _json, string *_errs /* = null return parse(readerBuilder, _input, _json, _errs); } +bool jsonParseFile(string const& _fileName, Json::Value& _json, string *_errs /* = nullptr */) +{ + return jsonParse(readFileAsString(_fileName), _json, _errs); +} + + } // namespace dev diff --git a/libdevcore/JSON.h b/libdevcore/JSON.h index 1ce822cd4..ecb934673 100644 --- a/libdevcore/JSON.h +++ b/libdevcore/JSON.h @@ -48,4 +48,11 @@ bool jsonParseStrict(std::string const& _input, Json::Value& _json, std::string* /// \return \c true if the document was successfully parsed, \c false if an error occurred. bool jsonParse(std::string const& _input, Json::Value& _json, std::string* _errs = nullptr); +/// Parse a JSON string (@a _input) and writes resulting JSON object to (@a _json) +/// \param _input file containing JSON input +/// \param _json [out] resulting JSON object +/// \param _errs [out] Formatted error messages +/// \return \c true if the document was successfully parsed, \c false if an error occurred. +bool jsonParseFile(std::string const& _fileName, Json::Value& _json, std::string* _errs = nullptr); + } diff --git a/libdevcore/Keccak256.cpp b/libdevcore/Keccak256.cpp index 7933fc7e0..752973822 100644 --- a/libdevcore/Keccak256.cpp +++ b/libdevcore/Keccak256.cpp @@ -47,17 +47,17 @@ namespace /******** The Keccak-f[1600] permutation ********/ /*** Constants. ***/ -static const uint8_t rho[24] = \ +static uint8_t const rho[24] = \ { 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44}; -static const uint8_t pi[24] = \ +static uint8_t const pi[24] = \ {10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1}; -static const uint64_t RC[24] = \ +static uint64_t const RC[24] = \ {1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, @@ -71,8 +71,8 @@ static const uint64_t RC[24] = \ #define REPEAT24(e) REPEAT6(e e e e) #define REPEAT5(e) e e e e e #define FOR5(v, s, e) \ - v = 0; \ - REPEAT5(e; v += s;) + v = 0; \ + REPEAT5(e; v += s;) /*** Keccak-f[1600] ***/ static inline void keccakf(void* state) { @@ -84,25 +84,25 @@ static inline void keccakf(void* state) { uint8_t x, y; // Theta FOR5(x, 1, - b[x] = 0; - FOR5(y, 5, - b[x] ^= a[x + y]; )) + b[x] = 0; + FOR5(y, 5, + b[x] ^= a[x + y]; )) FOR5(x, 1, - FOR5(y, 5, - a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) + FOR5(y, 5, + a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) // Rho and pi uint64_t t = a[1]; x = 0; REPEAT24(b[0] = a[pi[x]]; - a[pi[x]] = rol(t, rho[x]); - t = b[0]; - x++; ) + a[pi[x]] = rol(t, rho[x]); + t = b[0]; + x++; ) // Chi FOR5(y, - 5, - FOR5(x, 1, + 5, + FOR5(x, 1, b[x] = a[y + x];) - FOR5(x, 1, + FOR5(x, 1, a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); )) // Iota a[0] ^= RC[i]; @@ -115,19 +115,19 @@ static inline void keccakf(void* state) { #define _(S) do { S } while (0) #define FOR(i, ST, L, S) \ - _(for (size_t i = 0; i < L; i += ST) { S; }) + _(for (size_t i = 0; i < L; i += ST) { S; }) #define mkapply_ds(NAME, S) \ - static inline void NAME(uint8_t* dst, \ - const uint8_t* src, \ - size_t len) { \ - FOR(i, 1, len, S); \ - } + static inline void NAME(uint8_t* dst, \ + uint8_t const* src, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } #define mkapply_sd(NAME, S) \ - static inline void NAME(const uint8_t* src, \ - uint8_t* dst, \ - size_t len) { \ - FOR(i, 1, len, S); \ - } + static inline void NAME(uint8_t const* src, \ + uint8_t* dst, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } mkapply_ds(xorin, dst[i] ^= src[i]) // xorin mkapply_sd(setout, dst[i] = src[i]) // setout @@ -137,18 +137,18 @@ mkapply_sd(setout, dst[i] = src[i]) // setout // Fold P*F over the full blocks of an input. #define foldP(I, L, F) \ - while (L >= rate) { \ - F(a, I, rate); \ - P(a); \ - I += rate; \ - L -= rate; \ - } + while (L >= rate) { \ + F(a, I, rate); \ + P(a); \ + I += rate; \ + L -= rate; \ + } /** The sponge-based hash construction. **/ inline void hash( uint8_t* out, size_t outlen, - const uint8_t* in, + uint8_t const* in, size_t inlen, size_t rate, uint8_t delim diff --git a/libdevcore/Result.h b/libdevcore/Result.h new file mode 100644 index 000000000..ebc0db0e1 --- /dev/null +++ b/libdevcore/Result.h @@ -0,0 +1,85 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +#pragma once + +#include + +namespace dev +{ + +/// Simple generic result that holds a value and an optional error message. +/// Results can be implicitly converted to and created from the type of +/// the value they hold. The class is mainly designed for a result type of +/// bool or pointer type. The idea is that the default constructed value of +/// the result type is interpreted as an error value. +/// +/// Result check() +/// { +/// if (false) +/// return Result::err("Error message.") +/// return true; +/// } +/// + +template +class Result +{ +public: + /// Constructs a result with _value and an empty message. + /// This is meant to be called with valid results. Please use + /// the static err() member function to signal an error. + Result(ResultType _value): Result(_value, std::string{}) {} + + /// Constructs a result with a default-constructed value and an + /// error message. + static Result err(std::string _message) + { + return Result{ResultType{}, std::move(_message)}; + } + + /// @{ + /// @name Wrapper functions + /// Wrapper functions that provide implicit conversions to and explicit retrieval of + /// the value this result holds. + operator ResultType const&() const { return m_value; } + ResultType const& get() const { return m_value; } + /// @} + + /// @returns the error message (can be empty). + std::string const& message() const { return m_message; } + + /// Merges _other into this using the _merger + /// and appends the error messages. Meant to be called + /// with logical operators like logical_and, etc. + template + void merge(Result const& _other, F _merger) + { + m_value = _merger(m_value, _other.get()); + m_message += _other.message(); + } + +private: + explicit Result(ResultType _value, std::string _message): + m_value(std::move(_value)), + m_message(std::move(_message)) + {} + + ResultType m_value; + std::string m_message; +}; + +} diff --git a/libdevcore/StringUtils.cpp b/libdevcore/StringUtils.cpp index 50bf7cce8..fd6cad339 100644 --- a/libdevcore/StringUtils.cpp +++ b/libdevcore/StringUtils.cpp @@ -21,7 +21,7 @@ * String routines */ -#include "StringUtils.h" +#include #include #include #include @@ -91,8 +91,25 @@ string dev::quotedAlternativesList(vector const& suggestions) vector quotedSuggestions; for (auto& suggestion: suggestions) - quotedSuggestions.push_back("\"" + suggestion + "\""); + quotedSuggestions.emplace_back("\"" + suggestion + "\""); return joinHumanReadable(quotedSuggestions, ", ", " or "); } +string dev::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix) +{ + string result; + if (_startSuffix < _endSuffix) + { + result = _baseName + to_string(_startSuffix++); + while (_startSuffix < _endSuffix) + result += ", " + _baseName + to_string(_startSuffix++); + } + else if (_endSuffix < _startSuffix) + { + result = _baseName + to_string(_endSuffix++); + while (_endSuffix < _startSuffix) + result = _baseName + to_string(_endSuffix++) + ", " + result; + } + return result; +} diff --git a/libdevcore/StringUtils.h b/libdevcore/StringUtils.h index b02b9d129..b6deba790 100644 --- a/libdevcore/StringUtils.h +++ b/libdevcore/StringUtils.h @@ -26,6 +26,8 @@ #include #include +#include + namespace dev { @@ -37,6 +39,12 @@ size_t stringDistance(std::string const& _str1, std::string const& _str2); // Return a string having elements of suggestions as quoted, alternative suggestions. e.g. "a", "b" or "c" std::string quotedAlternativesList(std::vector const& suggestions); +/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed +/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix, +/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix. +/// If @a _startSuffix == @a _endSuffix, the empty string is returned. +std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix); + /// Joins collection of strings into one string with separators between, last separator can be different. /// @param _list collection of strings to join /// @param _separator defaults to ", " @@ -72,4 +80,100 @@ std::string joinHumanReadable return result; } +/// Joins collection of strings just like joinHumanReadable, but prepends the separator +/// unless the collection is empty. +template +std::string joinHumanReadablePrefixed +( + T const& _list, + std::string const& _separator = ", ", + std::string const& _lastSeparator = "" +) +{ + if (begin(_list) == end(_list)) + return {}; + else + return _separator + joinHumanReadable(_list, _separator, _lastSeparator); +} + +/// Formats large numbers to be easily readable by humans. +/// Returns decimal representation for smaller numbers; hex for large numbers. +/// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in +/// formulaic form like 0x01 * 2**24 - 1. +/// @a T will typically by unsigned, u160, u256 or bigint. +/// @param _value to be formatted +/// @param _useTruncation if true, internal truncation is also applied, +/// like 0x5555...{+56 more}...5555 +/// @example formatNumber((u256)0x7ffffff) +template +inline std::string formatNumberReadable( + T const& _value, + bool _useTruncation = false +) +{ + static_assert( + std::is_same::value || !std::numeric_limits::is_signed, + "only unsigned types or bigint supported" + ); //bigint does not carry sign bit on shift + + // smaller numbers return as decimal + if (_value <= 0x1000000) + return _value.str(); + + HexCase hexcase = HexCase::Mixed; + HexPrefix prefix = HexPrefix::Add; + + // when multiple trailing zero bytes, format as N * 2**x + int i = 0; + T v = _value; + for (; (v & 0xff) == 0; v >>= 8) + ++i; + if (i > 2) + { + // 0x100 yields 2**8 (N is 1 and redundant) + if (v == 1) + return "2**" + std::to_string(i * 8); + return toHex(toCompactBigEndian(v), prefix, hexcase) + + " * 2**" + + std::to_string(i * 8); + } + + // when multiple trailing FF bytes, format as N * 2**x - 1 + i = 0; + for (v = _value; (v & 0xff) == 0xff; v >>= 8) + ++i; + if (i > 2) + { + // 0xFF yields 2**8 - 1 (v is 0 in that case) + if (v == 0) + return "2**" + std::to_string(i * 8) + " - 1"; + return toHex(toCompactBigEndian(T(v + 1)), prefix, hexcase) + + " * 2**" + std::to_string(i * 8) + + " - 1"; + } + + std::string str = toHex(toCompactBigEndian(_value), prefix, hexcase); + if (_useTruncation) + { + // return as interior-truncated hex. + int len = str.size(); + + if (len < 24) + return str; + + int const initialChars = (prefix == HexPrefix::Add) ? 6 : 4; + int const finalChars = 4; + int numSkipped = len - initialChars - finalChars; + + return str.substr(0, initialChars) + + "...{+" + + std::to_string(numSkipped) + + " more}..." + + str.substr(len-finalChars, len); + } + + // otherwise, show whole value. + return str; +} + } diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp index 3b8d2f3e3..b317ff59a 100644 --- a/libdevcore/SwarmHash.cpp +++ b/libdevcore/SwarmHash.cpp @@ -61,9 +61,56 @@ h256 swarmHashIntermediate(string const& _input, size_t _offset, size_t _length) return swarmHashSimple(ref, _length); } +h256 bmtHash(bytesConstRef _data) +{ + if (_data.size() <= 64) + return keccak256(_data); + + size_t midPoint = _data.size() / 2; + return keccak256( + bmtHash(_data.cropped(0, midPoint)).asBytes() + + bmtHash(_data.cropped(midPoint)).asBytes() + ); +} + +h256 chunkHash(bytesConstRef const _data, bool _forceHigherLevel = false) +{ + bytes dataToHash; + if (_data.size() < 0x1000) + dataToHash = _data.toBytes(); + else if (_data.size() == 0x1000 && !_forceHigherLevel) + dataToHash = _data.toBytes(); + else + { + size_t maxRepresentedSize = 0x1000; + while (maxRepresentedSize * (0x1000 / 32) < _data.size()) + maxRepresentedSize *= (0x1000 / 32); + // If remaining size is 0x1000, but maxRepresentedSize is not, + // we have to still do one level of the chunk hashes. + bool forceHigher = maxRepresentedSize > 0x1000; + for (size_t i = 0; i < _data.size(); i += maxRepresentedSize) + { + size_t size = std::min(maxRepresentedSize, _data.size() - i); + dataToHash += chunkHash(_data.cropped(i, size), forceHigher).asBytes(); + } + } + + dataToHash.resize(0x1000, 0); + return keccak256(toLittleEndian(_data.size()) + bmtHash(&dataToHash).asBytes()); +} + + } -h256 dev::swarmHash(string const& _input) +h256 dev::bzzr0Hash(string const& _input) { return swarmHashIntermediate(_input, 0, _input.size()); } + + +h256 dev::bzzr1Hash(bytes const& _input) +{ + if (_input.empty()) + return h256{}; + return chunkHash(&_input); +} diff --git a/libdevcore/SwarmHash.h b/libdevcore/SwarmHash.h index a06f7bda6..0d1d2622f 100644 --- a/libdevcore/SwarmHash.h +++ b/libdevcore/SwarmHash.h @@ -26,7 +26,15 @@ namespace dev { -/// Compute the "swarm hash" of @a _input -h256 swarmHash(std::string const& _input); +/// Compute the "swarm hash" of @a _input (OLD 0x1000-section version) +h256 bzzr0Hash(std::string const& _input); + +/// Compute the "bzz hash" of @a _input (the NEW binary / BMT version) +h256 bzzr1Hash(bytes const& _input); + +inline h256 bzzr1Hash(std::string const& _input) +{ + return bzzr1Hash(asBytes(_input)); +} } diff --git a/libdevcore/UTF8.cpp b/libdevcore/UTF8.cpp index 2ae720ec4..a9fcf0ce3 100644 --- a/libdevcore/UTF8.cpp +++ b/libdevcore/UTF8.cpp @@ -21,12 +21,10 @@ * UTF-8 related helpers */ -#include "UTF8.h" - +#include namespace dev { - namespace { @@ -77,7 +75,9 @@ bool isWellFormed(unsigned char byte1, unsigned char byte2) return false; } -bool validateUTF8(const unsigned char *_input, size_t _length, size_t& _invalidPosition) +} + +bool validateUTF8(unsigned char const* _input, size_t _length, size_t& _invalidPosition) { bool valid = true; size_t i = 0; @@ -133,8 +133,6 @@ bool validateUTF8(const unsigned char *_input, size_t _length, size_t& _invalidP return false; } -} - bool validateUTF8(std::string const& _input, size_t& _invalidPosition) { return validateUTF8(reinterpret_cast(_input.c_str()), _input.length(), _invalidPosition); diff --git a/libdevcore/Whiskers.cpp b/libdevcore/Whiskers.cpp index a6db35c8e..c48c8d6f1 100644 --- a/libdevcore/Whiskers.cpp +++ b/libdevcore/Whiskers.cpp @@ -30,64 +30,89 @@ using namespace std; using namespace dev; -Whiskers::Whiskers(string const& _template): -m_template(_template) +Whiskers::Whiskers(string _template): + m_template(move(_template)) { } -Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value) +Whiskers& Whiskers::operator()(string _parameter, string _value) { - assertThrow( - m_parameters.count(_parameter) == 0, - WhiskersError, - _parameter + " already set." - ); - assertThrow( - m_listParameters.count(_parameter) == 0, - WhiskersError, - _parameter + " already set as list parameter." - ); - m_parameters[_parameter] = _value; + checkParameterValid(_parameter); + checkParameterUnknown(_parameter); + m_parameters[move(_parameter)] = move(_value); + return *this; +} +Whiskers& Whiskers::operator()(string _parameter, bool _value) +{ + checkParameterValid(_parameter); + checkParameterUnknown(_parameter); + m_conditions[move(_parameter)] = _value; return *this; } -Whiskers& Whiskers::operator ()( - string const& _listParameter, - vector> const& _values +Whiskers& Whiskers::operator()( + string _listParameter, + 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; +} + +string Whiskers::render() const +{ + return replace(m_template, m_parameters, m_conditions, m_listParameters); +} + +void Whiskers::checkParameterValid(string const& _parameter) const +{ + static boost::regex validParam("^" + paramRegex() + "$"); assertThrow( - m_listParameters.count(_listParameter) == 0, - WhiskersError, - _listParameter + " already set." - ); - assertThrow( - m_parameters.count(_listParameter) == 0, + boost::regex_match(_parameter, validParam), WhiskersError, - _listParameter + " already set as value parameter." + "Parameter" + _parameter + " contains invalid characters." ); - m_listParameters[_listParameter] = _values; - - return *this; } -string Whiskers::render() const +void Whiskers::checkParameterUnknown(string const& _parameter) const { - return replace(m_template, m_parameters, m_listParameters); + assertThrow( + !m_parameters.count(_parameter), + WhiskersError, + _parameter + " already set as value parameter." + ); + assertThrow( + !m_conditions.count(_parameter), + WhiskersError, + _parameter + " already set as condition parameter." + ); + assertThrow( + !m_listParameters.count(_parameter), + WhiskersError, + _parameter + " already set as list parameter." + ); } string Whiskers::replace( string const& _template, StringMap const& _parameters, + map const& _conditions, map> const& _listParameters ) { 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]); + string listName(_match[2]); + string conditionName(_match[4]); if (!tagName.empty()) { assertThrow( @@ -99,20 +124,32 @@ string Whiskers::replace( ); return _parameters.at(tagName); } - else + else if (!listName.empty()) { - string listName(_match[2]); string templ(_match[3]); - assertThrow(!listName.empty(), WhiskersError, ""); assertThrow( _listParameters.count(listName), WhiskersError, "List parameter " + listName + " not set." ); string replacement; for (auto const& parameters: _listParameters.at(listName)) - replacement += replace(templ, joinMaps(_parameters, parameters)); + replacement += replace(templ, joinMaps(_parameters, parameters), _conditions); return replacement; } + else + { + assertThrow(!conditionName.empty(), WhiskersError, ""); + assertThrow( + _conditions.count(conditionName), + WhiskersError, "Condition parameter " + conditionName + " not set." + ); + return replace( + _conditions.at(conditionName) ? _match[5] : _match[7], + _parameters, + _conditions, + _listParameters + ); + } }); } diff --git a/libdevcore/Whiskers.h b/libdevcore/Whiskers.h index 21d46af4c..5d326bf02 100644 --- a/libdevcore/Whiskers.h +++ b/libdevcore/Whiskers.h @@ -34,53 +34,77 @@ namespace dev DEV_SIMPLE_EXCEPTION(WhiskersError); -/// -/// Moustache-like templates. -/// -/// Usage: -/// std::vector> listValues(2); -/// listValues[0]["k"] = "key1"; -/// listValues[0]["v"] = "value1"; -/// listValues[1]["k"] = "key2"; -/// listValues[1]["v"] = "value2"; -/// auto s = Whiskers("\n<#list> -> \n") -/// ("p1", "HEAD") -/// ("list", listValues) -/// .render(); -/// -/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" -/// -/// Note that lists cannot themselves contain lists - this would be a future feature. +/** + * Moustache-like templates. + * + * Usage: + * std::vector> listValues(2); + * listValues[0]["k"] = "key1"; + * listValues[0]["v"] = "value1"; + * listValues[1]["k"] = "key2"; + * listValues[1]["v"] = "value2"; + * auto s = Whiskers("y\n<#list> -> \n") + * ("p1", "HEAD") + * ("c", true) + * ("list", listValues) + * .render(); + * + * results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n" + * + * Note that lists cannot themselves contain lists - this would be a future feature. + * + * The elements are: + * - Regular parameter: + * just replaced + * - Condition parameter: ......, where "" is optional + * replaced (and recursively expanded) by the first part if the condition is true + * and by the second (or empty string if missing) if the condition is false + * - List parameter: <#list>... + * The part between the tags is repeated as often as values are provided + * in the mapping. Each list element can have its own parameter -> value mapping. + */ class Whiskers { public: using StringMap = std::map; using StringListMap = std::map>; - explicit Whiskers(std::string const& _template); + explicit Whiskers(std::string _template); - /// Sets a single parameter, . - Whiskers& operator()(std::string const& _parameter, std::string const& _value); + /// Sets a single regular parameter, . + Whiskers& operator()(std::string _parameter, std::string _value); + Whiskers& operator()(std::string _parameter, char const* _value) { return (*this)(_parameter, std::string{_value}); } + /// Sets a condition parameter, ...... + Whiskers& operator()(std::string _parameter, bool _value); /// Sets a list parameter, <#listName> . Whiskers& operator()( - std::string const& _listParameter, - std::vector const& _values + std::string _listParameter, + std::vector _values ); std::string render() const; private: + // Prevent implicit cast to bool + Whiskers& operator()(std::string _parameter, long long); + void checkParameterValid(std::string const& _parameter) const; + void checkParameterUnknown(std::string const& _parameter) const; + static std::string replace( std::string const& _template, StringMap const& _parameters, + std::map const& _conditions, 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); std::string m_template; StringMap m_parameters; + std::map m_conditions; StringListMap m_listParameters; }; diff --git a/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp b/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp deleted file mode 100644 index 2568e17d4..000000000 --- a/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp +++ /dev/null @@ -1,520 +0,0 @@ - -// This is a copy of boost/multiprecision/detail/number_compare.hpp from boost 1.59 to replace buggy version from 1.58. - -#ifdef BOOST_MP_COMPARE_HPP -#error This bug workaround header must be included before original boost/multiprecision/detail/number_compare.hpp -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Copyright 2012 John Maddock. Distributed under the Boost -// Software License, Version 1.0. (See accompanying file -// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_MP_COMPARE_HPP -#define BOOST_MP_COMPARE_HPP - -// A copy of boost/multiprecision/traits/is_backend.hpp -#ifndef BOOST_MP_IS_BACKEND_HPP -#define BOOST_MP_IS_BACKEND_HPP - -#include -#include -#include -#include -#include - -namespace boost{ namespace multiprecision{ namespace detail{ - - BOOST_MPL_HAS_XXX_TRAIT_DEF(signed_types) - BOOST_MPL_HAS_XXX_TRAIT_DEF(unsigned_types) - BOOST_MPL_HAS_XXX_TRAIT_DEF(float_types) - - template - struct is_backend - { - static const bool value = has_signed_types::value && has_unsigned_types::value && has_float_types::value; - }; - - template - struct other_backend - { - typedef typename boost::conditional< - boost::is_same, number >::value, - number, number >::type type; - }; - - template - struct number_from_backend - { - typedef typename boost::conditional < - boost::is_convertible >::value, - number, - typename other_backend::type > ::type type; - }; - - template - struct is_first_backend_imp{ static const bool value = false; }; - template - struct is_first_backend_imp{ static const bool value = is_convertible >::value || is_convertible >::value; }; - - template - struct is_first_backend : is_first_backend_imp::value, T, U> {}; - - template - struct is_second_backend_imp{ static const bool value = false; }; - template - struct is_second_backend_imp{ static const bool value = is_convertible >::value || is_convertible >::value; }; - - template - struct is_second_backend : is_second_backend_imp::value, T, U> {}; - -} -} -} - -#endif // BOOST_MP_IS_BACKEND_HPP - -// -// Comparison operators for number. -// - -namespace boost{ namespace multiprecision{ - -namespace default_ops{ - -template -inline bool eval_eq(const B& a, const B& b) -{ - return a.compare(b) == 0; -} -template -inline typename enable_if_c::value, bool>::type eval_eq(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(b); - return eval_eq(a, t.backend()); -} -template -inline typename enable_if_c::value, bool>::type eval_eq(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(a); - return eval_eq(t.backend(), b); -} - -template -inline bool eval_lt(const B& a, const B& b) -{ - return a.compare(b) < 0; -} -template -inline typename enable_if_c::value, bool>::type eval_lt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(b); - return eval_lt(a, t.backend()); -} -template -inline typename enable_if_c::value, bool>::type eval_lt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(a); - return eval_lt(t.backend(), b); -} - -template -inline bool eval_gt(const B& a, const B& b) -{ - return a.compare(b) > 0; -} -template -inline typename enable_if_c::value, bool>::type eval_gt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(b); - return eval_gt(a, t.backend()); -} -template -inline typename enable_if_c::value, bool>::type eval_gt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend::type t(a); - return eval_gt(t.backend(), b); -} - -} // namespace default_ops - -namespace detail{ - -template -struct is_valid_mixed_compare : public mpl::false_ {}; - -template -struct is_valid_mixed_compare, Val> : public is_convertible > {}; - -template -struct is_valid_mixed_compare, number > : public mpl::false_ {}; - -template -struct is_valid_mixed_compare, expression > - : public mpl::bool_, number >::value> {}; - -template -struct is_valid_mixed_compare, number > - : public mpl::bool_, number >::value> {}; - -template -inline BOOST_CONSTEXPR typename boost::enable_if_c::value != number_kind_floating_point, bool>::type is_unordered_value(const number&) -{ - return false; -} -template -inline BOOST_CONSTEXPR typename boost::enable_if_c::value == number_kind_floating_point, bool>::type is_unordered_value(const number& a) -{ - using default_ops::eval_fpclassify; - return eval_fpclassify(a.backend()) == FP_NAN; -} - -template -inline BOOST_CONSTEXPR typename boost::enable_if_c::value != number_kind_floating_point, bool>::type is_unordered_value(const Arithmetic&) -{ - return false; -} -template -inline BOOST_CONSTEXPR typename boost::enable_if_c::value == number_kind_floating_point, bool>::type is_unordered_value(const Arithmetic& a) -{ - return (boost::math::isnan)(a); -} - -template -inline BOOST_CONSTEXPR bool is_unordered_comparison(const T& a, const U& b) -{ - return is_unordered_value(a) || is_unordered_value(b); -} - -} - -template -inline bool operator == (const number& a, const number& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator == (const number& a, const Arithmetic& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator == (const Arithmetic& a, const number& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator == (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_eq; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return eval_eq(t.backend(), result_type::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator == (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_eq; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return eval_eq(t.backend(), result_type::canonical_value(b)); -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator == (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_eq; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return eval_eq(t.backend(), t2.backend()); -} - -template -inline bool operator != (const number& a, const number& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator != (const number& a, const Arithmetic& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator != (const Arithmetic& a, const number& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator != (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_eq; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return true; - return !eval_eq(t.backend(), result_type::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator != (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_eq; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return true; - return !eval_eq(t.backend(), result_type::canonical_value(b)); -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator != (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_eq; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return true; - return !eval_eq(t.backend(), t2.backend()); -} - -template -inline bool operator < (const number& a, const number& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator < (const number& a, const Arithmetic& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator < (const Arithmetic& a, const number& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator < (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_gt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return eval_gt(t.backend(), result_type::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator < (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_lt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return eval_lt(t.backend(), result_type::canonical_value(b)); -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator < (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_lt; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return eval_lt(t.backend(), t2.backend()); -} - -template -inline bool operator > (const number& a, const number& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator > (const number& a, const Arithmetic& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator > (const Arithmetic& a, const number& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator > (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_lt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return a > t; -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator > (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_gt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return t > b; -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator > (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_gt; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return t > t2; -} - -template -inline bool operator <= (const number& a, const number& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator <= (const number& a, const Arithmetic& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator <= (const Arithmetic& a, const number& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator <= (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_lt; - if(detail::is_unordered_value(a) || detail::is_unordered_value(b)) - return false; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return !eval_lt(t.backend(), result_type::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator <= (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_gt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return !eval_gt(t.backend(), result_type::canonical_value(b)); -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator <= (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_gt; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return !eval_gt(t.backend(), t2.backend()); -} - -template -inline bool operator >= (const number& a, const number& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(a.backend(), b.backend()); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator >= (const number& a, const Arithmetic& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(a.backend(), number::canonical_value(b)); -} -template -inline typename enable_if_c, Arithmetic>::value, bool>::type - operator >= (const Arithmetic& a, const number& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(b.backend(), number::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator >= (const Arithmetic& a, const detail::expression& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_gt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return !eval_gt(t.backend(), result_type::canonical_value(a)); -} -template -inline typename enable_if_c::result_type, Arithmetic>::value, bool>::type - operator >= (const detail::expression& a, const Arithmetic& b) -{ - typedef typename detail::expression::result_type result_type; - using default_ops::eval_lt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return !eval_lt(t.backend(), result_type::canonical_value(b)); -} -template -inline typename enable_if::result_type, typename detail::expression::result_type>, bool>::type - operator >= (const detail::expression& a, const detail::expression& b) -{ - using default_ops::eval_lt; - typename detail::expression::result_type t(a); - typename detail::expression::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return !eval_lt(t.backend(), t2.backend()); -} - - -}} // namespaces - -#endif // BOOST_MP_COMPARE_HPP diff --git a/libdevcore/picosha2.h b/libdevcore/picosha2.h new file mode 100644 index 000000000..61aeb3b95 --- /dev/null +++ b/libdevcore/picosha2.h @@ -0,0 +1,288 @@ +/* +The MIT License (MIT) + +Copyright (C) 2014 okdshin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +#pragma once + +//picosha2:20140213 +#include +#include +#include +#include +#include +#include +#include + +namespace picosha2 +{ + +namespace detail +{ + +inline uint8_t mask_8bit(uint8_t x) +{ + return x & 0xff; +} + +inline uint32_t mask_32bit(uint32_t x) +{ + return x & 0xffffffff; +} + +static uint32_t const add_constant[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static uint32_t const initial_message_digest[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 +}; + +inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) ^ ((~x) & z); +} + +inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) ^ (x & z) ^ (y & z); +} + +inline uint32_t rotr(uint32_t x, std::size_t n) +{ + assert(n < 32); + return mask_32bit((x >> n) | (x << (32 - n))); +} + +inline uint32_t bsig0(uint32_t x) +{ + return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); +} + +inline uint32_t bsig1(uint32_t x) +{ + return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); +} + +inline uint32_t shr(uint32_t x, std::size_t n) +{ + assert(n < 32); + return x >> n; +} + +inline uint32_t ssig0(uint32_t x) +{ + return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); +} + +inline uint32_t ssig1(uint32_t x) +{ + return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); +} + +template +void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) +{ + (void)last; // FIXME: check this is valid + uint32_t w[64]; + std::fill(w, w+64, 0); + for (std::size_t i = 0; i < 16; ++i) + w[i] = (static_cast(mask_8bit(*(first + i * 4))) << 24) + | (static_cast(mask_8bit(*(first + i * 4 + 1))) << 16) + | (static_cast(mask_8bit(*(first + i * 4 + 2))) << 8) + | (static_cast(mask_8bit(*(first + i * 4 + 3)))); + for (std::size_t i = 16; i < 64; ++i) + w[i] = mask_32bit(ssig1(w[i-2])+w[i-7]+ssig0(w[i-15])+w[i-16]); + + uint32_t a = *message_digest; + uint32_t b = *(message_digest + 1); + uint32_t c = *(message_digest + 2); + uint32_t d = *(message_digest + 3); + uint32_t e = *(message_digest + 4); + uint32_t f = *(message_digest + 5); + uint32_t g = *(message_digest + 6); + uint32_t h = *(message_digest + 7); + + for (std::size_t i = 0; i < 64; ++i) + { + uint32_t temp1 = h+bsig1(e)+ch(e,f,g)+add_constant[i]+w[i]; + uint32_t temp2 = bsig0(a)+maj(a,b,c); + h = g; + g = f; + f = e; + e = mask_32bit(d+temp1); + d = c; + c = b; + b = a; + a = mask_32bit(temp1+temp2); + } + *message_digest += a; + *(message_digest+1) += b; + *(message_digest+2) += c; + *(message_digest+3) += d; + *(message_digest+4) += e; + *(message_digest+5) += f; + *(message_digest+6) += g; + *(message_digest+7) += h; + for (std::size_t i = 0; i < 8; ++i) + *(message_digest+i) = mask_32bit(*(message_digest+i)); +} + +}//namespace detail + +class hash256_one_by_one +{ +public: + hash256_one_by_one() + { + init(); + } + + void init() + { + buffer_.clear(); + std::fill(data_length_digits_, data_length_digits_ + 4, 0); + std::copy(detail::initial_message_digest, detail::initial_message_digest+8, h_); + } + + template + void process(RaIter first, RaIter last) + { + add_to_data_length(std::distance(first, last)); + std::copy(first, last, std::back_inserter(buffer_)); + std::size_t i = 0; + for (;i + 64 <= buffer_.size(); i+=64) + detail::hash256_block(h_, buffer_.begin()+i, buffer_.begin()+i+64); + buffer_.erase(buffer_.begin(), buffer_.begin()+i); + } + + void finish() + { + uint8_t temp[64]; + std::fill(temp, temp+64, 0); + std::size_t remains = buffer_.size(); + std::copy(buffer_.begin(), buffer_.end(), temp); + temp[remains] = 0x80; + + if (remains > 55) + { + std::fill(temp+remains+1, temp+64, 0); + detail::hash256_block(h_, temp, temp+64); + std::fill(temp, temp+64-4, 0); + } + else + std::fill(temp+remains+1, temp+64-4, 0); + + write_data_bit_length(&(temp[56])); + detail::hash256_block(h_, temp, temp+64); + } + + template + void get_hash_bytes(OutIter first, OutIter last) const + { + for (uint32_t const* iter = h_; iter != h_ + 8; ++iter) + for (std::size_t i = 0; i < 4 && first != last; ++i) + *(first++) = detail::mask_8bit(static_cast(*iter >> (24 - 8 * i))); + } + +private: + void add_to_data_length(uint32_t n) + { + uint32_t carry = 0; + data_length_digits_[0] += n; + for (std::size_t i = 0; i < 4; ++i) + { + data_length_digits_[i] += carry; + if (data_length_digits_[i] >= 65536u) + { + carry = data_length_digits_[i] >> 16; + data_length_digits_[i] &= 65535u; + } + else + break; + } + } + void write_data_bit_length(uint8_t* begin) + { + uint32_t data_bit_length_digits[4]; + std::copy( + data_length_digits_, data_length_digits_ + 4, + data_bit_length_digits + ); + + // convert byte length to bit length (multiply 8 or shift 3 times left) + uint32_t carry = 0; + for (std::size_t i = 0; i < 4; ++i) + { + uint32_t before_val = data_bit_length_digits[i]; + data_bit_length_digits[i] <<= 3; + data_bit_length_digits[i] |= carry; + data_bit_length_digits[i] &= 65535u; + carry = (before_val >> (16-3)) & 65535u; + } + + // write data_bit_length + for (int i = 3; i >= 0; --i) + { + (*begin++) = static_cast(data_bit_length_digits[i] >> 8); + (*begin++) = static_cast(data_bit_length_digits[i]); + } + } + std::vector buffer_; + uint32_t data_length_digits_[4]; //as 64bit integer (16bit x 4 integer) + uint32_t h_[8]; +}; + +template +void hash256(RaIter first, RaIter last, OutIter first2, OutIter last2) +{ + hash256_one_by_one hasher; + //hasher.init(); + hasher.process(first, last); + hasher.finish(); + hasher.get_hash_bytes(first2, last2); +} + +template +std::vector hash256(RaContainer const& _src) +{ + std::vector ret(32); + hash256(_src.begin(), _src.end(), ret.begin(), ret.end()); + return ret; +} + +}//namespace picosha2 diff --git a/libdevcore/vector_ref.h b/libdevcore/vector_ref.h index b4dcff651..a866f6628 100644 --- a/libdevcore/vector_ref.h +++ b/libdevcore/vector_ref.h @@ -25,6 +25,8 @@ class vector_ref using mutable_value_type = typename std::conditional::value, typename std::remove_const<_T>::type, _T>::type; using string_type = typename std::conditional::value, std::string const, std::string>::type; using vector_type = typename std::conditional::value, std::vector::type> const, std::vector<_T>>::type; + using iterator = _T*; + using const_iterator = _T const*; static_assert(std::is_pod::value, "vector_ref can only be used with PODs due to its low-level treatment of data."); diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e63194a0b..183cba930 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "Assembly.h" +#include #include #include @@ -35,6 +35,7 @@ using namespace std; using namespace dev; using namespace dev::eth; +using namespace langutil; void Assembly::append(Assembly const& _a) { @@ -82,7 +83,7 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i) { assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); m_deposit += _i.deposit(); - m_items.push_back(_i); + m_items.emplace_back(_i); if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) m_items.back().setLocation(m_currentSourceLocation); return back(); @@ -113,10 +114,10 @@ namespace string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) { - if (_location.isEmpty() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0) + if (_location.isEmpty() || !_location.source.get() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0) return ""; - auto it = _sourceCodes.find(*_location.sourceName); + auto it = _sourceCodes.find(_location.source->name()); if (it == _sourceCodes.end()) return ""; @@ -185,11 +186,11 @@ class Functionalizer void printLocation() { - if (!m_location.sourceName && m_location.isEmpty()) + if (!m_location.source && m_location.isEmpty()) return; m_out << m_prefix << " /*"; - if (m_location.sourceName) - m_out << " \"" + *m_location.sourceName + "\""; + if (m_location.source) + m_out << " \"" + m_location.source->name() + "\""; if (!m_location.isEmpty()) m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); m_out << " " << locationFromSources(m_sourceCodes, m_location); @@ -352,14 +353,14 @@ AssemblyItem Assembly::namedTag(string const& _name) assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); if (!m_namedTags.count(_name)) m_namedTags[_name] = size_t(newTag().data()); - return AssemblyItem(Tag, m_namedTags.at(_name)); + return AssemblyItem{Tag, m_namedTags.at(_name)}; } AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier) { h256 h(dev::keccak256(_identifier)); m_libraries[h] = _identifier; - return AssemblyItem(PushLibraryAddress, h); + return AssemblyItem{PushLibraryAddress, h}; } Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs) @@ -389,7 +390,7 @@ Assembly& Assembly::optimise(OptimiserSettings const& _settings) map Assembly::optimiseInternal( OptimiserSettings const& _settings, - std::set const& _tagsReferencedFromOutside + std::set _tagsReferencedFromOutside ) { // Run optimisation for sub-assemblies. @@ -414,14 +415,14 @@ map Assembly::optimiseInternal( if (_settings.runJumpdestRemover) { - JumpdestRemover jumpdestOpt(m_items); + JumpdestRemover jumpdestOpt{m_items}; if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) count++; } if (_settings.runPeephole) { - PeepholeOptimiser peepOpt(m_items); + PeepholeOptimiser peepOpt{m_items}; while (peepOpt.optimise()) { count++; @@ -432,10 +433,25 @@ map Assembly::optimiseInternal( // This only modifies PushTags, we have to run again to actually remove code. if (_settings.runDeduplicate) { - BlockDeduplicator dedup(m_items); + BlockDeduplicator dedup{m_items}; if (dedup.deduplicate()) { - tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); + for (auto const& replacement: dedup.replacedTags()) + { + assertThrow( + replacement.first <= size_t(-1) && replacement.second <= size_t(-1), + OptimizerException, + "Invalid tag replacement." + ); + assertThrow( + !tagReplacements.count(replacement.first), + OptimizerException, + "Replacement already known." + ); + tagReplacements[replacement.first] = replacement.second; + if (_tagsReferencedFromOutside.erase(size_t(replacement.first))) + _tagsReferencedFromOutside.insert(size_t(replacement.second)); + } count++; } } @@ -447,13 +463,13 @@ map Assembly::optimiseInternal( // function types that can be stored in storage. AssemblyItems optimisedItems; - bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem(Instruction::MSIZE)) != m_items.end()); + bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem{Instruction::MSIZE}) != m_items.end()); auto iter = m_items.begin(); while (iter != m_items.end()) { KnownState emptyState; - CommonSubexpressionEliminator eliminator(emptyState); + CommonSubexpressionEliminator eliminator{emptyState}; auto orig = iter; iter = eliminator.feedItems(iter, m_items.end(), usesMSize); bool shouldReplace = false; @@ -495,8 +511,7 @@ map Assembly::optimiseInternal( _settings.isCreation, _settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment, _settings.evmVersion, - *this, - m_items + *this ); return tagReplacements; @@ -581,12 +596,14 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); break; case PushSub: + assertThrow(i.data() <= size_t(-1), AssemblyException, ""); ret.bytecode.push_back(dataRefPush); subRef.insert(make_pair(size_t(i.data()), ret.bytecode.size())); ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); break; case PushSubSize: { + assertThrow(i.data() <= size_t(-1), AssemblyException, ""); auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); uint8_t b = max(1, dev::bytesRequired(s)); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 8ef369232..ebf032ac1 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -18,12 +18,12 @@ #pragma once #include -#include +#include #include #include #include -#include +#include #include #include @@ -45,8 +45,6 @@ using AssemblyPointer = std::shared_ptr; class Assembly { public: - Assembly() {} - AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } /// Returns a tag identified by the given name. Creates it if it does not yet exist. @@ -70,10 +68,10 @@ class Assembly void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } - AssemblyItem appendJump() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMP); return ret; } - AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(solidity::Instruction::JUMPI); return ret; } - AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMP); return ret; } - AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(solidity::Instruction::JUMPI); return ret; } + AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } + AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } + AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } + AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. @@ -88,12 +86,15 @@ class Assembly /// Returns the assembly items. AssemblyItems const& items() const { return m_items; } + /// Returns the mutable assembly items. Use with care! + AssemblyItems& items() { return m_items; } + int deposit() const { return m_deposit; } void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } /// Changes the source location used for each appended item. - void setSourceLocation(SourceLocation const& _location) { m_currentSourceLocation = _location; } + void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; } /// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached. LinkerObject const& assemble() const; @@ -106,13 +107,14 @@ class Assembly bool runDeduplicate = false; bool runCSE = false; bool runConstantOptimiser = false; - solidity::EVMVersion evmVersion; + langutil::EVMVersion evmVersion; /// This specifies an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. size_t expectedExecutionsPerDeployment = 200; }; - /// Execute optimisation passes as defined by @a _settings and return the optimised assembly. + /// Modify and return the current assembly such that creation and execution gas usage + /// is optimised according to the settings in @a _settings. Assembly& optimise(OptimiserSettings const& _settings); /// Modify (if @a _enable is set) and return the current assembly such that creation and @@ -120,7 +122,7 @@ class Assembly /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. /// If @a _enable is not set, will perform some simple peephole optimizations. - Assembly& optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation = true, size_t _runs = 200); + Assembly& optimise(bool _enable, langutil::EVMVersion _evmVersion, bool _isCreation, size_t _runs); /// Create a text representation of the assembly. std::string assemblyString( @@ -153,7 +155,7 @@ class Assembly /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. Also takes an argument containing the tags of this assembly /// that are referenced in a super-assembly. - std::map optimiseInternal(OptimiserSettings const& _settings, std::set const& _tagsReferencedFromOutside); + std::map optimiseInternal(OptimiserSettings const& _settings, std::set _tagsReferencedFromOutside); unsigned bytesRequired(unsigned subTagSize) const; @@ -178,7 +180,7 @@ class Assembly int m_deposit = 0; - SourceLocation m_currentSourceLocation; + langutil::SourceLocation m_currentSourceLocation; }; inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index a3a4d6b63..61355530e 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -110,13 +110,16 @@ int AssemblyItem::returnValues() const return 1; case Tag: return 0; - default:; + default: + break; } return 0; } bool AssemblyItem::canBeFunctional() const { + if (m_jumpType != JumpType::Ordinary) + return false; switch (m_type) { case Operation: @@ -133,9 +136,10 @@ bool AssemblyItem::canBeFunctional() const return true; case Tag: return false; - default:; + default: + break; } - return 0; + return false; } string AssemblyItem::getJumpTypeAsString() const @@ -166,7 +170,7 @@ string AssemblyItem::toAssemblyText() const break; } case Push: - text = toHex(toCompactBigEndian(data(), 1), 1, HexPrefix::Add); + text = toHex(toCompactBigEndian(data(), 1), HexPrefix::Add); break; case PushString: text = string("data_") + toHex(data()); @@ -227,7 +231,7 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) { case Operation: _out << " " << instructionInfo(_item.instruction()).name; - if (_item.instruction() == solidity::Instruction::JUMP || _item.instruction() == solidity::Instruction::JUMPI) + if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) _out << "\t" << _item.getJumpTypeAsString(); break; case Push: diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 6187e18f4..95b09d47f 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -21,14 +21,13 @@ #pragma once -#include -#include +#include +#include +#include #include #include -#include -#include -#include "Exceptions.h" -using namespace dev::solidity; +#include +#include namespace dev { @@ -57,22 +56,26 @@ class AssemblyItem public: enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; - AssemblyItem(u256 _push, SourceLocation const& _location = SourceLocation()): - AssemblyItem(Push, _push, _location) { } - AssemblyItem(solidity::Instruction _i, SourceLocation const& _location = SourceLocation()): + AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()): + AssemblyItem(Push, std::move(_push), std::move(_location)) { } + AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()): m_type(Operation), m_instruction(_i), - m_location(_location) + m_location(std::move(_location)) {} - AssemblyItem(AssemblyItemType _type, u256 _data = 0, SourceLocation const& _location = SourceLocation()): + AssemblyItem(AssemblyItemType _type, u256 _data = 0, langutil::SourceLocation _location = langutil::SourceLocation()): m_type(_type), - m_location(_location) + m_location(std::move(_location)) { if (m_type == Operation) m_instruction = Instruction(uint8_t(_data)); else - m_data = std::make_shared(_data); + m_data = std::make_shared(std::move(_data)); } + AssemblyItem(AssemblyItem const&) = default; + AssemblyItem(AssemblyItem&&) = default; + AssemblyItem& operator=(AssemblyItem const&) = default; + AssemblyItem& operator=(AssemblyItem&&) = default; AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, data()); } AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, data()); } @@ -114,6 +117,13 @@ class AssemblyItem return data() < _other.data(); } + /// Shortcut that avoids constructing an AssemblyItem just to perform the comparison. + bool operator==(Instruction _instr) const + { + return type() == Operation && instruction() == _instr; + } + bool operator!=(Instruction _instr) const { return !operator==(_instr); } + /// @returns an upper bound for the number of bytes required by this item, assuming that /// the value of a jump tag takes @a _addressLength bytes. unsigned bytesRequired(unsigned _addressLength) const; @@ -124,8 +134,8 @@ class AssemblyItem /// @returns true if the assembly item can be used in a functional context. bool canBeFunctional() const; - void setLocation(SourceLocation const& _location) { m_location = _location; } - SourceLocation const& location() const { return m_location; } + void setLocation(langutil::SourceLocation const& _location) { m_location = _location; } + langutil::SourceLocation const& location() const { return m_location; } void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } JumpType getJumpType() const { return m_jumpType; } @@ -140,7 +150,7 @@ class AssemblyItem AssemblyItemType m_type; Instruction m_instruction; ///< Only valid if m_type == Operation std::shared_ptr m_data; ///< Only valid if m_type != Operation - SourceLocation m_location; + langutil::SourceLocation m_location; JumpType m_jumpType = JumpType::Ordinary; /// Pushed value for operations with data to be determined during assembly stage, /// e.g. PushSubSize, PushTag, PushSub, etc. diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index b7c695311..ca439925c 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -41,7 +41,7 @@ bool BlockDeduplicator::deduplicate() // Virtual tag that signifies "the current block" and which is used to optimise loops. // We abort if this virtual tag actually exists. - AssemblyItem pushSelf(PushTag, u256(-4)); + AssemblyItem pushSelf{PushTag, u256(-4)}; if ( std::count(m_items.cbegin(), m_items.cend(), pushSelf.tag()) || std::count(m_items.cbegin(), m_items.cend(), pushSelf.pushTag()) @@ -55,17 +55,17 @@ bool BlockDeduplicator::deduplicate() // To compare recursive loops, we have to already unify PushTag opcodes of the // block's own tag. - AssemblyItem pushFirstTag(pushSelf); - AssemblyItem pushSecondTag(pushSelf); + AssemblyItem pushFirstTag{pushSelf}; + AssemblyItem pushSecondTag{pushSelf}; if (_i < m_items.size() && m_items.at(_i).type() == Tag) pushFirstTag = m_items.at(_i).pushTag(); if (_j < m_items.size() && m_items.at(_j).type() == Tag) pushSecondTag = m_items.at(_j).pushTag(); - BlockIterator first(m_items.begin() + _i, m_items.end(), &pushFirstTag, &pushSelf); - BlockIterator second(m_items.begin() + _j, m_items.end(), &pushSecondTag, &pushSelf); - BlockIterator end(m_items.end(), m_items.end()); + BlockIterator first{m_items.begin() + _i, m_items.end(), &pushFirstTag, &pushSelf}; + BlockIterator second{m_items.begin() + _j, m_items.end(), &pushSecondTag, &pushSelf}; + BlockIterator end{m_items.end(), m_items.end()}; if (first != end && (*first).type() == Tag) ++first; @@ -126,7 +126,7 @@ BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() { if (it == end) return *this; - if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem(Instruction::JUMPI)) + if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI}) it = end; else { diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index 86192c1ba..426799385 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -1,5 +1,39 @@ -file(GLOB sources "*.cpp") -file(GLOB headers "*.h") +set(sources + Assembly.cpp + Assembly.h + AssemblyItem.cpp + AssemblyItem.h + BlockDeduplicator.cpp + BlockDeduplicator.h + CommonSubexpressionEliminator.cpp + CommonSubexpressionEliminator.h + ConstantOptimiser.cpp + ConstantOptimiser.h + ControlFlowGraph.cpp + ControlFlowGraph.h + Exceptions.h + ExpressionClasses.cpp + ExpressionClasses.h + GasMeter.cpp + GasMeter.h + Instruction.cpp + Instruction.h + JumpdestRemover.cpp + JumpdestRemover.h + KnownState.cpp + KnownState.h + LinkerObject.cpp + LinkerObject.h + PathGasMeter.cpp + PathGasMeter.h + PeepholeOptimiser.cpp + PeepholeOptimiser.h + SemanticInformation.cpp + SemanticInformation.h + SimplificationRule.h + SimplificationRules.cpp + SimplificationRules.h +) -add_library(evmasm ${sources} ${headers}) +add_library(evmasm ${sources}) target_link_libraries(evmasm PUBLIC devcore) diff --git a/libevmasm/CommonSubexpressionEliminator.cpp b/libevmasm/CommonSubexpressionEliminator.cpp index 04926986d..949d2c75a 100644 --- a/libevmasm/CommonSubexpressionEliminator.cpp +++ b/libevmasm/CommonSubexpressionEliminator.cpp @@ -30,6 +30,7 @@ using namespace std; using namespace dev; using namespace dev::eth; +using namespace langutil; vector CommonSubexpressionEliminator::getOptimizedItems() { diff --git a/libevmasm/CommonSubexpressionEliminator.h b/libevmasm/CommonSubexpressionEliminator.h index b20de2469..cddc928aa 100644 --- a/libevmasm/CommonSubexpressionEliminator.h +++ b/libevmasm/CommonSubexpressionEliminator.h @@ -34,6 +34,11 @@ #include #include +namespace langutil +{ +struct SourceLocation; +} + namespace dev { namespace eth @@ -137,14 +142,14 @@ class CSECodeGenerator bool removeStackTopIfPossible(); /// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position. - void appendDup(int _fromPosition, SourceLocation const& _location); + void appendDup(int _fromPosition, langutil::SourceLocation const& _location); /// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position. /// @note this might also remove the last item if it exactly the same swap instruction. - void appendOrRemoveSwap(int _fromPosition, SourceLocation const& _location); + void appendOrRemoveSwap(int _fromPosition, langutil::SourceLocation const& _location); /// Appends the given assembly item. void appendItem(AssemblyItem const& _item); - static const int c_invalidPosition = -0x7fffffff; + static int const c_invalidPosition = -0x7fffffff; AssemblyItems m_generatedItems; /// Current height of the stack relative to the start. @@ -156,7 +161,7 @@ class CSECodeGenerator /// Current positions of equivalence classes, equal to the empty set if already deleted. std::map> m_classPositions; - /// The actual eqivalence class items and how to compute them. + /// The actual equivalence class items and how to compute them. ExpressionClasses& m_expressionClasses; /// Keeps information about which storage or memory slots were written to by which operations. /// The operations are sorted ascendingly by sequence number. diff --git a/libevmasm/ConstantOptimiser.cpp b/libevmasm/ConstantOptimiser.cpp index 9844ba3a3..4825bc5cf 100644 --- a/libevmasm/ConstantOptimiser.cpp +++ b/libevmasm/ConstantOptimiser.cpp @@ -29,11 +29,13 @@ using namespace dev::eth; unsigned ConstantOptimisationMethod::optimiseConstants( bool _isCreation, size_t _runs, - solidity::EVMVersion _evmVersion, - Assembly& _assembly, - AssemblyItems& _items + langutil::EVMVersion _evmVersion, + Assembly& _assembly ) { + // TODO: design the optimiser in a way this is not needed + AssemblyItems& _items = _assembly.items(); + unsigned optimisations = 0; map pushes; for (AssemblyItem const& item: _items) @@ -129,16 +131,11 @@ bigint LiteralMethod::gasNeeded() const return combineGas( simpleRunGas({Instruction::PUSH1}), // PUSHX plus data - (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas) + dataGas(), + (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)), 0 ); } -CodeCopyMethod::CodeCopyMethod(Params const& _params, u256 const& _value): - ConstantOptimisationMethod(_params, _value) -{ -} - bigint CodeCopyMethod::gasNeeded() const { return combineGas( @@ -154,6 +151,7 @@ bigint CodeCopyMethod::gasNeeded() const AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const { bytes data = toBigEndian(m_value); + assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding."); AssemblyItems actualCopyRoutine = copyRoutine(); actualCopyRoutine[4] = _assembly.newData(data); return actualCopyRoutine; @@ -162,15 +160,25 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const AssemblyItems const& CodeCopyMethod::copyRoutine() { AssemblyItems static copyRoutine{ + // constant to be reused 3+ times u256(0), + + // back up memory + // mload(0) Instruction::DUP1, - Instruction::MLOAD, // back up memory + Instruction::MLOAD, + + // codecopy(0, , 32) u256(32), - AssemblyItem(PushData, u256(1) << 16), // has to be replaced + AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4] Instruction::DUP4, Instruction::CODECOPY, + + // mload(0) Instruction::DUP2, Instruction::MLOAD, + + // restore original memory Instruction::SWAP2, Instruction::MSTORE }; @@ -193,7 +201,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) bigint bestGas = gasNeeded(routine); for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits) { - unsigned gapDetector = unsigned(_value >> (bits - 8)) & 0x1ff; + unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff); if (gapDetector != 0xff && gapDetector != 0x100) continue; @@ -213,9 +221,17 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) AssemblyItems newRoutine; if (lowerPart != 0) newRoutine += findRepresentation(u256(abs(lowerPart))); - newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; - if (upperPart != 1) - newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; + if (m_params.evmVersion.hasBitwiseShifting()) + { + newRoutine += findRepresentation(upperPart); + newRoutine += AssemblyItems{u256(bits), Instruction::SHL}; + } + else + { + newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; + if (upperPart != 1) + newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; + } if (lowerPart > 0) newRoutine += AssemblyItems{Instruction::ADD}; else if (lowerPart < 0) @@ -234,7 +250,7 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) } } -bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) +bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const { // This is a tiny EVM that can only evaluate some instructions. vector stack; @@ -266,6 +282,24 @@ bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& case Instruction::NOT: sp[0] = ~sp[0]; break; + case Instruction::SHL: + assertThrow( + m_params.evmVersion.hasBitwiseShifting(), + OptimizerException, + "Shift generated for invalid EVM version." + ); + assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated."); + sp[-1] = u256(bigint(sp[-1]) << unsigned(sp[0])); + break; + case Instruction::SHR: + assertThrow( + m_params.evmVersion.hasBitwiseShifting(), + OptimizerException, + "Shift generated for invalid EVM version." + ); + assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated."); + sp[-1] = sp[-1] >> unsigned(sp[0]); + break; default: return false; } diff --git a/libevmasm/ConstantOptimiser.h b/libevmasm/ConstantOptimiser.h index 2c753fa8c..be1628c71 100644 --- a/libevmasm/ConstantOptimiser.h +++ b/libevmasm/ConstantOptimiser.h @@ -23,7 +23,7 @@ #include -#include +#include #include #include @@ -47,22 +47,24 @@ class ConstantOptimisationMethod { public: /// Tries to optimised how constants are represented in the source code and modifies - /// @a _assembly and its @a _items. + /// @a _assembly. /// @returns zero if no optimisations could be performed. static unsigned optimiseConstants( bool _isCreation, size_t _runs, - solidity::EVMVersion _evmVersion, - Assembly& _assembly, - AssemblyItems& _items + langutil::EVMVersion _evmVersion, + Assembly& _assembly ); +protected: + /// This is the public API for the optimiser methods, but it doesn't need to be exposed to the caller. + struct Params { bool isCreation; ///< Whether this is called during contract creation or runtime. size_t runs; ///< Estimated number of calls per opcode oven the lifetime of the contract. size_t multiplicity; ///< Number of times the constant appears in the code. - solidity::EVMVersion evmVersion; ///< Version of the EVM + langutil::EVMVersion evmVersion; ///< Version of the EVM }; explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): @@ -79,8 +81,6 @@ class ConstantOptimisationMethod static bigint simpleRunGas(AssemblyItems const& _items); /// @returns the gas needed to store the given data literally bigint dataGas(bytes const& _data) const; - /// @returns the gas needed to store the value literally - bigint dataGas() const { return dataGas(toCompactBigEndian(m_value, 1)); } static size_t bytesRequired(AssemblyItems const& _items); /// @returns the combined estimated gas usage taking @a m_params into account. bigint combineGas( @@ -109,8 +109,8 @@ class LiteralMethod: public ConstantOptimisationMethod public: explicit LiteralMethod(Params const& _params, u256 const& _value): ConstantOptimisationMethod(_params, _value) {} - virtual bigint gasNeeded() const override; - virtual AssemblyItems execute(Assembly&) const override { return AssemblyItems{}; } + bigint gasNeeded() const override; + AssemblyItems execute(Assembly&) const override { return AssemblyItems{}; } }; /** @@ -119,9 +119,10 @@ class LiteralMethod: public ConstantOptimisationMethod class CodeCopyMethod: public ConstantOptimisationMethod { public: - explicit CodeCopyMethod(Params const& _params, u256 const& _value); - virtual bigint gasNeeded() const override; - virtual AssemblyItems execute(Assembly& _assembly) const override; + explicit CodeCopyMethod(Params const& _params, u256 const& _value): + ConstantOptimisationMethod(_params, _value) {} + bigint gasNeeded() const override; + AssemblyItems execute(Assembly& _assembly) const override; protected: static AssemblyItems const& copyRoutine(); @@ -144,8 +145,8 @@ class ComputeMethod: public ConstantOptimisationMethod ); } - virtual bigint gasNeeded() const override { return gasNeeded(m_routine); } - virtual AssemblyItems execute(Assembly&) const override + bigint gasNeeded() const override { return gasNeeded(m_routine); } + AssemblyItems execute(Assembly&) const override { return m_routine; } @@ -154,7 +155,7 @@ class ComputeMethod: public ConstantOptimisationMethod /// Tries to recursively find a way to compute @a _value. AssemblyItems findRepresentation(u256 const& _value); /// Recomputes the value from the calculated representation and checks for correctness. - static bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine); + bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const; bigint gasNeeded(AssemblyItems const& _routine) const; /// Counter for the complexity of optimization, will stop when it reaches zero. diff --git a/libevmasm/ControlFlowGraph.cpp b/libevmasm/ControlFlowGraph.cpp index 86f16d481..e82c2903f 100644 --- a/libevmasm/ControlFlowGraph.cpp +++ b/libevmasm/ControlFlowGraph.cpp @@ -87,7 +87,7 @@ void ControlFlowGraph::splitBlocks() m_blocks[id].begin = index; } if (item.type() == PushTag) - m_blocks[id].pushedTags.push_back(BlockId(item.data())); + m_blocks[id].pushedTags.emplace_back(item.data()); if (SemanticInformation::altersControlFlow(item)) { m_blocks[id].end = index + 1; @@ -275,7 +275,7 @@ void ControlFlowGraph::gatherKnowledge() //@todo in the case of JUMPI, add knowledge about the condition to the state // (for both values of the condition) set tags = state->tagsInExpression( - state->stackElement(state->stackHeight(), SourceLocation()) + state->stackElement(state->stackHeight(), langutil::SourceLocation{}) ); state->feedItem(m_items.at(pc++)); diff --git a/libevmasm/ExpressionClasses.cpp b/libevmasm/ExpressionClasses.cpp index 9dacbae93..41cf8990e 100644 --- a/libevmasm/ExpressionClasses.cpp +++ b/libevmasm/ExpressionClasses.cpp @@ -34,7 +34,7 @@ using namespace std; using namespace dev; using namespace dev::eth; - +using namespace langutil; bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression const& _other) const { @@ -196,13 +196,16 @@ ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr) if (auto match = rules.findFirstMatch(_expr, *this)) { // Debug info - //cout << "Simplifying " << *_expr.item << "("; - //for (Id arg: _expr.arguments) - // cout << fullDAGToString(arg) << ", "; - //cout << ")" << endl; - //cout << "with rule " << match->pattern.toString() << endl; - //ExpressionTemplate t(match->action(), _expr.item->location()); - //cout << "to " << t.toString() << endl; + if (false) + { + cout << "Simplifying " << *_expr.item << "("; + for (Id arg: _expr.arguments) + cout << fullDAGToString(arg) << ", "; + cout << ")" << endl; + cout << "with rule " << match->pattern.toString() << endl; + cout << "to " << match->action().toString() << endl; + } + return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location())); } diff --git a/libevmasm/ExpressionClasses.h b/libevmasm/ExpressionClasses.h index df8082f93..a34844c54 100644 --- a/libevmasm/ExpressionClasses.h +++ b/libevmasm/ExpressionClasses.h @@ -31,6 +31,11 @@ #include #include +namespace langutil +{ +struct SourceLocation; +} + namespace dev { namespace eth @@ -82,7 +87,7 @@ class ExpressionClasses void forceEqual(Id _id, AssemblyItem const& _item, Ids const& _arguments, bool _copyItem = true); /// @returns the id of a new class which is different to all other classes. - Id newClass(SourceLocation const& _location); + Id newClass(langutil::SourceLocation const& _location); /// @returns true if the values of the given classes are known to be different (on every input). /// @note that this function might still return false for some different inputs. diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index d20dcb251..a8472db18 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -197,6 +197,12 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::BALANCE: gas = GasCosts::balanceGas(m_evmVersion); break; + case Instruction::CHAINID: + gas = runGas(Instruction::CHAINID); + break; + case Instruction::SELFBALANCE: + gas = runGas(Instruction::SELFBALANCE); + break; default: gas = runGas(_item.instruction()); break; diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 6af85ba3a..e3a19592f 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include @@ -47,24 +47,24 @@ namespace GasCosts static unsigned const tier5Gas = 10; static unsigned const tier6Gas = 20; static unsigned const tier7Gas = 0; - inline unsigned extCodeGas(EVMVersion _evmVersion) + inline unsigned extCodeGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::tangerineWhistle() ? 700 : 20; + return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 700 : 20; } - inline unsigned balanceGas(EVMVersion _evmVersion) + inline unsigned balanceGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::tangerineWhistle() ? 400 : 20; + return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 400 : 20; } static unsigned const expGas = 10; - inline unsigned expByteGas(EVMVersion _evmVersion) + inline unsigned expByteGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::spuriousDragon() ? 50 : 10; + return _evmVersion >= langutil::EVMVersion::spuriousDragon() ? 50 : 10; } static unsigned const keccak256Gas = 30; static unsigned const keccak256WordGas = 6; - inline unsigned sloadGas(EVMVersion _evmVersion) + inline unsigned sloadGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::tangerineWhistle() ? 200 : 50; + return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 200 : 50; } static unsigned const sstoreSetGas = 20000; static unsigned const sstoreResetGas = 5000; @@ -74,16 +74,16 @@ namespace GasCosts static unsigned const logDataGas = 8; static unsigned const logTopicGas = 375; static unsigned const createGas = 32000; - inline unsigned callGas(EVMVersion _evmVersion) + inline unsigned callGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::tangerineWhistle() ? 700 : 40; + return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 700 : 40; } static unsigned const callStipend = 2300; static unsigned const callValueTransferGas = 9000; static unsigned const callNewAccountGas = 25000; - inline unsigned selfdestructGas(EVMVersion _evmVersion) + inline unsigned selfdestructGas(langutil::EVMVersion _evmVersion) { - return _evmVersion >= EVMVersion::tangerineWhistle() ? 5000 : 0; + return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 5000 : 0; } static unsigned const selfdestructRefundGas = 24000; static unsigned const memoryGas = 3; @@ -124,7 +124,7 @@ class GasMeter }; /// Constructs a new gas meter given the current state. - GasMeter(std::shared_ptr const& _state, solidity::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0): + GasMeter(std::shared_ptr const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0): m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {} /// @returns an upper bound on the gas consumed by the given instruction and updates @@ -154,7 +154,7 @@ class GasMeter GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize); std::shared_ptr m_state; - EVMVersion m_evmVersion; + langutil::EVMVersion m_evmVersion; /// Largest point where memory was accessed since the creation of this object. u256 m_largestMemoryAccess; }; diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 6f4b7210d..d525ace3b 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -19,17 +19,18 @@ * @date 2014 */ -#include "./Instruction.h" +#include -#include -#include #include #include +#include +#include + using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace dev::eth; -const std::map dev::solidity::c_instructions = +std::map const dev::eth::c_instructions = { { "STOP", Instruction::STOP }, { "ADD", Instruction::ADD }, @@ -88,6 +89,8 @@ const std::map dev::solidity::c_instructions = { "NUMBER", Instruction::NUMBER }, { "DIFFICULTY", Instruction::DIFFICULTY }, { "GASLIMIT", Instruction::GASLIMIT }, + { "CHAINID", Instruction::CHAINID }, + { "SELFBALANCE", Instruction::SELFBALANCE }, { "POP", Instruction::POP }, { "MLOAD", Instruction::MLOAD }, { "MSTORE", Instruction::MSTORE }, @@ -181,7 +184,7 @@ const std::map dev::solidity::c_instructions = { "SELFDESTRUCT", Instruction::SELFDESTRUCT } }; -static const std::map c_instructionInfo = +static std::map const c_instructionInfo = { // Add, Args, Ret, SideEffects, GasPriceTier { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } }, { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } }, @@ -240,6 +243,8 @@ static const std::map c_instructionInfo = { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } }, { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } }, { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } }, + { Instruction::CHAINID, { "CHAINID", 0, 0, 1, false, Tier::Base } }, + { Instruction::SELFBALANCE, { "SELFBALANCE", 0, 0, 1, false, Tier::Low } }, { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } }, { Instruction::MLOAD, { "MLOAD", 0, 1, 1, true, Tier::VeryLow } }, { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } }, @@ -333,7 +338,7 @@ static const std::map c_instructionInfo = { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } } }; -void dev::solidity::eachInstruction( +void dev::eth::eachInstruction( bytes const& _mem, function const& _onInstruction ) @@ -362,7 +367,7 @@ void dev::solidity::eachInstruction( } } -string dev::solidity::disassemble(bytes const& _mem) +string dev::eth::disassemble(bytes const& _mem) { stringstream ret; eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) { @@ -379,7 +384,7 @@ string dev::solidity::disassemble(bytes const& _mem) return ret.str(); } -InstructionInfo dev::solidity::instructionInfo(Instruction _inst) +InstructionInfo dev::eth::instructionInfo(Instruction _inst) { try { @@ -391,7 +396,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst) } } -bool dev::solidity::isValidInstruction(Instruction _inst) +bool dev::eth::isValidInstruction(Instruction _inst) { return !!c_instructionInfo.count(_inst); } diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index a821b45bd..bab9f6af4 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -21,14 +21,14 @@ #pragma once -#include +#include #include #include -#include "Exceptions.h" +#include namespace dev { -namespace solidity +namespace eth { DEV_SIMPLE_EXCEPTION(InvalidDeposit); @@ -94,6 +94,8 @@ enum class Instruction: uint8_t NUMBER, ///< get the block's number DIFFICULTY, ///< get the block's difficulty GASLIMIT, ///< get the block's gas limit + CHAINID, ///< get the config's chainid param + SELFBALANCE, ///< get balance of the current account POP = 0x50, ///< remove item from stack MLOAD, ///< load word from memory diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp index 60493a99e..457853c53 100644 --- a/libevmasm/JumpdestRemover.cpp +++ b/libevmasm/JumpdestRemover.cpp @@ -19,7 +19,7 @@ * Removes unused JUMPDESTs. */ -#include "JumpdestRemover.h" +#include #include diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index b6c1bcc9b..73db60560 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -21,14 +21,16 @@ * Contains knowledge about the state of the virtual machine at a specific instruction. */ -#include "KnownState.h" -#include -#include +#include #include +#include + +#include using namespace std; using namespace dev; using namespace dev::eth; +using namespace langutil; ostream& KnownState::stream(ostream& _out) const { @@ -303,7 +305,7 @@ KnownState::StoreOperation KnownState::storeInStorage( AssemblyItem item(Instruction::SSTORE, _location); Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); - StoreOperation operation(StoreOperation::Storage, _slot, m_sequenceNumber, id); + StoreOperation operation{StoreOperation::Storage, _slot, m_sequenceNumber, id}; m_storageContent[_slot] = _value; // increment a second time so that we get unique sequence numbers for writes m_sequenceNumber++; @@ -335,7 +337,7 @@ KnownState::StoreOperation KnownState::storeInMemory(Id _slot, Id _value, Source AssemblyItem item(Instruction::MSTORE, _location); Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); - StoreOperation operation(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id)); + StoreOperation operation{StoreOperation::Memory, _slot, m_sequenceNumber, id}; m_memoryContent[_slot] = _value; // increment a second time so that we get unique sequence numbers for writes m_sequenceNumber++; diff --git a/libevmasm/KnownState.h b/libevmasm/KnownState.h index cd50550ed..3b5e9e7af 100644 --- a/libevmasm/KnownState.h +++ b/libevmasm/KnownState.h @@ -46,6 +46,11 @@ #include #include +namespace langutil +{ +struct SourceLocation; +} + namespace dev { namespace eth @@ -69,18 +74,13 @@ class KnownState struct StoreOperation { enum Target { Invalid, Memory, Storage }; - StoreOperation(): target(Invalid), sequenceNumber(-1) {} - StoreOperation( - Target _target, - Id _slot, - unsigned _sequenceNumber, - Id _expression - ): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} + bool isValid() const { return target != Invalid; } - Target target; - Id slot; - unsigned sequenceNumber; - Id expression; + + Target target{Invalid}; + Id slot{std::numeric_limits::max()}; + unsigned sequenceNumber{std::numeric_limits::max()}; + Id expression{std::numeric_limits::max()}; }; explicit KnownState( @@ -121,9 +121,9 @@ class KnownState /// Retrieves the current equivalence class fo the given stack element (or generates a new /// one if it does not exist yet). - Id stackElement(int _stackHeight, SourceLocation const& _location); + Id stackElement(int _stackHeight, langutil::SourceLocation const& _location); /// @returns the stackElement relative to the current stack height. - Id relativeStackElement(int _stackOffset, SourceLocation const& _location = SourceLocation()); + Id relativeStackElement(int _stackOffset, langutil::SourceLocation const& _location = {}); /// @returns its set of tags if the given expression class is a known tag union; returns a set /// containing the tag if it is a PushTag expression and the empty set otherwise. @@ -142,22 +142,22 @@ class KnownState /// Assigns a new equivalence class to the next sequence number of the given stack element. void setStackElement(int _stackHeight, Id _class); /// Swaps the given stack elements in their next sequence number. - void swapStackElements(int _stackHeightA, int _stackHeightB, SourceLocation const& _location); + void swapStackElements(int _stackHeightA, int _stackHeightB, langutil::SourceLocation const& _location); /// Increments the sequence number, deletes all storage information that might be overwritten /// and stores the new value at the given slot. /// @returns the store operation, which might be invalid if storage was not modified - StoreOperation storeInStorage(Id _slot, Id _value, SourceLocation const& _location); + StoreOperation storeInStorage(Id _slot, Id _value, langutil::SourceLocation const& _location); /// Retrieves the current value at the given slot in storage or creates a new special sload class. - Id loadFromStorage(Id _slot, SourceLocation const& _location); + Id loadFromStorage(Id _slot, langutil::SourceLocation const& _location); /// Increments the sequence number, deletes all memory information that might be overwritten /// and stores the new value at the given slot. /// @returns the store operation, which might be invalid if memory was not modified - StoreOperation storeInMemory(Id _slot, Id _value, SourceLocation const& _location); + StoreOperation storeInMemory(Id _slot, Id _value, langutil::SourceLocation const& _location); /// Retrieves the current value at the given slot in memory or creates a new special mload class. - Id loadFromMemory(Id _slot, SourceLocation const& _location); + Id loadFromMemory(Id _slot, langutil::SourceLocation const& _location); /// Finds or creates a new expression that applies the Keccak-256 hash function to the contents in memory. - Id applyKeccak256(Id _start, Id _length, SourceLocation const& _location); + Id applyKeccak256(Id _start, Id _length, langutil::SourceLocation const& _location); /// @returns a new or already used Id representing the given set of tags. Id tagUnion(std::set _tags); diff --git a/libevmasm/PathGasMeter.cpp b/libevmasm/PathGasMeter.cpp index cdadba768..761defdc5 100644 --- a/libevmasm/PathGasMeter.cpp +++ b/libevmasm/PathGasMeter.cpp @@ -19,7 +19,7 @@ * @date 2015 */ -#include "PathGasMeter.h" +#include #include #include @@ -27,7 +27,7 @@ using namespace std; using namespace dev; using namespace dev::eth; -PathGasMeter::PathGasMeter(AssemblyItems const& _items, solidity::EVMVersion _evmVersion): +PathGasMeter::PathGasMeter(AssemblyItems const& _items, langutil::EVMVersion _evmVersion): m_items(_items), m_evmVersion(_evmVersion) { for (size_t i = 0; i < m_items.size(); ++i) diff --git a/libevmasm/PathGasMeter.h b/libevmasm/PathGasMeter.h index fb821684d..67b607c42 100644 --- a/libevmasm/PathGasMeter.h +++ b/libevmasm/PathGasMeter.h @@ -23,7 +23,7 @@ #include -#include +#include #include #include @@ -53,13 +53,13 @@ struct GasPath class PathGasMeter { public: - explicit PathGasMeter(AssemblyItems const& _items, solidity::EVMVersion _evmVersion); + explicit PathGasMeter(AssemblyItems const& _items, langutil::EVMVersion _evmVersion); GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr const& _state); static GasMeter::GasConsumption estimateMax( AssemblyItems const& _items, - solidity::EVMVersion _evmVersion, + langutil::EVMVersion _evmVersion, size_t _startIndex, std::shared_ptr const& _state ) @@ -81,7 +81,7 @@ class PathGasMeter std::map m_highestGasUsagePerJumpdest; std::map m_tagPositions; AssemblyItems const& m_items; - solidity::EVMVersion m_evmVersion; + langutil::EVMVersion m_evmVersion; }; } diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 6d8e1df6d..3e2c45b6f 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -19,7 +19,7 @@ * Performs local optimising code changes to assembly. */ -#include "PeepholeOptimiser.h" +#include #include #include @@ -45,6 +45,14 @@ struct ApplyRule { }; template +struct ApplyRule +{ + static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out); + } +}; +template struct ApplyRule { static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) @@ -160,8 +168,7 @@ struct CommutativeSwap: SimplePeepholeOptimizerMethod { // Remove SWAP1 if following instruction is commutative if ( - _swap.type() == Operation && - _swap.instruction() == Instruction::SWAP1 && + _swap == Instruction::SWAP1 && SemanticInformation::isCommutativeOperation(_op) ) { @@ -177,7 +184,7 @@ struct SwapComparison: SimplePeepholeOptimizerMethod { static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator _out) { - map swappableOps{ + static map const swappableOps{ { Instruction::LT, Instruction::GT }, { Instruction::GT, Instruction::LT }, { Instruction::SLT, Instruction::SGT }, @@ -185,8 +192,7 @@ struct SwapComparison: SimplePeepholeOptimizerMethod }; if ( - _swap.type() == Operation && - _swap.instruction() == Instruction::SWAP1 && + _swap == Instruction::SWAP1 && _op.type() == Operation && swappableOps.count(_op.instruction()) ) @@ -199,6 +205,32 @@ struct SwapComparison: SimplePeepholeOptimizerMethod } }; +struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod +{ + static size_t applySimple( + AssemblyItem const& _iszero1, + AssemblyItem const& _iszero2, + AssemblyItem const& _pushTag, + AssemblyItem const& _jumpi, + std::back_insert_iterator _out + ) + { + if ( + _iszero1 == Instruction::ISZERO && + _iszero2 == Instruction::ISZERO && + _pushTag.type() == PushTag && + _jumpi == Instruction::JUMPI + ) + { + *_out = _pushTag; + *_out = _jumpi; + return true; + } + else + return false; + } +}; + struct JumpToNext: SimplePeepholeOptimizerMethod { static size_t applySimple( @@ -322,7 +354,12 @@ bool PeepholeOptimiser::optimise() { OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; while (state.i < m_items.size()) - applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), JumpToNext(), UnreachableCode(), TagConjunctions(), TruthyAnd(), Identity()); + applyMethods( + state, + PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), + IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(), + TagConjunctions(), TruthyAnd(), Identity() + ); if (m_optimisedItems.size() < m_items.size() || ( m_optimisedItems.size() == m_items.size() && ( eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) || diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 7a78dd3db..6e1b8de72 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -21,17 +21,20 @@ #pragma once -#include -#include #include #include #include +#include + +#include +#include + namespace dev { -namespace solidity +namespace eth { template S divWorkaround(S const& _a, S const& _b) @@ -44,7 +47,14 @@ template S modWorkaround(S const& _a, S const& _b) return (S)(bigint(_a) % bigint(_b)); } -// This part of simplificationRuleList below was split out to prevent +// This works around a bug fixed with Boost 1.64. +// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64 +inline u256 shlWorkaround(u256 const& _x, unsigned _amount) +{ + return u256((bigint(_x) << _amount) & u256(-1)); +} + +// simplificationRuleList below was split up into parts to prevent // stack overflows in the JavaScript optimizer for emscripten builds // that affected certain browser versions. template @@ -52,8 +62,8 @@ std::vector> simplificationRuleListPart1( Pattern A, Pattern B, Pattern C, - Pattern X, - Pattern Y + Pattern, + Pattern ) { return std::vector> { @@ -79,29 +89,41 @@ 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(); unsigned testBit = unsigned(A.d()) * 8 + 7; u256 mask = (u256(1) << testBit) - 1; - return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); + return boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask; }, false}, {{Instruction::SHL, {A, B}}, [=]{ if (A.d() > 255) return u256(0); - return u256(bigint(B.d()) << unsigned(A.d())); + return shlWorkaround(B.d(), unsigned(A.d())); }, false}, {{Instruction::SHR, {A, B}}, [=]{ if (A.d() > 255) return u256(0); return B.d() >> unsigned(A.d()); - }, false}, + }, false} + }; +} +template +std::vector> simplificationRuleListPart2( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + return std::vector> { // invariants involving known constants {{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}, @@ -128,7 +150,31 @@ std::vector> simplificationRuleListPart1( {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, {{Instruction::EQ, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, + {{Instruction::SHL, {0, X}}, [=]{ return X; }, false}, + {{Instruction::SHR, {0, X}}, [=]{ return X; }, false}, + {{Instruction::SHL, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::SHR, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::GT, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false}, + {{Instruction::LT, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false}, + {{Instruction::GT, {X, ~u256(0)}}, [=]{ return u256(0); }, true}, + {{Instruction::LT, {~u256(0), X}}, [=]{ return u256(0); }, true}, + {{Instruction::GT, {0, X}}, [=]{ return u256(0); }, true}, + {{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true}, + {{Instruction::AND, {{Instruction::BYTE, {X, Y}}, {u256(0xff)}}}, [=]() -> Pattern { return {Instruction::BYTE, {X, Y}}; }, false}, + {{Instruction::BYTE, {31, X}}, [=]() -> Pattern { return {Instruction::AND, {X, u256(0xff)}}; }, false} + }; +} +template +std::vector> simplificationRuleListPart3( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern +) +{ + return std::vector> { // operations involving an expression and itself {{Instruction::AND, {X, X}}, [=]{ return X; }, true}, {{Instruction::OR, {X, X}}, [=]{ return X; }, true}, @@ -139,8 +185,20 @@ std::vector> simplificationRuleListPart1( {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }, true}, {{Instruction::GT, {X, X}}, [=]{ return u256(0); }, true}, {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }, true}, - {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }, true} + }; +} +template +std::vector> simplificationRuleListPart4( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + return std::vector> { // logical instruction combinations {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }, false}, {{Instruction::XOR, {X, {Instruction::XOR, {X, Y}}}}, [=]{ return Y; }, true}, @@ -163,16 +221,13 @@ std::vector> simplificationRuleListPart1( } -// This part of simplificationRuleList below was split out to prevent -// stack overflows in the JavaScript optimizer for emscripten builds -// that affected certain browser versions. template -std::vector> simplificationRuleListPart2( +std::vector> simplificationRuleListPart5( Pattern A, - Pattern B, + Pattern, Pattern, Pattern X, - Pattern Y + Pattern ) { std::vector> rules; @@ -188,6 +243,30 @@ std::vector> simplificationRuleListPart2( }); } + // Replace SHL >=256, X with 0 + rules.push_back({ + {Instruction::SHL, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 256; } + }); + + // Replace SHR >=256, X with 0 + rules.push_back({ + {Instruction::SHR, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 256; } + }); + + // Replace BYTE(A, X), A >= 32 with 0 + rules.push_back({ + {Instruction::BYTE, {A, X}}, + [=]() -> Pattern { return u256(0); }, + true, + [=]() { return A.d() >= 32; } + }); + for (auto const& op: std::vector{ Instruction::ADDRESS, Instruction::CALLER, @@ -208,6 +287,19 @@ std::vector> simplificationRuleListPart2( }); } + return rules; +} + +template +std::vector> simplificationRuleListPart6( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector> rules; // Double negation of opcodes with boolean result for (auto const& op: std::vector{ Instruction::EQ, @@ -234,6 +326,19 @@ std::vector> simplificationRuleListPart2( false }); + return rules; +} + +template +std::vector> simplificationRuleListPart7( + Pattern A, + Pattern B, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector> rules; // Associative operations for (auto const& opFun: std::vector>>{ {Instruction::ADD, std::plus()}, @@ -273,13 +378,164 @@ std::vector> simplificationRuleListPart2( }}; } } - for (auto const& add_sub: std::vector>{ - {Instruction::ADD, Instruction::SUB} - }) + + // Combine two SHL by constant + rules.push_back({ + // SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X) + {Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}}, + [=]() -> Pattern { + bigint sum = bigint(A.d()) + B.d(); + if (sum >= 256) + return {Instruction::AND, {X, u256(0)}}; + else + return {Instruction::SHL, {u256(sum), X}}; + }, + false + }); + + // Combine two SHR by constant + rules.push_back({ + // SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X) + {Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}}, + [=]() -> Pattern { + bigint sum = bigint(A.d()) + B.d(); + if (sum >= 256) + return {Instruction::AND, {X, u256(0)}}; + else + return {Instruction::SHR, {u256(sum), X}}; + }, + false + }); + + // Combine SHL-SHR by constant + rules.push_back({ + // SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) + {Instruction::SHR, {{B}, {Instruction::SHL, {{A}, {X}}}}}, + [=]() -> Pattern { + u256 mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d()); + + if (A.d() > B.d()) + return {Instruction::AND, {{Instruction::SHL, {A.d() - B.d(), X}}, mask}}; + else if (B.d() > A.d()) + return {Instruction::AND, {{Instruction::SHR, {B.d() - A.d(), X}}, mask}}; + else + return {Instruction::AND, {X, mask}}; + }, + false, + [=] { return A.d() < 256 && B.d() < 256; } + }); + + // Combine SHR-SHL by constant + rules.push_back({ + // SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) + {Instruction::SHL, {{B}, {Instruction::SHR, {{A}, {X}}}}}, + [=]() -> Pattern { + u256 mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d())); + + if (A.d() > B.d()) + return {Instruction::AND, {{Instruction::SHR, {A.d() - B.d(), X}}, mask}}; + else if (B.d() > A.d()) + return {Instruction::AND, {{Instruction::SHL, {B.d() - A.d(), X}}, mask}}; + else + return {Instruction::AND, {X, mask}}; + }, + false, + [=] { return A.d() < 256 && B.d() < 256; } + }); + + // Move AND with constant across SHL and SHR by constant + for (auto shiftOp: {Instruction::SHL, Instruction::SHR}) { - auto const& add = add_sub.first; - auto const& sub = add_sub.second; - for (auto xa: {std::vector{X, A}, std::vector{A, X}}) + auto replacement = [=]() -> Pattern { + u256 mask = + shiftOp == Instruction::SHL ? + shlWorkaround(A.d(), unsigned(B.d())) : + A.d() >> unsigned(B.d()); + return {Instruction::AND, {{shiftOp, {B.d(), X}}, std::move(mask)}}; + }; + rules.push_back({ + // SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) + {shiftOp, {{B}, {Instruction::AND, {{X}, {A}}}}}, + replacement, + false, + [=] { return B.d() < 256; } + }); + rules.push_back({ + // SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) + {shiftOp, {{B}, {Instruction::AND, {{A}, {X}}}}}, + replacement, + false, + [=] { return B.d() < 256; } + }); + } + + rules.push_back({ + // MUL(X, SHL(Y, 1)) -> SHL(Y, X) + {Instruction::MUL, {X, {Instruction::SHL, {Y, u256(1)}}}}, + [=]() -> Pattern { + return {Instruction::SHL, {Y, X}}; + }, + // Actually only changes the order, does not remove. + true + }); + rules.push_back({ + // MUL(SHL(X, 1), Y) -> SHL(X, Y) + {Instruction::MUL, {{Instruction::SHL, {X, u256(1)}}, Y}}, + [=]() -> Pattern { + return {Instruction::SHL, {X, Y}}; + }, + false + }); + + rules.push_back({ + // DIV(X, SHL(Y, 1)) -> SHR(Y, X) + {Instruction::DIV, {X, {Instruction::SHL, {Y, u256(1)}}}}, + [=]() -> Pattern { + return {Instruction::SHR, {Y, X}}; + }, + // Actually only changes the order, does not remove. + true + }); + + std::function feasibilityFunction = [=]() { + if (B.d() > 256) + return false; + unsigned bAsUint = static_cast(B.d()); + return (A.d() & (u256(-1) >> bAsUint)) == (u256(-1) >> bAsUint); + }; + + rules.push_back({ + // AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B) + {Instruction::AND, {A, {Instruction::SHR, {B, X}}}}, + [=]() -> Pattern { return {Instruction::SHR, {B, X}}; }, + false, + feasibilityFunction + }); + + rules.push_back({ + // AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B) + {Instruction::AND, {{Instruction::SHR, {B, X}}, A}}, + [=]() -> Pattern { return {Instruction::SHR, {B, X}}; }, + false, + feasibilityFunction + }); + + return rules; +} + +template +std::vector> simplificationRuleListPart8( + Pattern A, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector> rules; + + // move constants across subtractions + rules += std::vector>{ { rules += std::vector>{{ // (X + A) - B -> X + (A - B), X - (B - A) @@ -362,8 +618,48 @@ std::vector> simplificationRuleListPart2( return rules; } +template +std::vector> simplificationRuleListPart9( + Pattern, + Pattern, + Pattern, + Pattern W, + Pattern X, + Pattern Y, + Pattern Z +) +{ + std::vector> rules; + + u256 const mask = (u256(1) << 160) - 1; + // CREATE + rules.push_back({ + {Instruction::AND, {{Instruction::CREATE, {W, X, Y}}, mask}}, + [=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; }, + false + }); + rules.push_back({ + {Instruction::AND, {{mask, {Instruction::CREATE, {W, X, Y}}}}}, + [=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; }, + false + }); + // CREATE2 + rules.push_back({ + {Instruction::AND, {{Instruction::CREATE2, {W, X, Y, Z}}, mask}}, + [=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; }, + false + }); + rules.push_back({ + {Instruction::AND, {{mask, {Instruction::CREATE2, {W, X, Y, Z}}}}}, + [=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; }, + false + }); + + return rules; +} + /// @returns a list of simplification rules given certain match placeholders. -/// A, B and C should represent constants, X and Y arbitrary expressions. +/// A, B and C should represent constants, W, X, Y, and Z arbitrary expressions. /// The simplifications should never change the order of evaluation of /// arbitrary operations. template @@ -371,13 +667,22 @@ std::vector> simplificationRuleList( Pattern A, Pattern B, Pattern C, + Pattern W, Pattern X, - Pattern Y + Pattern Y, + Pattern Z ) { std::vector> rules; - rules += simplificationRuleListPart1(A, B, C, X, Y); - rules += simplificationRuleListPart2(A, B, C, X, Y); + rules += simplificationRuleListPart1(A, B, C, W, X); + rules += simplificationRuleListPart2(A, B, C, W, X); + rules += simplificationRuleListPart3(A, B, C, W, X); + rules += simplificationRuleListPart4(A, B, C, W, X); + rules += simplificationRuleListPart5(A, B, C, W, X); + rules += simplificationRuleListPart6(A, B, C, W, X); + rules += simplificationRuleListPart7(A, B, C, W, X); + rules += simplificationRuleListPart8(A, B, C, W, X); + rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z); return rules; } diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 908466f83..6f25064bb 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -96,19 +96,19 @@ bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) { if (_item.type() != Operation) return false; - return solidity::isDupInstruction(_item.instruction()); + return dev::eth::isDupInstruction(_item.instruction()); } bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) { if (_item.type() != Operation) return false; - return solidity::isSwapInstruction(_item.instruction()); + return dev::eth::isSwapInstruction(_item.instruction()); } bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) { - return _item == AssemblyItem(Instruction::JUMP) || _item == AssemblyItem(Instruction::JUMPI); + return _item == Instruction::JUMP || _item == Instruction::JUMPI; } bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) @@ -132,6 +132,28 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) } } +bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + else + return terminatesControlFlow(_item.instruction()); +} + +bool SemanticInformation::terminatesControlFlow(Instruction _instruction) +{ + switch (_instruction) + { + case Instruction::RETURN: + case Instruction::SELFDESTRUCT: + case Instruction::STOP: + case Instruction::INVALID: + case Instruction::REVERT: + return true; + default: + return false; + } +} bool SemanticInformation::isDeterministic(AssemblyItem const& _item) { @@ -150,6 +172,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) case Instruction::PC: case Instruction::MSIZE: // depends on previous writes and reads, not only on content case Instruction::BALANCE: // depends on previous calls + case Instruction::SELFBALANCE: // depends on previous calls case Instruction::EXTCODESIZE: case Instruction::EXTCODEHASH: case Instruction::RETURNDATACOPY: // depends on previous calls @@ -172,6 +195,7 @@ bool SemanticInformation::movable(Instruction _instruction) { case Instruction::KECCAK256: case Instruction::BALANCE: + case Instruction::SELFBALANCE: case Instruction::EXTCODESIZE: case Instruction::EXTCODEHASH: case Instruction::RETURNDATASIZE: @@ -186,6 +210,22 @@ bool SemanticInformation::movable(Instruction _instruction) return true; } +bool SemanticInformation::sideEffectFree(Instruction _instruction) +{ + // These are not really functional. + assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, ""); + + return !instructionInfo(_instruction).sideEffects; +} + +bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction) +{ + if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD) + return true; + else + return sideEffectFree(_instruction); +} + bool SemanticInformation::invalidatesMemory(Instruction _instruction) { switch (_instruction) @@ -227,6 +267,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) switch (_instruction) { case Instruction::ADDRESS: + case Instruction::SELFBALANCE: case Instruction::BALANCE: case Instruction::ORIGIN: case Instruction::CALLER: diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index 8bdc70bec..5f2d3b056 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -47,19 +47,31 @@ struct SemanticInformation static bool isSwapInstruction(AssemblyItem const& _item); static bool isJumpInstruction(AssemblyItem const& _item); static bool altersControlFlow(AssemblyItem const& _item); + static bool terminatesControlFlow(AssemblyItem const& _item); + static bool terminatesControlFlow(Instruction _instruction); /// @returns false if the value put on the stack by _item depends on anything else than /// the information in the current block header, memory, storage or stack. static bool isDeterministic(AssemblyItem const& _item); /// @returns true if the instruction can be moved or copied (together with its arguments) /// without altering the semantics. This means it cannot depend on storage or memory, /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain. - static bool movable(solidity::Instruction _instruction); + static bool movable(Instruction _instruction); + /// @returns true if the instruction can be removed without changing the semantics. + /// This does not mean that it has to be deterministic or retrieve information from + /// somewhere else than purely the values of its arguments. + static bool sideEffectFree(Instruction _instruction); + /// @returns true if the instruction can be removed without changing the semantics. + /// This does not mean that it has to be deterministic or retrieve information from + /// somewhere else than purely the values of its arguments. + /// If true, the instruction is still allowed to influence the value returned by the + /// msize instruction. + static bool sideEffectFreeIfNoMSize(Instruction _instruction); /// @returns true if the given instruction modifies memory. - static bool invalidatesMemory(solidity::Instruction _instruction); + static bool invalidatesMemory(Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). - static bool invalidatesStorage(solidity::Instruction _instruction); - static bool invalidInPureFunctions(solidity::Instruction _instruction); - static bool invalidInViewFunctions(solidity::Instruction _instruction); + static bool invalidatesStorage(Instruction _instruction); + static bool invalidInPureFunctions(Instruction _instruction); + static bool invalidInViewFunctions(Instruction _instruction); }; } diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index 7b4dea687..5c752545f 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -24,7 +24,7 @@ namespace dev { -namespace solidity +namespace eth { /** @@ -36,9 +36,22 @@ namespace solidity template struct SimplificationRule { + SimplificationRule( + Pattern _pattern, + std::function _action, + bool _removesNonConstants, + std::function _feasible = {} + ): + pattern(std::move(_pattern)), + action(std::move(_action)), + removesNonConstants(_removesNonConstants), + feasible(std::move(_feasible)) + {} + Pattern pattern; std::function action; bool removesNonConstants; + std::function feasible; }; } diff --git a/libevmasm/SimplificationRules.cpp b/libevmasm/SimplificationRules.cpp index 120d17877..ca201862a 100644 --- a/libevmasm/SimplificationRules.cpp +++ b/libevmasm/SimplificationRules.cpp @@ -38,7 +38,7 @@ using namespace std; using namespace dev; using namespace dev::eth; - +using namespace langutil; SimplificationRule const* Rules::findFirstMatch( Expression const& _expr, @@ -51,7 +51,9 @@ SimplificationRule const* Rules::findFirstMatch( for (auto const& rule: m_rules[uint8_t(_expr.item->instruction())]) { if (rule.pattern.matches(_expr, _classes)) - return &rule; + if (!rule.feasible || rule.feasible()) + return &rule; + resetMatchGroups(); } return nullptr; @@ -81,15 +83,19 @@ Rules::Rules() Pattern B(Push); Pattern C(Push); // Anything. + Pattern W; Pattern X; Pattern Y; + Pattern Z; A.setMatchGroup(1, m_matchGroups); B.setMatchGroup(2, m_matchGroups); C.setMatchGroup(3, m_matchGroups); - X.setMatchGroup(4, m_matchGroups); - Y.setMatchGroup(5, m_matchGroups); + W.setMatchGroup(4, m_matchGroups); + X.setMatchGroup(5, m_matchGroups); + Y.setMatchGroup(6, m_matchGroups); + Z.setMatchGroup(7, m_matchGroups); - addRules(simplificationRuleList(A, B, C, X, Y)); + addRules(simplificationRuleList(A, B, C, W, X, Y, Z)); assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); } @@ -209,7 +215,7 @@ ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation c item = _pattern.toAssemblyItem(_location); } for (auto const& arg: _pattern.arguments()) - arguments.push_back(ExpressionTemplate(arg, _location)); + arguments.emplace_back(arg, _location); } string ExpressionTemplate::toString() const diff --git a/libevmasm/SimplificationRules.h b/libevmasm/SimplificationRules.h index fbe5a2b09..fc45a46c3 100644 --- a/libevmasm/SimplificationRules.h +++ b/libevmasm/SimplificationRules.h @@ -31,6 +31,11 @@ #include #include +namespace langutil +{ +struct SourceLocation; +} + namespace dev { namespace eth @@ -97,7 +102,7 @@ class Pattern unsigned matchGroup() const { return m_matchGroup; } bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; - AssemblyItem toAssemblyItem(SourceLocation const& _location) const; + AssemblyItem toAssemblyItem(langutil::SourceLocation const& _location) const; std::vector arguments() const { return m_arguments; } /// @returns the id of the matched expression if this pattern is part of a match group. @@ -135,7 +140,7 @@ struct ExpressionTemplate { using Expression = ExpressionClasses::Expression; using Id = ExpressionClasses::Id; - explicit ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location); + explicit ExpressionTemplate(Pattern const& _pattern, langutil::SourceLocation const& _location); std::string toString() const; bool hasId = false; /// Id of the matched expression, if available. diff --git a/libevmasm/SourceLocation.h b/libevmasm/SourceLocation.h deleted file mode 100644 index b42c3aa98..000000000 --- a/libevmasm/SourceLocation.h +++ /dev/null @@ -1,105 +0,0 @@ -/* - 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 Lefteris Karapetsas - * @date 2015 - * Represents a location in a source file - */ - -#pragma once - -#include -#include -#include -#include -#include // defines noexcept macro for MSVC - -namespace dev -{ - -/** - * Representation of an interval of source positions. - * The interval includes start and excludes end. - */ -struct SourceLocation -{ - SourceLocation(): start(-1), end(-1) { } - SourceLocation(int _start, int _end, std::shared_ptr _sourceName): - start(_start), end(_end), sourceName(_sourceName) { } - SourceLocation(SourceLocation&& _other) noexcept: - start(_other.start), - end(_other.end), - sourceName(std::move(_other.sourceName)) - {} - SourceLocation(SourceLocation const&) = default; - SourceLocation& operator=(SourceLocation const&) = default; - SourceLocation& operator=(SourceLocation&& _other) noexcept - { - start = _other.start; - end = _other.end; - sourceName = std::move(_other.sourceName); - return *this; - } - - bool operator==(SourceLocation const& _other) const - { - return start == _other.start && end == _other.end && - ((!sourceName && !_other.sourceName) || (sourceName && _other.sourceName && *sourceName == *_other.sourceName)); - } - bool operator!=(SourceLocation const& _other) const { return !operator==(_other); } - inline bool operator<(SourceLocation const& _other) const; - inline bool contains(SourceLocation const& _other) const; - inline bool intersects(SourceLocation const& _other) const; - - bool isEmpty() const { return start == -1 && end == -1; } - - int start; - int end; - std::shared_ptr sourceName; -}; - -/// Stream output for Location (used e.g. in boost exceptions). -inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) -{ - if (_location.isEmpty()) - return _out << "NO_LOCATION_SPECIFIED"; - return _out << *_location.sourceName << "[" << _location.start << "," << _location.end << ")"; -} - -bool SourceLocation::operator<(SourceLocation const& _other) const -{ - if (!sourceName || !_other.sourceName) - return std::make_tuple(int(!!sourceName), start, end) < std::make_tuple(int(!!_other.sourceName), _other.start, _other.end); - else - return std::make_tuple(*sourceName, start, end) < std::make_tuple(*_other.sourceName, _other.start, _other.end); -} - -bool SourceLocation::contains(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || ((!sourceName || !_other.sourceName || *sourceName != *_other.sourceName) && (sourceName || _other.sourceName))) - return false; - return start <= _other.start && _other.end <= end; -} - -bool SourceLocation::intersects(SourceLocation const& _other) const -{ - if (isEmpty() || _other.isEmpty() || ((!sourceName || !_other.sourceName || *sourceName != *_other.sourceName) && (sourceName || _other.sourceName))) - return false; - return _other.start < end && start < _other.end; -} - -} diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt new file mode 100644 index 000000000..63c9de39d --- /dev/null +++ b/liblangutil/CMakeLists.txt @@ -0,0 +1,31 @@ +# Solidity Commons Library (Solidity related sharing bits between libsolidity and libyul) +set(sources + Common.h + CharStream.cpp + CharStream.h + ErrorReporter.cpp + ErrorReporter.h + EVMVersion.h + EVMVersion.cpp + Exceptions.cpp + Exceptions.h + ParserBase.cpp + ParserBase.h + Scanner.cpp + Scanner.h + SemVerHandler.cpp + SemVerHandler.h + SourceLocation.h + SourceReferenceExtractor.cpp + SourceReferenceExtractor.h + SourceReferenceFormatter.cpp + SourceReferenceFormatter.h + SourceReferenceFormatterHuman.cpp + SourceReferenceFormatterHuman.h + Token.cpp + Token.h + UndefMacros.h +) + +add_library(langutil ${sources}) +target_link_libraries(langutil PUBLIC devcore) diff --git a/liblangutil/CharStream.cpp b/liblangutil/CharStream.cpp new file mode 100644 index 000000000..58edced3b --- /dev/null +++ b/liblangutil/CharStream.cpp @@ -0,0 +1,115 @@ +/* + * This file is part of solidity. + * + * solidity is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * solidity is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with solidity. If not, see . + * + * This file is derived from the file "scanner.cc", which was part of the + * V8 project. The original copyright header follows: + * + * Copyright 2006-2012, the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * @author Christian + * @date 2014 + * Solidity scanner. + */ + +#include +#include + +using namespace std; +using namespace langutil; + +char CharStream::advanceAndGet(size_t _chars) +{ + if (isPastEndOfInput()) + return 0; + m_position += _chars; + if (isPastEndOfInput()) + return 0; + return m_source[m_position]; +} + +char CharStream::rollback(size_t _amount) +{ + solAssert(m_position >= _amount, ""); + m_position -= _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 + using size_type = string::size_type; + size_type searchStart = min(m_source.size(), _position); + if (searchStart > 0) + searchStart--; + size_type lineStart = m_source.rfind('\n', searchStart); + if (lineStart == string::npos) + lineStart = 0; + else + lineStart++; + return m_source.substr( + lineStart, + min(m_source.find('\n', lineStart), m_source.size()) - lineStart + ); +} + +tuple CharStream::translatePositionToLineColumn(int _position) const +{ + using size_type = string::size_type; + size_type searchPosition = min(m_source.size(), _position); + int lineNumber = count(m_source.begin(), m_source.begin() + searchPosition, '\n'); + size_type lineStart; + if (searchPosition == 0) + lineStart = 0; + else + { + lineStart = m_source.rfind('\n', searchPosition - 1); + lineStart = lineStart == string::npos ? 0 : lineStart + 1; + } + return tuple(lineNumber, searchPosition - lineStart); +} diff --git a/liblangutil/CharStream.h b/liblangutil/CharStream.h new file mode 100644 index 000000000..504c39da5 --- /dev/null +++ b/liblangutil/CharStream.h @@ -0,0 +1,106 @@ +/* + * 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 . + * + * This file is derived from the file "scanner.h", which was part of the + * V8 project. The original copyright header follows: + * + * Copyright 2006-2012, the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * @author Christian + * @date 2014 + * Solidity scanner. + */ + +#pragma once + +#include +#include +#include + +namespace langutil +{ + +/** + * Bidirectional stream of characters. + * + * This CharStream is used by lexical analyzers as the source. + */ +class CharStream +{ +public: + CharStream() = default; + explicit CharStream(std::string const& _source, std::string const& name): + m_source(_source), m_name(name) {} + + int position() const { return m_position; } + bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); } + + 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; } + + std::string const& source() const noexcept { return m_source; } + std::string const& name() const noexcept { return m_name; } + + ///@{ + ///@name Error printing helper functions + /// Functions that help pretty-printing parse errors + /// Do only use in error cases, they are quite expensive. + std::string lineAtPosition(int _position) const; + std::tuple translatePositionToLineColumn(int _position) const; + ///@} + +private: + std::string m_source; + std::string m_name; + size_t m_position{0}; +}; + +} diff --git a/liblangutil/Common.h b/liblangutil/Common.h new file mode 100644 index 000000000..a8d935270 --- /dev/null +++ b/liblangutil/Common.h @@ -0,0 +1,58 @@ +/* + This file is part of solidity. + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +namespace langutil +{ + +inline bool isDecimalDigit(char c) +{ + return '0' <= c && c <= '9'; +} + +inline bool isHexDigit(char c) +{ + return + isDecimalDigit(c) || + ('a' <= c && c <= 'f') || + ('A' <= c && c <= 'F'); +} + +inline bool isWhiteSpace(char c) +{ + return c == ' ' || c == '\n' || c == '\t' || c == '\r'; +} + +inline bool isIdentifierStart(char c) +{ + return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); +} + +inline bool isIdentifierPart(char c) +{ + return isIdentifierStart(c) || isDecimalDigit(c); +} + +inline int hexValue(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else return -1; +} +} diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp new file mode 100644 index 000000000..3d546a9e4 --- /dev/null +++ b/liblangutil/EVMVersion.cpp @@ -0,0 +1,51 @@ +/* + 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 . +*/ +/** + * EVM versioning. + */ + +#include + +using namespace langutil; +using namespace dev::eth; + +bool EVMVersion::hasOpcode(Instruction _opcode) const +{ + switch (_opcode) + { + case Instruction::RETURNDATACOPY: + case Instruction::RETURNDATASIZE: + return supportsReturndata(); + case Instruction::STATICCALL: + return hasStaticCall(); + case Instruction::SHL: + case Instruction::SHR: + case Instruction::SAR: + return hasBitwiseShifting(); + case Instruction::CREATE2: + return hasCreate2(); + case Instruction::EXTCODEHASH: + return hasExtCodeHash(); + case Instruction::CHAINID: + return hasChainID(); + case Instruction::SELFBALANCE: + return hasSelfBalance(); + default: + return true; + } +} + diff --git a/libsolidity/interface/EVMVersion.h b/liblangutil/EVMVersion.h similarity index 76% rename from libsolidity/interface/EVMVersion.h rename to liblangutil/EVMVersion.h index 2df961eaf..99a8a0326 100644 --- a/libsolidity/interface/EVMVersion.h +++ b/liblangutil/EVMVersion.h @@ -20,26 +20,27 @@ #pragma once +#include + #include #include #include -namespace dev -{ -namespace solidity + +namespace langutil { /** * A version specifier of the EVM we want to compile to. - * Defaults to the latest version. + * Defaults to the latest version deployed on Ethereum mainnet at the time of compiler release. */ class EVMVersion: boost::less_than_comparable, boost::equality_comparable { public: - EVMVersion() {} + EVMVersion() = default; static EVMVersion homestead() { return {Version::Homestead}; } static EVMVersion tangerineWhistle() { return {Version::TangerineWhistle}; } @@ -47,9 +48,14 @@ class EVMVersion: static EVMVersion byzantium() { return {Version::Byzantium}; } static EVMVersion lity(){ return {Version::Lity}; } static EVMVersion constantinople() { return {Version::Constantinople}; } + static EVMVersion petersburg() { return {Version::Petersburg}; } + static EVMVersion istanbul() { return {Version::Istanbul}; } + static EVMVersion berlin() { return {Version::Berlin}; } + static EVMVersion lity() { return {Version::Lity}; } + static boost::optional fromString(std::string const& _version) { - for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), lity(), constantinople()}) + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), lity(), constantinople(), petersburg(), istanbul(), berlin()}) if (_version == v.name()) return v; return {}; @@ -68,6 +74,9 @@ class EVMVersion: case Version::Byzantium: return "byzantium"; case Version::Lity: return "lity"; case Version::Constantinople: return "constantinople"; + case Version::Petersburg: return "petersburg"; + case Version::Istanbul: return "istanbul"; + case Version::Berlin: return "berlin"; } return "INVALID"; } @@ -83,19 +92,23 @@ class EVMVersion: bool hasRand() const {return *this == lity(); } bool hasBitwiseShifting() const { return *this >= constantinople(); } bool hasCreate2() const { return *this >= constantinople(); } + bool hasExtCodeHash() const { return *this >= constantinople(); } + bool hasChainID() const { return *this >= istanbul(); } + bool hasSelfBalance() const { return *this >= istanbul(); } + + bool hasOpcode(dev::eth::Instruction _opcode) const; /// Whether we have to retain the costs for the call opcode itself (false), /// or whether we can just forward easily all remaining gas (true). bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } private: - enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Lity, Constantinople }; + + enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, Lity }; EVMVersion(Version _version): m_version(_version) {} Version m_version = Version::Lity; }; - -} } diff --git a/libsolidity/interface/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp similarity index 87% rename from libsolidity/interface/ErrorReporter.cpp rename to liblangutil/ErrorReporter.cpp index e78083fd4..b098ddf6a 100644 --- a/libsolidity/interface/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -20,13 +20,13 @@ * Error helper class. */ -#include -#include +#include +#include #include using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace langutil; ErrorReporter& ErrorReporter::operator=(ErrorReporter const& _errorReporter) { @@ -108,6 +108,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::Info) @@ -151,6 +156,12 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type) return false; } +void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + error(_type, _location, _secondaryLocation, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} + void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description) { error(_type, _location, _description); @@ -167,7 +178,7 @@ void ErrorReporter::clear() m_errorList.clear(); } -void ErrorReporter::declarationError(SourceLocation const& _location, SecondarySourceLocation const&_secondaryLocation, string const& _description) +void ErrorReporter::declarationError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) { error( Error::Type::DeclarationError, @@ -240,6 +251,15 @@ void ErrorReporter::typeError(SourceLocation const& _location, string const& _de ); } +void ErrorReporter::fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) +{ + fatalError( + Error::Type::TypeError, + _location, + _secondaryLocation, + _description + ); +} void ErrorReporter::fatalTypeError(SourceLocation const& _location, string const& _description) { diff --git a/libsolidity/interface/ErrorReporter.h b/liblangutil/ErrorReporter.h similarity index 70% rename from libsolidity/interface/ErrorReporter.h rename to liblangutil/ErrorReporter.h index 64c15259b..b0360d95b 100644 --- a/libsolidity/interface/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -22,15 +22,16 @@ #pragma once -#include -#include +#include -namespace dev -{ -namespace solidity -{ +#include +#include +#include -class ASTNode; +#include + +namespace langutil +{ class ErrorReporter { @@ -44,15 +45,10 @@ class ErrorReporter ErrorReporter& operator=(ErrorReporter const& _errorReporter); - void info(std::string const& _description); - - void info(SourceLocation const& _location, std::string const& _description); - - void info( - SourceLocation const& _location, - std::string const& _description, - SecondarySourceLocation const& _secondaryLocation - ); + void append(ErrorList const& _errorList) + { + m_errorList += _errorList; + } void warning(std::string const& _description); @@ -94,7 +90,21 @@ class ErrorReporter void typeError(SourceLocation const& _location, std::string const& _description); + template + void typeErrorConcatenateDescriptions(SourceLocation const& _location, Strings const&... _descriptions) + { + std::initializer_list const descs = {_descriptions...}; + solAssert(descs.size() > 0, "Need error descriptions!"); + + auto filterEmpty = boost::adaptors::filtered([](std::string const& _s) { return !_s.empty(); }); + + std::string errorStr = dev::joinHumanReadable(descs | filterEmpty, " "); + + error(Error::Type::TypeError, _location, errorStr); + } + void fatalTypeError(SourceLocation const& _location, std::string const& _description); + void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description); void docstringParsingError(std::string const& _description); @@ -108,13 +118,24 @@ class ErrorReporter return m_errorCount > 0; } + // @returns true if the maximum error count has been reached. + bool hasExcessiveErrors() const; + private: - void error(Error::Type _type, + void error( + Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, std::string const& _description = std::string()); - void fatalError(Error::Type _type, + void fatalError( + Error::Type _type, + SourceLocation const& _location, + SecondarySourceLocation const& _secondaryLocation, + std::string const& _description = std::string()); + + void fatalError( + Error::Type _type, SourceLocation const& _location = SourceLocation(), std::string const& _description = std::string()); @@ -127,12 +148,8 @@ class ErrorReporter unsigned m_warningCount = 0; unsigned m_infoCount = 0; - const unsigned c_maxInfosAllowed = 256; - const unsigned c_maxWarningsAllowed = 256; - const unsigned c_maxErrorsAllowed = 256; + unsigned const c_maxWarningsAllowed = 256; + unsigned const c_maxErrorsAllowed = 256; }; - -} } - diff --git a/libsolidity/interface/Exceptions.cpp b/liblangutil/Exceptions.cpp similarity index 92% rename from libsolidity/interface/Exceptions.cpp rename to liblangutil/Exceptions.cpp index 4d2b310e1..60d706e27 100644 --- a/libsolidity/interface/Exceptions.cpp +++ b/liblangutil/Exceptions.cpp @@ -20,11 +20,11 @@ * Solidity exception hierarchy. */ -#include +#include using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace langutil; Error::Error(Type _type, SourceLocation const& _location, string const& _description): m_type(_type) @@ -59,7 +59,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip *this << errinfo_comment(_description); } -Error::Error(Error::Type _type, const std::string& _description, const SourceLocation& _location): +Error::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location): Error(_type) { if (!_location.isEmpty()) diff --git a/libsolidity/interface/Exceptions.h b/liblangutil/Exceptions.h similarity index 65% rename from libsolidity/interface/Exceptions.h rename to liblangutil/Exceptions.h index acc6100e2..7b0ae4378 100644 --- a/libsolidity/interface/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -24,33 +24,34 @@ #include #include +#include +#include #include #include -#include +#include +#include -namespace dev -{ -namespace solidity +namespace langutil { class Error; using ErrorList = std::vector>; -struct CompilerError: virtual Exception {}; -struct InternalCompilerError: virtual Exception {}; -struct FatalError: virtual Exception {}; -struct UnimplementedFeatureError: virtual Exception{}; +struct CompilerError: virtual dev::Exception {}; +struct InternalCompilerError: virtual dev::Exception {}; +struct FatalError: virtual dev::Exception {}; +struct UnimplementedFeatureError: virtual dev::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ - assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) + assertThrow(CONDITION, ::langutil::InternalCompilerError, DESCRIPTION) #define solUnimplementedAssert(CONDITION, DESCRIPTION) \ - assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION) + assertThrow(CONDITION, ::langutil::UnimplementedFeatureError, DESCRIPTION) #define solUnimplemented(DESCRIPTION) \ - solUnimplementedAssert(false, DESCRIPTION) + solUnimplementedAssert(false, DESCRIPTION) -class Error: virtual public Exception +class Error: virtual public dev::Exception { public: enum class Type @@ -111,7 +112,6 @@ class Error: virtual public Exception std::string m_typeName; }; - using errorSourceLocationInfo = std::pair; class SecondarySourceLocation @@ -119,9 +119,15 @@ class SecondarySourceLocation public: SecondarySourceLocation& append(std::string const& _errMsg, SourceLocation const& _sourceLocation) { - infos.push_back(std::make_pair(_errMsg, _sourceLocation)); + infos.emplace_back(_errMsg, _sourceLocation); + return *this; + } + SecondarySourceLocation& append(SecondarySourceLocation&& _other) + { + infos += std::move(_other.infos); return *this; } + /// Limits the number of secondary source locations to 32 and appends a notice to the /// error message. void limitSize(std::string& _message) @@ -137,9 +143,8 @@ class SecondarySourceLocation std::vector infos; }; - using errinfo_sourceLocation = boost::error_info; using errinfo_secondarySourceLocation = boost::error_info; -} + } diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp new file mode 100644 index 000000000..4ceb9358f --- /dev/null +++ b/liblangutil/ParserBase.cpp @@ -0,0 +1,173 @@ +/* + 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 Christian + * @date 2016 + * Solidity parser shared functionality. + */ + +#include +#include +#include + +using namespace std; +using namespace langutil; + +int ParserBase::position() const +{ + return m_scanner->currentLocation().start; +} + +int ParserBase::endPosition() const +{ + return m_scanner->currentLocation().end; +} + +Token ParserBase::currentToken() const +{ + return m_scanner->currentToken(); +} + +Token ParserBase::peekNextToken() const +{ + return m_scanner->peekNextToken(); +} + +string ParserBase::currentLiteral() const +{ + return m_scanner->currentLiteral(); +} + +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) + { + 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 >= 1200) + fatalParserError("Maximum recursion depth reached during parsing."); +} + +void ParserBase::decreaseRecursionDepth() +{ + solAssert(m_recursionDepth > 0, ""); + 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) +{ + parserError(SourceLocation{position(), endPosition(), source()}, _description); +} + +void ParserBase::fatalParserError(string const& _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 new file mode 100644 index 000000000..98123b813 --- /dev/null +++ b/liblangutil/ParserBase.h @@ -0,0 +1,116 @@ +/* + 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 Christian + * @date 2016 + * Solidity parser shared functionality. + */ + +#pragma once + +#include +#include +#include +#include + +namespace langutil +{ + +class ErrorReporter; +class Scanner; + +class ParserBase +{ +public: + /// 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(); } + +protected: + /// Utility class that creates an error and throws an exception if the + /// recursion depth is too deep. + class RecursionGuard + { + public: + explicit RecursionGuard(ParserBase& _parser): m_parser(_parser) + { + m_parser.increaseRecursionDepth(); + } + ~RecursionGuard() { m_parser.decreaseRecursionDepth(); } + private: + ParserBase& m_parser; + }; + + /// Start position of the current token + int position() const; + /// End position of the current token + int endPosition() const; + + ///@{ + ///@name Helper functions + /// 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(); + ///@} + + /// Increases the recursion depth and throws an exception if it is too deep. + void increaseRecursionDepth(); + void decreaseRecursionDepth(); + + /// 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/libsolidity/parsing/Scanner.cpp b/liblangutil/Scanner.cpp similarity index 65% rename from libsolidity/parsing/Scanner.cpp rename to liblangutil/Scanner.cpp index e9dad2ad2..131462289 100644 --- a/libsolidity/parsing/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -1,48 +1,48 @@ /* - 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 . - - This file is derived from the file "scanner.cc", which was part of the - V8 project. The original copyright header follows: - - Copyright 2006-2012, the V8 project authors. All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * 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 . + * + * This file is derived from the file "scanner.cc", which was part of the + * V8 project. The original copyright header follows: + * + * Copyright 2006-2012, the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @author Christian @@ -50,63 +50,51 @@ * Solidity scanner. */ +#include +#include +#include +#include #include +#include #include -#include -#include using namespace std; +using namespace langutil; -namespace dev +string langutil::to_string(ScannerError _errorCode) { -namespace solidity -{ - -namespace -{ -bool isDecimalDigit(char c) -{ - return '0' <= c && c <= '9'; -} -bool isHexDigit(char c) -{ - return isDecimalDigit(c) - || ('a' <= c && c <= 'f') - || ('A' <= c && c <= 'F'); -} -bool isLineTerminator(char c) -{ - return c == '\n'; -} -bool isWhiteSpace(char c) -{ - return c == ' ' || c == '\n' || c == '\t' || c == '\r'; -} -bool isIdentifierStart(char c) -{ - return c == '_' || c == '$' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); + switch (_errorCode) + { + case ScannerError::NoError: return "No error."; + case ScannerError::IllegalToken: return "Invalid token."; + case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles within double-quotes."; + case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid."; + case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator."; + case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence."; + case ScannerError::IllegalStringEndQuote: return "Expected string end-quote."; + case ScannerError::IllegalNumberSeparator: return "Invalid use of number separator '_'."; + case ScannerError::IllegalExponent: return "Invalid exponent."; + case ScannerError::IllegalNumberEnd: return "Identifier-start is not allowed at end of a number."; + case ScannerError::OctalNotAllowed: return "Octal numbers not allowed."; + default: + solAssert(false, "Unhandled case in to_string(ScannerError)"); + return ""; + } } -bool isIdentifierPart(char c) + + +ostream& langutil::operator<<(ostream& os, ScannerError _errorCode) { - return isIdentifierStart(c) || isDecimalDigit(c); -} -int hexValue(char c) -{ - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - else if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - else return -1; + return os << to_string(_errorCode); } -} // end anonymous namespace - +namespace langutil +{ /// Scoped helper for literal recording. Automatically drops the literal /// if aborting the scanning before it's complete. -enum LiteralType { +enum LiteralType +{ LITERAL_TYPE_STRING, LITERAL_TYPE_NUMBER, // not really different from string type in behaviour LITERAL_TYPE_COMMENT @@ -115,9 +103,10 @@ enum LiteralType { class LiteralScope { public: - explicit LiteralScope(Scanner* _self, enum LiteralType _type): m_type(_type) - , m_scanner(_self) - , m_complete(false) + explicit LiteralScope(Scanner* _self, enum LiteralType _type): + m_type(_type), + m_scanner(_self), + m_complete(false) { if (_type == LITERAL_TYPE_COMMENT) m_scanner->m_nextSkippedComment.literal.clear(); @@ -140,25 +129,46 @@ class LiteralScope enum LiteralType m_type; Scanner* m_scanner; bool m_complete; -}; // end of LiteralScope class +}; + +} +void Scanner::reset(CharStream _source) +{ + m_source = make_shared(std::move(_source)); + reset(); +} -void Scanner::reset(CharStream const& _source, string const& _sourceName) +void Scanner::reset(shared_ptr _source) { - m_source = _source; - m_sourceName = make_shared(_sourceName); + solAssert(_source.get() != nullptr, "You MUST provide a CharStream when resetting."); + m_source = std::move(_source); reset(); } void Scanner::reset() { - m_source.reset(); - m_char = m_source.get(); + m_source->reset(); + m_supportPeriodInIdentifier = false; + m_char = m_source->get(); skipWhitespace(); + next(); + next(); +} + +void Scanner::setPosition(size_t _offset) +{ + m_char = m_source->setPosition(_offset); scanToken(); next(); } +void Scanner::supportPeriodInIdentifier(bool _value) +{ + m_supportPeriodInIdentifier = _value; + rescan(); +} + bool Scanner::scanHexByte(char& o_scannedByte) { char x = 0; @@ -177,7 +187,7 @@ bool Scanner::scanHexByte(char& o_scannedByte) return true; } -bool Scanner::scanUnicode(unsigned & o_codepoint) +boost::optional Scanner::scanUnicode() { unsigned x = 0; for (int i = 0; i < 4; i++) @@ -186,13 +196,12 @@ bool Scanner::scanUnicode(unsigned & o_codepoint) if (d < 0) { rollback(i); - return false; + return {}; } x = x * 16 + d; advance(); } - o_codepoint = x; - return true; + return x; } // This supports codepoints between 0000 and FFFF. @@ -213,6 +222,18 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint) } } +void Scanner::rescan() +{ + size_t rollbackTo = 0; + if (m_skippedComment.literal.empty()) + rollbackTo = m_currentToken.location.start; + else + rollbackTo = m_skippedComment.location.start; + m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo); + next(); + next(); +} + // Ensure that tokens can be stored in a byte. BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100); @@ -259,6 +280,29 @@ Token Scanner::skipSingleLineComment() return Token::Whitespace; } +bool Scanner::atEndOfLine() const +{ + return m_char == '\n' || m_char == '\r'; +} + +bool Scanner::tryScanEndOfLine() +{ + if (m_char == '\n') + { + advance(); + return true; + } + + if (m_char == '\r') + { + if (advance() && m_char == '\n') + advance(); + return true; + } + + return false; +} + Token Scanner::scanSingleLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); @@ -268,21 +312,20 @@ Token Scanner::scanSingleLineDocComment() while (!isSourcePastEndOfInput()) { - if (isLineTerminator(m_char)) + if (tryScanEndOfLine()) { // check if next line is also a documentation comment skipWhitespace(); - if (!m_source.isPastEndOfInput(3) && - m_source.get(0) == '/' && - m_source.get(1) == '/' && - m_source.get(2) == '/') + if (!m_source->isPastEndOfInput(3) && + m_source->get(0) == '/' && + m_source->get(1) == '/' && + m_source->get(2) == '/') { addCommentLiteralChar('\n'); - m_char = m_source.advanceAndGet(3); + m_char = m_source->advanceAndGet(3); } else break; // next line is not a documentation comment, we are done - } else if (isUnicodeLinebreak()) // Any line terminator that is not '\n' is considered to end the @@ -313,7 +356,7 @@ Token Scanner::skipMultiLineComment() } } // Unterminated multi-line comment. - return Token::Illegal; + return setError(ScannerError::IllegalCommentTerminator); } Token Scanner::scanMultiLineDocComment() @@ -322,29 +365,29 @@ Token Scanner::scanMultiLineDocComment() bool endFound = false; bool charsAdded = false; - while (isWhiteSpace(m_char) && !isLineTerminator(m_char)) + while (isWhiteSpace(m_char) && !atEndOfLine()) advance(); while (!isSourcePastEndOfInput()) { //handle newlines in multline comments - if (isLineTerminator(m_char)) + if (atEndOfLine()) { skipWhitespace(); - if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '*') + if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '*') { // it is unknown if this leads to the end of the comment addCommentLiteralChar('*'); advance(); } - else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/') + else if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) != '/') { // skip first '*' in subsequent lines if (charsAdded) addCommentLiteralChar('\n'); - m_char = m_source.advanceAndGet(2); + m_char = m_source->advanceAndGet(2); } - else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') + else if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '/') { // if after newline the comment ends, don't insert the newline - m_char = m_source.advanceAndGet(2); + m_char = m_source->advanceAndGet(2); endFound = true; break; } @@ -352,9 +395,9 @@ Token Scanner::scanMultiLineDocComment() addCommentLiteralChar('\n'); } - if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/') + if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '/') { - m_char = m_source.advanceAndGet(2); + m_char = m_source->advanceAndGet(2); endFound = true; break; } @@ -364,7 +407,7 @@ Token Scanner::scanMultiLineDocComment() } literal.complete(); if (!endFound) - return Token::Illegal; + return setError(ScannerError::IllegalCommentTerminator); else return Token::CommentLiteral; } @@ -394,7 +437,7 @@ Token Scanner::scanSlash() { // doxygen style /** natspec comment if (!advance()) /* slash star comment before EOS */ - return Token::Illegal; + return setError(ScannerError::IllegalCommentTerminator); else if (m_char == '*') { advance(); //consume the last '*' at /** @@ -412,7 +455,7 @@ Token Scanner::scanSlash() m_nextSkippedComment.location.end = sourcePos(); m_nextSkippedComment.token = comment; if (comment == Token::Illegal) - return Token::Illegal; + return Token::Illegal; // error already set else return Token::Whitespace; } @@ -427,6 +470,7 @@ Token Scanner::scanSlash() void Scanner::scanToken() { + m_nextToken.error = ScannerError::NoError; m_nextToken.literal.clear(); m_nextToken.extendedTokenInfo = make_tuple(0, 0); m_nextSkippedComment.literal.clear(); @@ -564,7 +608,12 @@ void Scanner::scanToken() token = Token::Period; break; case ':': - token = selectToken(Token::Colon); + // : := + advance(); + if (m_char == '=') + token = selectToken(Token::AssemblyAssign); + else + token = Token::Colon; break; case ';': token = selectToken(Token::Semicolon); @@ -612,7 +661,7 @@ void Scanner::scanToken() if (m_char == '"' || m_char == '\'') token = scanHexString(); else - token = Token::IllegalHex; + token = setError(ScannerError::IllegalToken); } } else if (isDecimalDigit(m_char)) @@ -622,7 +671,7 @@ void Scanner::scanToken() else if (isSourcePastEndOfInput()) token = Token::EOS; else - token = selectToken(Token::Illegal); + token = selectErrorToken(ScannerError::IllegalToken); break; } // Continue scanning for tokens as long as we're just skipping @@ -637,10 +686,12 @@ void Scanner::scanToken() bool Scanner::scanEscape() { char c = m_char; - advance(); + // Skip escaped newlines. - if (isLineTerminator(c)) + if (tryScanEndOfLine()) return true; + advance(); + switch (c) { case '\'': // fall through @@ -667,10 +718,10 @@ bool Scanner::scanEscape() break; case 'u': { - unsigned codepoint; - if (!scanUnicode(codepoint)) + if (boost::optional codepoint = scanUnicode()) + addUnicodeAsUTF8(*codepoint); + else return false; - addUnicodeAsUTF8(codepoint); return true; } case 'x': @@ -690,11 +741,11 @@ bool Scanner::isUnicodeLinebreak() if (0x0a <= m_char && m_char <= 0x0d) // line feed, vertical tab, form feed, carriage return return true; - else if (!m_source.isPastEndOfInput(1) && uint8_t(m_source.get(0)) == 0xc2 && uint8_t(m_source.get(1)) == 0x85) + else if (!m_source->isPastEndOfInput(1) && uint8_t(m_source->get(0)) == 0xc2 && uint8_t(m_source->get(1)) == 0x85) // NEL - U+0085, C2 85 in utf8 return true; - else if (!m_source.isPastEndOfInput(2) && uint8_t(m_source.get(0)) == 0xe2 && uint8_t(m_source.get(1)) == 0x80 && ( - uint8_t(m_source.get(2)) == 0xa8 || uint8_t(m_source.get(2)) == 0xa9 + else if (!m_source->isPastEndOfInput(2) && uint8_t(m_source->get(0)) == 0xe2 && uint8_t(m_source->get(1)) == 0x80 && ( + uint8_t(m_source->get(2)) == 0xa8 || uint8_t(m_source->get(2)) == 0xa9 )) // LS - U+2028, E2 80 A8 in utf8 // PS - U+2029, E2 80 A9 in utf8 @@ -715,13 +766,13 @@ Token Scanner::scanString() if (c == '\\') { if (isSourcePastEndOfInput() || !scanEscape()) - return Token::Illegal; + return setError(ScannerError::IllegalEscapeSequence); } else addLiteralChar(c); } if (m_char != quote) - return Token::Illegal; + return setError(ScannerError::IllegalStringEndQuote); literal.complete(); advance(); // consume quote return Token::StringLiteral; @@ -736,11 +787,14 @@ Token Scanner::scanHexString() { char c = m_char; if (!scanHexByte(c)) - return Token::IllegalHex; + // can only return false if hex-byte is incomplete (only one hex digit instead of two) + return setError(ScannerError::IllegalHexString); addLiteralChar(c); } + if (m_char != quote) - return Token::IllegalHex; + return setError(ScannerError::IllegalStringEndQuote); + literal.complete(); advance(); // consume quote return Token::StringLiteral; @@ -754,8 +808,9 @@ void Scanner::scanDecimalDigits() return; // May continue with decimal digit or underscore for grouping. - do addLiteralCharAndAdvance(); - while (!m_source.isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_')); + do + addLiteralCharAndAdvance(); + while (!m_source->isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_')); // Defer further validation of underscore to SyntaxChecker. } @@ -769,7 +824,7 @@ Token Scanner::scanNumber(char _charSeen) // we have already seen a decimal point of the float addLiteralChar('.'); if (m_char == '_') - return Token::Illegal; + return setError(ScannerError::IllegalToken); scanDecimalDigits(); // we know we have at least one digit } else @@ -786,14 +841,14 @@ Token Scanner::scanNumber(char _charSeen) kind = HEX; addLiteralCharAndAdvance(); if (!isHexDigit(m_char)) - return Token::Illegal; // we must have at least one hex digit after 'x' + return setError(ScannerError::IllegalHexDigit); // we must have at least one hex digit after 'x' while (isHexDigit(m_char) || m_char == '_') // We keep the underscores for later validation addLiteralCharAndAdvance(); } else if (isDecimalDigit(m_char)) // We do not allow octal numbers - return Token::Illegal; + return setError(ScannerError::OctalNotAllowed); } // Parse decimal digits and allow trailing fractional part. if (kind == DECIMAL) @@ -801,7 +856,7 @@ Token Scanner::scanNumber(char _charSeen) scanDecimalDigits(); // optional if (m_char == '.') { - if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') + if (!m_source->isPastEndOfInput(1) && m_source->get(1) == '_') { // Assume the input may be a floating point number with leading '_' in fraction part. // Recover by consuming it all but returning `Illegal` right away. @@ -809,7 +864,7 @@ Token Scanner::scanNumber(char _charSeen) addLiteralCharAndAdvance(); // '_' scanDecimalDigits(); } - if (m_source.isPastEndOfInput() || !isDecimalDigit(m_source.get(1))) + if (m_source->isPastEndOfInput() || !isDecimalDigit(m_source->get(1))) { // A '.' has to be followed by a number. literal.complete(); @@ -825,8 +880,8 @@ Token Scanner::scanNumber(char _charSeen) { solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number"); if (kind != DECIMAL) - return Token::Illegal; - else if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_') + return setError(ScannerError::IllegalExponent); + else if (!m_source->isPastEndOfInput(1) && m_source->get(1) == '_') { // Recover from wrongly placed underscore as delimiter in literal with scientific // notation by consuming until the end. @@ -840,8 +895,8 @@ Token Scanner::scanNumber(char _charSeen) addLiteralCharAndAdvance(); // 'e' | 'E' if (m_char == '+' || m_char == '-') addLiteralCharAndAdvance(); - if (!isDecimalDigit(m_char)) - return Token::Illegal; // we must have at least one decimal digit after 'e'/'E' + if (!isDecimalDigit(m_char)) // we must have at least one decimal digit after 'e'/'E' + return setError(ScannerError::IllegalExponent); scanDecimalDigits(); } // The source character immediately following a numeric literal must @@ -849,7 +904,7 @@ Token Scanner::scanNumber(char _charSeen) // section 7.8.3, page 17 (note that we read only one decimal digit // if the value is 0). if (isDecimalDigit(m_char) || isIdentifierStart(m_char)) - return Token::Illegal; + return setError(ScannerError::IllegalNumberEnd); literal.complete(); return Token::Number; } @@ -860,61 +915,8 @@ tuple Scanner::scanIdentifierOrKeyword() LiteralScope literal(this, LITERAL_TYPE_STRING); addLiteralCharAndAdvance(); // Scan the rest of the identifier characters. - while (isIdentifierPart(m_char)) //get full literal + while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier)) addLiteralCharAndAdvance(); literal.complete(); return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal); } - -char CharStream::advanceAndGet(size_t _chars) -{ - if (isPastEndOfInput()) - return 0; - m_position += _chars; - if (isPastEndOfInput()) - return 0; - return m_source[m_position]; -} - -char CharStream::rollback(size_t _amount) -{ - solAssert(m_position >= _amount, ""); - m_position -= _amount; - return get(); -} - -string CharStream::lineAtPosition(int _position) const -{ - // if _position points to \n, it returns the line before the \n - using size_type = string::size_type; - size_type searchStart = min(m_source.size(), _position); - if (searchStart > 0) - searchStart--; - size_type lineStart = m_source.rfind('\n', searchStart); - if (lineStart == string::npos) - lineStart = 0; - else - lineStart++; - return m_source.substr(lineStart, min(m_source.find('\n', lineStart), - m_source.size()) - lineStart); -} - -tuple CharStream::translatePositionToLineColumn(int _position) const -{ - using size_type = string::size_type; - size_type searchPosition = min(m_source.size(), _position); - int lineNumber = count(m_source.begin(), m_source.begin() + searchPosition, '\n'); - size_type lineStart; - if (searchPosition == 0) - lineStart = 0; - else - { - lineStart = m_source.rfind('\n', searchPosition - 1); - lineStart = lineStart == string::npos ? 0 : lineStart + 1; - } - return tuple(lineNumber, searchPosition - lineStart); -} - - -} -} diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h new file mode 100644 index 000000000..3914b85ac --- /dev/null +++ b/liblangutil/Scanner.h @@ -0,0 +1,265 @@ +/* + * 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 . + * + * This file is derived from the file "scanner.h", which was part of the + * V8 project. The original copyright header follows: + * + * Copyright 2006-2012, the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * @author Christian + * @date 2014 + * Solidity scanner. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace langutil +{ + +class AstRawString; +class AstValueFactory; +class ParserRecorder; + +enum class ScannerError +{ + NoError, + + IllegalToken, + IllegalHexString, + IllegalHexDigit, + IllegalCommentTerminator, + IllegalEscapeSequence, + IllegalStringEndQuote, + IllegalNumberSeparator, + IllegalExponent, + IllegalNumberEnd, + + OctalNotAllowed, +}; + +std::string to_string(ScannerError _errorCode); +std::ostream& operator<<(std::ostream& os, ScannerError _errorCode); + +class Scanner +{ + friend class LiteralScope; +public: + explicit Scanner(std::shared_ptr _source) { reset(std::move(_source)); } + explicit Scanner(CharStream _source = CharStream()) { reset(std::move(_source)); } + + std::string const& source() const noexcept { return m_source->source(); } + + std::shared_ptr charStream() noexcept { return m_source; } + + /// Resets the scanner as if newly constructed with _source as input. + void reset(CharStream _source); + void reset(std::shared_ptr _source); + /// Resets scanner to the start of input. + void reset(); + + /// Enables or disables support for period in identifier. + /// This re-scans the current token and comment literal and thus invalidates it. + void supportPeriodInIdentifier(bool _value); + + /// @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 + + /// @returns the current token + Token currentToken() const + { + return m_currentToken.token; + } + ElementaryTypeNameToken currentElementaryTypeNameToken() const + { + unsigned firstSize; + unsigned secondSize; + std::tie(firstSize, secondSize) = m_currentToken.extendedTokenInfo; + return ElementaryTypeNameToken(m_currentToken.token, firstSize, secondSize); + } + + SourceLocation currentLocation() const { return m_currentToken.location; } + std::string const& currentLiteral() const { return m_currentToken.literal; } + std::tuple const& currentTokenInfo() const { return m_currentToken.extendedTokenInfo; } + + /// Retrieves the last error that occurred during lexical analysis. + /// @note If no error occurred, the value is undefined. + ScannerError currentError() const noexcept { return m_currentToken.error; } + ///@} + + ///@{ + ///@name Information about the current comment token + + SourceLocation currentCommentLocation() const { return m_skippedComment.location; } + std::string const& currentCommentLiteral() const { return m_skippedComment.literal; } + /// Called by the parser during FunctionDefinition parsing to clear the current comment + void clearCurrentCommentLiteral() { m_skippedComment.literal.clear(); } + + ///@} + + ///@{ + ///@name Information about the next token + + /// @returns the next token without advancing input. + Token peekNextToken() const { return m_nextToken.token; } + SourceLocation peekLocation() const { return m_nextToken.location; } + std::string const& peekLiteral() const { return m_nextToken.literal; } + ///@} + + ///@{ + ///@name Error printing helper functions + /// Functions that help pretty-printing parse errors + /// Do only use in error cases, they are quite expensive. + std::string lineAtPosition(int _position) const { return m_source->lineAtPosition(_position); } + std::tuple translatePositionToLineColumn(int _position) const { return m_source->translatePositionToLineColumn(_position); } + std::string sourceAt(SourceLocation const& _location) const + { + solAssert(!_location.isEmpty(), ""); + solAssert(m_source.get() == _location.source.get(), "CharStream memory locations must match."); + return m_source->source().substr(_location.start, _location.end - _location.start); + } + ///@} + +private: + inline Token setError(ScannerError _error) noexcept + { + m_nextToken.error = _error; + return Token::Illegal; + } + + /// Used for the current and look-ahead token and comments + struct TokenDesc + { + Token token; + SourceLocation location; + std::string literal; + ScannerError error = ScannerError::NoError; + std::tuple extendedTokenInfo; + }; + + ///@{ + ///@name Literal buffer support + inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); } + inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); } + inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } + void addUnicodeAsUTF8(unsigned codepoint); + ///@} + + bool advance() { m_char = m_source->advanceAndGet(); return !m_source->isPastEndOfInput(); } + void rollback(int _amount) { m_char = m_source->rollback(_amount); } + /// Rolls back to the start of the current token and re-runs the scanner. + void rescan(); + + inline Token selectErrorToken(ScannerError _err) { advance(); return setError(_err); } + inline Token selectToken(Token _tok) { advance(); return _tok; } + /// If the next character is _next, advance and return _then, otherwise return _else. + inline Token selectToken(char _next, Token _then, Token _else); + + bool scanHexByte(char& o_scannedByte); + boost::optional scanUnicode(); + + /// Scans a single Solidity token. + void scanToken(); + + /// Skips all whitespace and @returns true if something was skipped. + bool skipWhitespace(); + /// Skips all whitespace that are neither '\r' nor '\n'. + void skipWhitespaceExceptUnicodeLinebreak(); + Token skipSingleLineComment(); + Token skipMultiLineComment(); + + /// Tests if current source position is CR, LF or CRLF. + bool atEndOfLine() const; + + /// Tries to consume CR, LF or CRLF line terminators and returns success or failure. + bool tryScanEndOfLine(); + + void scanDecimalDigits(); + Token scanNumber(char _charSeen = 0); + std::tuple scanIdentifierOrKeyword(); + + Token scanString(); + Token scanHexString(); + Token scanSingleLineDocComment(); + Token scanMultiLineDocComment(); + /// Scans a slash '/' and depending on the characters returns the appropriate token + Token scanSlash(); + + /// Scans an escape-sequence which is part of a string and adds the + /// decoded character to the current literal. Returns true if a pattern + /// is scanned. + bool scanEscape(); + + /// @returns true iff we are currently positioned at a unicode line break. + bool isUnicodeLinebreak(); + + /// Return the current source position. + int sourcePos() const { return m_source->position(); } + bool isSourcePastEndOfInput() const { return m_source->isPastEndOfInput(); } + + bool m_supportPeriodInIdentifier = false; + + TokenDesc m_skippedComment; // desc for current skipped comment + TokenDesc m_nextSkippedComment; // desc for next skipped comment + + TokenDesc m_currentToken; // desc for current token (as returned by Next()) + TokenDesc m_nextToken; // desc for next token (one token look-ahead) + + std::shared_ptr m_source; + + /// one character look-ahead, equals 0 at end of input + char m_char; +}; + +} diff --git a/libsolidity/analysis/SemVerHandler.cpp b/liblangutil/SemVerHandler.cpp similarity index 98% rename from libsolidity/analysis/SemVerHandler.cpp rename to liblangutil/SemVerHandler.cpp index 64fa17b3d..378923420 100644 --- a/libsolidity/analysis/SemVerHandler.cpp +++ b/liblangutil/SemVerHandler.cpp @@ -20,12 +20,13 @@ * Utilities to handle semantic versioning. */ -#include +#include + #include using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace langutil; SemVerVersion::SemVerVersion(string const& _versionString) { diff --git a/libsolidity/analysis/SemVerHandler.h b/liblangutil/SemVerHandler.h similarity index 97% rename from libsolidity/analysis/SemVerHandler.h rename to liblangutil/SemVerHandler.h index 03a557c56..09e6c6f4a 100644 --- a/libsolidity/analysis/SemVerHandler.h +++ b/liblangutil/SemVerHandler.h @@ -22,12 +22,11 @@ #pragma once +#include +#include #include -#include -namespace dev -{ -namespace solidity +namespace langutil { class SemVerError: dev::Exception @@ -108,4 +107,3 @@ class SemVerMatchExpressionParser }; } -} diff --git a/liblangutil/SourceLocation.h b/liblangutil/SourceLocation.h new file mode 100644 index 000000000..7aafd03da --- /dev/null +++ b/liblangutil/SourceLocation.h @@ -0,0 +1,125 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author Lefteris Karapetsas + * @date 2015 + * Represents a location in a source file + */ + +#pragma once + +#include +#include // defines noexcept macro for MSVC +#include +#include +#include +#include +#include +#include + +namespace langutil +{ +struct SourceLocationError: virtual dev::Exception {}; + +/** + * Representation of an interval of source positions. + * The interval includes start and excludes end. + */ +struct SourceLocation +{ + bool operator==(SourceLocation const& _other) const + { + return source.get() == _other.source.get() && start == _other.start && end == _other.end; + } + bool operator!=(SourceLocation const& _other) const { return !operator==(_other); } + inline bool operator<(SourceLocation const& _other) const; + inline bool contains(SourceLocation const& _other) const; + inline bool intersects(SourceLocation const& _other) const; + + bool isEmpty() const { return start == -1 && end == -1; } + + std::string text() const + { + assertThrow(source, SourceLocationError, "Requested text from null source."); + assertThrow(!isEmpty(), SourceLocationError, "Requested text from empty source location."); + assertThrow(start <= end, SourceLocationError, "Invalid source location."); + assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location."); + return source->source().substr(start, end - start); + } + + /// @returns the smallest SourceLocation that contains both @param _a and @param _b. + /// Assumes that @param _a and @param _b refer to the same source (exception: if the source of either one + /// is unset, the source of the other will be used for the result, even if that is unset as well). + /// Invalid start and end positions (with value of -1) are ignored (if start or end are -1 for both @param _a and + /// @param _b, then start resp. end of the result will be -1 as well). + static SourceLocation smallestCovering(SourceLocation _a, SourceLocation const& _b) + { + if (!_a.source) + _a.source = _b.source; + + if (_a.start < 0) + _a.start = _b.start; + else if (_b.start >= 0 && _b.start < _a.start) + _a.start = _b.start; + if (_b.end > _a.end) + _a.end = _b.end; + + return _a; + } + + int start = -1; + int end = -1; + std::shared_ptr source; +}; + +/// Stream output for Location (used e.g. in boost exceptions). +inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) +{ + if (_location.isEmpty()) + return _out << "NO_LOCATION_SPECIFIED"; + + if (_location.source) + _out << _location.source->name(); + + _out << "[" << _location.start << "," << _location.end << ")"; + + return _out; +} + +bool SourceLocation::operator<(SourceLocation const& _other) const +{ + if (!source|| !_other.source) + return std::make_tuple(int(!!source), start, end) < std::make_tuple(int(!!_other.source), _other.start, _other.end); + else + return std::make_tuple(source->name(), start, end) < std::make_tuple(_other.source->name(), _other.start, _other.end); +} + +bool SourceLocation::contains(SourceLocation const& _other) const +{ + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return start <= _other.start && _other.end <= end; +} + +bool SourceLocation::intersects(SourceLocation const& _other) const +{ + if (isEmpty() || _other.isEmpty() || source.get() != _other.source.get()) + return false; + return _other.start < end && start < _other.end; +} + +} diff --git a/liblangutil/SourceReferenceExtractor.cpp b/liblangutil/SourceReferenceExtractor.cpp new file mode 100644 index 000000000..1a6dbdb31 --- /dev/null +++ b/liblangutil/SourceReferenceExtractor.cpp @@ -0,0 +1,91 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +#include +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace langutil; + +SourceReferenceExtractor::Message SourceReferenceExtractor::extract(Exception const& _exception, string _category) +{ + SourceLocation const* location = boost::get_error_info(_exception); + + string const* message = boost::get_error_info(_exception); + SourceReference primary = extract(location, message ? *message : ""); + + std::vector secondary; + auto secondaryLocation = boost::get_error_info(_exception); + if (secondaryLocation && !secondaryLocation->infos.empty()) + for (auto const& info: secondaryLocation->infos) + secondary.emplace_back(extract(&info.second, info.first)); + + return Message{std::move(primary), _category, std::move(secondary)}; +} + +SourceReference SourceReferenceExtractor::extract(SourceLocation const* _location, std::string message) +{ + if (!_location || !_location->source.get()) // Nothing we can extract here + return SourceReference::MessageOnly(std::move(message)); + + shared_ptr const& source = _location->source; + + LineColumn const interest = source->translatePositionToLineColumn(_location->start); + LineColumn start = interest; + LineColumn end = source->translatePositionToLineColumn(_location->end); + bool const isMultiline = start.line != end.line; + + string line = source->lineAtPosition(_location->start); + + int locationLength = isMultiline ? line.length() - start.column : end.column - start.column; + if (locationLength > 150) + { + int const lhs = start.column + 35; + int const rhs = (isMultiline ? line.length() : end.column) - 35; + line = line.substr(0, lhs) + " ... " + line.substr(rhs); + end.column = start.column + 75; + locationLength = 75; + } + + if (line.length() > 150) + { + int const len = line.length(); + line = line.substr(max(0, start.column - 35), min(start.column, 35) + min(locationLength + 35, len - start.column)); + if (start.column + locationLength + 35 < len) + line += " ..."; + if (start.column > 35) + { + line = " ... " + line; + start.column = 40; + } + end.column = start.column + locationLength; + } + + return SourceReference{ + std::move(message), + source->name(), + interest, + isMultiline, + line, + start.column, + end.column, + }; +} diff --git a/liblangutil/SourceReferenceExtractor.h b/liblangutil/SourceReferenceExtractor.h new file mode 100644 index 000000000..bcbc50bc8 --- /dev/null +++ b/liblangutil/SourceReferenceExtractor.h @@ -0,0 +1,75 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +#pragma once + +#include +#include +#include +#include + +namespace dev +{ +struct Exception; +} + +namespace langutil +{ + +struct LineColumn +{ + int line = {-1}; + int column = {-1}; + + LineColumn() = default; + LineColumn(std::tuple const& _t): line{std::get<0>(_t)}, column{std::get<1>(_t)} {} +}; + +struct SourceReference +{ + std::string message; ///< A message that relates to this source reference (such as a warning or an error message). + std::string sourceName; ///< Underlying source name (for example the filename). + LineColumn position; ///< Actual (error) position this source reference is surrounding. + bool multiline = {false}; ///< Indicates whether the actual SourceReference is truncated to one line. + std::string text; ///< Extracted source code text (potentially truncated if multiline or too long). + int startColumn = {-1}; ///< Highlighting range-start of text field. + int endColumn = {-1}; ///< Highlighting range-end of text field. + + /// Constructs a SourceReference containing a message only. + static SourceReference MessageOnly(std::string _msg) + { + SourceReference sref; + sref.message = std::move(_msg); + return sref; + } +}; + +struct SourceLocation; + +namespace SourceReferenceExtractor +{ + struct Message + { + SourceReference primary; + std::string category; // "Error", "Warning", ... + std::vector secondary; + }; + + Message extract(dev::Exception const& _exception, std::string _category); + SourceReference extract(SourceLocation const* _location, std::string message = ""); +} + +} diff --git a/liblangutil/SourceReferenceFormatter.cpp b/liblangutil/SourceReferenceFormatter.cpp new file mode 100644 index 000000000..01c1dd5ff --- /dev/null +++ b/liblangutil/SourceReferenceFormatter.cpp @@ -0,0 +1,100 @@ +/* + 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 Christian + * @date 2014 + * Formatting functions for errors referencing positions and locations in the source. + */ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace langutil; + +void SourceReferenceFormatter::printSourceLocation(SourceLocation const* _location) +{ + printSourceLocation(SourceReferenceExtractor::extract(_location)); +} + +void SourceReferenceFormatter::printSourceLocation(SourceReference const& _ref) +{ + if (_ref.position.line < 0) + return; // Nothing we can print here + + if (!_ref.multiline) + { + m_stream << _ref.text << endl; + + // mark the text-range like this: ^-----^ + for_each( + _ref.text.cbegin(), + _ref.text.cbegin() + _ref.startColumn, + [this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); } + ); + m_stream << "^"; + if (_ref.endColumn > _ref.startColumn + 2) + m_stream << string(_ref.endColumn - _ref.startColumn - 2, '-'); + if (_ref.endColumn > _ref.startColumn + 1) + m_stream << "^"; + m_stream << endl; + } + else + m_stream << + _ref.text << + endl << + string(_ref.startColumn, ' ') << + "^ (Relevant source part starts here and spans across multiple lines)." << + endl; +} + +void SourceReferenceFormatter::printSourceName(SourceReference const& _ref) +{ + if (_ref.position.line != -1) + m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": "; +} + +void SourceReferenceFormatter::printExceptionInformation(dev::Exception const& _exception, std::string const& _category) +{ + printExceptionInformation(SourceReferenceExtractor::extract(_exception, _category)); +} + +void SourceReferenceFormatter::printErrorInformation(Error const& _error) +{ + printExceptionInformation( + _error, + (_error.type() == Error::Type::Warning) ? "Warning" : "Error" + ); +} + +void SourceReferenceFormatter::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) +{ + printSourceName(_msg.primary); + + m_stream << _msg.category << ": " << _msg.primary.message << endl; + + printSourceLocation(_msg.primary); + + for (auto const& ref: _msg.secondary) + { + printSourceName(ref); + m_stream << ref.message << endl; + printSourceLocation(ref); + } +} diff --git a/libsolidity/interface/SourceReferenceFormatter.h b/liblangutil/SourceReferenceFormatter.h similarity index 55% rename from libsolidity/interface/SourceReferenceFormatter.h rename to liblangutil/SourceReferenceFormatter.h index a32babdce..fee223088 100644 --- a/libsolidity/interface/SourceReferenceFormatter.h +++ b/liblangutil/SourceReferenceFormatter.h @@ -25,55 +25,61 @@ #include #include #include -#include +#include +#include namespace dev { - struct Exception; // forward +} -namespace solidity +namespace langutil { - -class Scanner; // forward -class CompilerStack; // forward +struct SourceLocation; +class Scanner; class SourceReferenceFormatter { public: - using ScannerFromSourceNameFun = std::function; - - explicit SourceReferenceFormatter( - std::ostream& _stream, - ScannerFromSourceNameFun _scannerFromSourceName - ): - m_stream(_stream), - m_scannerFromSourceName(std::move(_scannerFromSourceName)) + explicit SourceReferenceFormatter(std::ostream& _stream): + m_stream(_stream) {} + virtual ~SourceReferenceFormatter() = default; + /// Prints source location if it is given. - void printSourceLocation(SourceLocation const* _location); - void printExceptionInformation(Exception const& _exception, std::string const& _name); + virtual void printSourceLocation(SourceReference const& _ref); + virtual void printExceptionInformation(SourceReferenceExtractor::Message const& _msg); + + virtual void printSourceLocation(SourceLocation const* _location); + virtual void printExceptionInformation(dev::Exception const& _exception, std::string const& _category); + virtual void printErrorInformation(Error const& _error); + + static std::string formatErrorInformation(Error const& _error) + { + return formatExceptionInformation( + _error, + (_error.type() == Error::Type::Warning) ? "Warning" : "Error" + ); + } static std::string formatExceptionInformation( - Exception const& _exception, - std::string const& _name, - ScannerFromSourceNameFun const& _scannerFromSourceName + dev::Exception const& _exception, + std::string const& _name ) { std::ostringstream errorOutput; - SourceReferenceFormatter formatter(errorOutput, _scannerFromSourceName); + SourceReferenceFormatter formatter(errorOutput); formatter.printExceptionInformation(_exception, _name); return errorOutput.str(); } -private: + +protected: /// Prints source name if location is given. - void printSourceName(SourceLocation const* _location); + void printSourceName(SourceReference const& _ref); std::ostream& m_stream; - ScannerFromSourceNameFun m_scannerFromSourceName; }; } -} diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp new file mode 100644 index 000000000..de288df91 --- /dev/null +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -0,0 +1,136 @@ +/* + 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 . +*/ +/** + * Formatting functions for errors referencing positions and locations in the source. + */ + +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::formatting; +using namespace langutil; + +AnsiColorized SourceReferenceFormatterHuman::normalColored() const +{ + return AnsiColorized(m_stream, m_colored, {WHITE}); +} + +AnsiColorized SourceReferenceFormatterHuman::frameColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, BLUE}); +} + +AnsiColorized SourceReferenceFormatterHuman::errorColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, RED}); +} + +AnsiColorized SourceReferenceFormatterHuman::messageColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, WHITE}); +} + +AnsiColorized SourceReferenceFormatterHuman::secondaryColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, CYAN}); +} + +AnsiColorized SourceReferenceFormatterHuman::highlightColored() const +{ + return AnsiColorized(m_stream, m_colored, {YELLOW}); +} + +AnsiColorized SourceReferenceFormatterHuman::diagColored() const +{ + return AnsiColorized(m_stream, m_colored, {BOLD, YELLOW}); +} + +void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref) +{ + if (_ref.position.line < 0) + return; // Nothing we can print here + + int const leftpad = static_cast(log10(max(_ref.position.line, 1))) + 1; + + // line 0: source name + frameColored() << string(leftpad, ' ') << "--> "; + m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n'; + + if (!_ref.multiline) + { + int const locationLength = _ref.endColumn - _ref.startColumn; + + // line 1: + m_stream << string(leftpad, ' '); + frameColored() << " |" << '\n'; + + // line 2: + frameColored() << (_ref.position.line + 1) << " | "; + m_stream << _ref.text.substr(0, _ref.startColumn); + highlightColored() << _ref.text.substr(_ref.startColumn, locationLength); + m_stream << _ref.text.substr(_ref.endColumn) << '\n'; + + // line 3: + m_stream << string(leftpad, ' '); + frameColored() << " | "; + for_each( + _ref.text.cbegin(), + _ref.text.cbegin() + _ref.startColumn, + [this](char ch) { m_stream << (ch == '\t' ? '\t' : ' '); } + ); + diagColored() << string(locationLength, '^') << '\n'; + } + else + { + // line 1: + m_stream << string(leftpad, ' '); + frameColored() << " |" << '\n'; + + // line 2: + frameColored() << (_ref.position.line + 1) << " | "; + m_stream << _ref.text.substr(0, _ref.startColumn); + highlightColored() << _ref.text.substr(_ref.startColumn) << '\n'; + + // line 3: + frameColored() << string(leftpad, ' ') << " | "; + m_stream << string(_ref.startColumn, ' '); + diagColored() << "^ (Relevant source part starts here and spans across multiple lines).\n"; + } +} + +void SourceReferenceFormatterHuman::printExceptionInformation(SourceReferenceExtractor::Message const& _msg) +{ + // exception header line + errorColored() << _msg.category; + messageColored() << ": " << _msg.primary.message << '\n'; + + printSourceLocation(_msg.primary); + + for (auto const& secondary: _msg.secondary) + { + secondaryColored() << "Note"; + messageColored() << ": " << secondary.message << '\n'; + printSourceLocation(secondary); + } + + m_stream << '\n'; +} diff --git a/liblangutil/SourceReferenceFormatterHuman.h b/liblangutil/SourceReferenceFormatterHuman.h new file mode 100644 index 000000000..a5e803894 --- /dev/null +++ b/liblangutil/SourceReferenceFormatterHuman.h @@ -0,0 +1,80 @@ +/* + 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 . +*/ +/** + * Formatting functions for errors referencing positions and locations in the source. + */ + +#pragma once + +#include +#include // SourceReferenceFormatterBase + +#include + +#include +#include +#include + +namespace dev +{ +struct Exception; // forward +} + +namespace langutil +{ + +struct SourceLocation; +struct SourceReference; + +class SourceReferenceFormatterHuman: public SourceReferenceFormatter +{ +public: + SourceReferenceFormatterHuman(std::ostream& _stream, bool colored): + SourceReferenceFormatter{_stream}, m_colored{colored} + {} + + void printSourceLocation(SourceReference const& _ref) override; + void printExceptionInformation(SourceReferenceExtractor::Message const& _msg) override; + using SourceReferenceFormatter::printExceptionInformation; + + static std::string formatExceptionInformation( + dev::Exception const& _exception, + std::string const& _name, + bool colored = false + ) + { + std::ostringstream errorOutput; + + SourceReferenceFormatterHuman formatter(errorOutput, colored); + formatter.printExceptionInformation(_exception, _name); + return errorOutput.str(); + } + +private: + dev::AnsiColorized normalColored() const; + dev::AnsiColorized frameColored() const; + dev::AnsiColorized errorColored() const; + dev::AnsiColorized messageColored() const; + dev::AnsiColorized secondaryColored() const; + dev::AnsiColorized highlightColored() const; + dev::AnsiColorized diagColored() const; + +private: + bool m_colored; +}; + +} diff --git a/libsolidity/parsing/Token.cpp b/liblangutil/Token.cpp similarity index 97% rename from libsolidity/parsing/Token.cpp rename to liblangutil/Token.cpp index b949d27b8..a17af9cd6 100644 --- a/libsolidity/parsing/Token.cpp +++ b/liblangutil/Token.cpp @@ -40,16 +40,13 @@ // You should have received a copy of the GNU General Public License // along with solidity. If not, see . -#include -#include -#include +#include #include +#include using namespace std; -namespace dev -{ -namespace solidity +namespace langutil { void ElementaryTypeNameToken::assertDetails(Token _baseType, unsigned const& _first, unsigned const& _second) @@ -146,7 +143,7 @@ static Token keywordByName(string const& _name) // and keywords to be put inside the keywords variable. #define KEYWORD(name, string, precedence) {string, Token::name}, #define TOKEN(name, string, precedence) - static const map keywords({TOKEN_LIST(TOKEN, KEYWORD)}); + static map const keywords({TOKEN_LIST(TOKEN, KEYWORD)}); #undef KEYWORD #undef TOKEN auto it = keywords.find(_name); @@ -205,4 +202,3 @@ tuple fromIdentifierOrKeyword(string const& _ } } -} diff --git a/liblangutil/Token.h b/liblangutil/Token.h new file mode 100644 index 000000000..35d4f76f3 --- /dev/null +++ b/liblangutil/Token.h @@ -0,0 +1,378 @@ +// Copyright 2006-2012, the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Modifications as part of solidity under the following license: +// +// solidity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// solidity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with solidity. If not, see . + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace langutil +{ + +// TOKEN_LIST takes a list of 3 macros M, all of which satisfy the +// same signature M(name, string, precedence), where name is the +// symbolic token name, string is the corresponding syntactic symbol +// (or nullptr, for literals), and precedence is the precedence (or 0). +// The parameters are invoked for token categories as follows: +// +// T: Non-keyword tokens +// K: Keyword tokens + +// IGNORE_TOKEN is a convenience macro that can be supplied as +// an argument (at any position) for a TOKEN_LIST call. It does +// nothing with tokens belonging to the respective category. + +#define IGNORE_TOKEN(name, string, precedence) + +#define TOKEN_LIST(T, K) \ + /* End of source indicator. */ \ + T(EOS, "EOS", 0) \ + \ + /* Punctuators (ECMA-262, section 7.7, page 15). */ \ + T(LParen, "(", 0) \ + T(RParen, ")", 0) \ + T(LBrack, "[", 0) \ + T(RBrack, "]", 0) \ + T(LBrace, "{", 0) \ + T(RBrace, "}", 0) \ + T(Colon, ":", 0) \ + T(Semicolon, ";", 0) \ + T(Period, ".", 0) \ + T(Conditional, "?", 3) \ + T(Arrow, "=>", 0) \ + \ + /* Assignment operators. */ \ + /* IsAssignmentOp() relies on this block of enum values being */ \ + /* contiguous and sorted in the same order!*/ \ + T(Assign, "=", 2) \ + /* The following have to be in exactly the same order as the simple binary operators*/ \ + T(AssignBitOr, "|=", 2) \ + T(AssignBitXor, "^=", 2) \ + T(AssignBitAnd, "&=", 2) \ + T(AssignShl, "<<=", 2) \ + T(AssignSar, ">>=", 2) \ + T(AssignShr, ">>>=", 2) \ + T(AssignAdd, "+=", 2) \ + T(AssignSub, "-=", 2) \ + T(AssignMul, "*=", 2) \ + T(AssignDiv, "/=", 2) \ + T(AssignMod, "%=", 2) \ + \ + /* Binary operators sorted by precedence. */ \ + /* IsBinaryOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(Comma, ",", 1) \ + T(Or, "||", 4) \ + T(And, "&&", 5) \ + T(BitOr, "|", 8) \ + T(BitXor, "^", 9) \ + T(BitAnd, "&", 10) \ + T(SHL, "<<", 11) \ + T(SAR, ">>", 11) \ + T(SHR, ">>>", 11) \ + T(Add, "+", 12) \ + T(Sub, "-", 12) \ + T(Mul, "*", 13) \ + T(Div, "/", 13) \ + T(Mod, "%", 13) \ + T(Exp, "**", 14) \ + \ + /* Compare operators sorted by precedence. */ \ + /* IsCompareOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(Equal, "==", 6) \ + T(NotEqual, "!=", 6) \ + T(LessThan, "<", 7) \ + T(GreaterThan, ">", 7) \ + T(LessThanOrEqual, "<=", 7) \ + T(GreaterThanOrEqual, ">=", 7) \ + \ + /* Unary operators. */ \ + /* IsUnaryOp() relies on this block of enum values */ \ + /* being contiguous and sorted in the same order! */ \ + T(Not, "!", 0) \ + T(BitNot, "~", 0) \ + T(Inc, "++", 0) \ + T(Dec, "--", 0) \ + K(Delete, "delete", 0) \ + \ + /* Inline Assembly Operators */ \ + T(AssemblyAssign, ":=", 2) \ + /* Keywords */ \ + K(Anonymous, "anonymous", 0) \ + K(As, "as", 0) \ + K(Assembly, "assembly", 0) \ + K(Break, "break", 0) \ + K(Constant, "constant", 0) \ + K(Constructor, "constructor", 0) \ + K(Continue, "continue", 0) \ + K(Contract, "contract", 0) \ + K(Do, "do", 0) \ + K(Else, "else", 0) \ + K(Enum, "enum", 0) \ + K(Emit, "emit", 0) \ + K(Event, "event", 0) \ + K(External, "external", 0) \ + K(For, "for", 0) \ + K(Function, "function", 0) \ + K(Hex, "hex", 0) \ + K(If, "if", 0) \ + K(Indexed, "indexed", 0) \ + K(Interface, "interface", 0) \ + K(Internal, "internal", 0) \ + K(Import, "import", 0) \ + K(Is, "is", 0) \ + K(Library, "library", 0) \ + K(Mapping, "mapping", 0) \ + K(Memory, "memory", 0) \ + K(Modifier, "modifier", 0) \ + K(New, "new", 0) \ + K(Payable, "payable", 0) \ + K(Public, "public", 0) \ + K(Pragma, "pragma", 0) \ + K(Private, "private", 0) \ + K(Pure, "pure", 0) \ + K(Return, "return", 0) \ + K(Returns, "returns", 0) \ + K(Storage, "storage", 0) \ + K(CallData, "calldata", 0) \ + K(Struct, "struct", 0) \ + K(Throw, "throw", 0) \ + K(Type, "type", 0) \ + K(Using, "using", 0) \ + K(Var, "var", 0) \ + K(View, "view", 0) \ + K(While, "while", 0) \ + \ + /* Ether subdenominations */ \ + K(SubWei, "wei", 0) \ + K(SubSzabo, "szabo", 0) \ + K(SubFinney, "finney", 0) \ + K(SubEther, "ether", 0) \ + K(SubSecond, "seconds", 0) \ + K(SubMinute, "minutes", 0) \ + K(SubHour, "hours", 0) \ + K(SubDay, "days", 0) \ + K(SubWeek, "weeks", 0) \ + K(SubYear, "years", 0) \ + /* type keywords*/ \ + K(Int, "int", 0) \ + K(UInt, "uint", 0) \ + K(Bytes, "bytes", 0) \ + K(Byte, "byte", 0) \ + K(String, "string", 0) \ + K(Address, "address", 0) \ + K(Bool, "bool", 0) \ + K(Fixed, "fixed", 0) \ + K(UFixed, "ufixed", 0) \ + T(IntM, "intM", 0) \ + T(UIntM, "uintM", 0) \ + T(BytesM, "bytesM", 0) \ + T(FixedMxN, "fixedMxN", 0) \ + T(UFixedMxN, "ufixedMxN", 0) \ + T(TypesEnd, nullptr, 0) /* used as type enum end marker */ \ + \ + /* Literals */ \ + K(TrueLiteral, "true", 0) \ + K(FalseLiteral, "false", 0) \ + T(Number, nullptr, 0) \ + T(StringLiteral, nullptr, 0) \ + T(CommentLiteral, nullptr, 0) \ + \ + /* Identifiers (not keywords or future reserved words). */ \ + T(Identifier, nullptr, 0) \ + \ + /* Keywords reserved for future use. */ \ + K(Abstract, "abstract", 0) \ + K(After, "after", 0) \ + K(Alias, "alias", 0) \ + K(Apply, "apply", 0) \ + K(Auto, "auto", 0) \ + K(Case, "case", 0) \ + K(Catch, "catch", 0) \ + K(CopyOf, "copyof", 0) \ + K(Default, "default", 0) \ + K(Define, "define", 0) \ + K(Final, "final", 0) \ + K(Immutable, "immutable", 0) \ + K(Implements, "implements", 0) \ + K(In, "in", 0) \ + K(Inline, "inline", 0) \ + K(Let, "let", 0) \ + K(Macro, "macro", 0) \ + K(Match, "match", 0) \ + K(Mutable, "mutable", 0) \ + K(NullLiteral, "null", 0) \ + K(Of, "of", 0) \ + K(Override, "override", 0) \ + K(Partial, "partial", 0) \ + K(Promise, "promise", 0) \ + K(Reference, "reference", 0) \ + K(Relocatable, "relocatable", 0) \ + K(Sealed, "sealed", 0) \ + K(Sizeof, "sizeof", 0) \ + K(Static, "static", 0) \ + K(Supports, "supports", 0) \ + K(Switch, "switch", 0) \ + K(Try, "try", 0) \ + K(Typedef, "typedef", 0) \ + K(TypeOf, "typeof", 0) \ + K(Unchecked, "unchecked", 0) \ + \ + /* Illegal token - not able to scan. */ \ + T(Illegal, "ILLEGAL", 0) \ + \ + /* Scanner-internal use only. */ \ + T(Whitespace, nullptr, 0) + +// All token values. +// attention! msvc issue: +// http://stackoverflow.com/questions/9567868/compile-errors-after-adding-v8-to-my-project-c2143-c2059 +// @todo: avoid TOKEN_LIST macro +enum class Token : unsigned int { +#define T(name, string, precedence) name, + TOKEN_LIST(T, T) + NUM_TOKENS +#undef T +}; + +namespace TokenTraits +{ + constexpr size_t count() { return static_cast(Token::NUM_TOKENS); } + + // Predicates + constexpr bool isElementaryTypeName(Token tok) { return Token::Int <= tok && tok < Token::TypesEnd; } + constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; } + constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; } + constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd || + op == Token::Add || op == Token::Mul || op == Token::Equal || op == Token::NotEqual; } + constexpr bool isArithmeticOp(Token op) { return Token::Add <= op && op <= Token::Exp; } + constexpr bool isCompareOp(Token op) { return Token::Equal <= op && op <= Token::GreaterThanOrEqual; } + + constexpr bool isBitOp(Token op) { return (Token::BitOr <= op && op <= Token::BitAnd) || op == Token::BitNot; } + constexpr bool isBooleanOp(Token op) { return (Token::Or <= op && op <= Token::And) || op == Token::Not; } + constexpr bool isUnaryOp(Token op) { return (Token::Not <= op && op <= Token::Delete) || op == Token::Add || op == Token::Sub; } + constexpr bool isCountOp(Token op) { return op == Token::Inc || op == Token::Dec; } + constexpr bool isShiftOp(Token op) { return (Token::SHL <= op) && (op <= Token::SHR); } + constexpr bool isVariableVisibilitySpecifier(Token op) { return op == Token::Public || op == Token::Private || op == Token::Internal; } + constexpr bool isVisibilitySpecifier(Token op) { return isVariableVisibilitySpecifier(op) || op == Token::External; } + constexpr bool isLocationSpecifier(Token op) { return op == Token::Memory || op == Token::Storage || op == Token::CallData; } + + constexpr bool isStateMutabilitySpecifier(Token op, bool _allowConstant = true) + { + return (op == Token::Constant && _allowConstant) + || op == Token::Pure || op == Token::View || op == Token::Payable; + } + + constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; } + constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; } + constexpr bool isReservedKeyword(Token op) { return (Token::Abstract <= op && op <= Token::Unchecked); } + + inline Token AssignmentToBinaryOp(Token op) + { + solAssert(isAssignmentOp(op) && op != Token::Assign, ""); + return static_cast(static_cast(op) + (static_cast(Token::BitOr) - static_cast(Token::AssignBitOr))); + } + + // @returns the precedence > 0 for binary and compare + // operators; returns 0 otherwise. + int precedence(Token tok); + + std::tuple fromIdentifierOrKeyword(std::string const& _literal); + + // @returns a string corresponding to the C++ token name + // (e.g. "LT" for the token LT). + char const* name(Token tok); + + // @returns a string corresponding to the JS token string + // (.e., "<" for the token LT) or nullptr if the token doesn't + // have a (unique) string (e.g. an IDENTIFIER). + char const* toString(Token tok); + + std::string friendlyName(Token tok); +} + +inline std::ostream& operator<<(std::ostream& os, Token token) +{ + os << TokenTraits::friendlyName(token); + return os; +} + +class ElementaryTypeNameToken +{ +public: + ElementaryTypeNameToken(Token _token, unsigned const& _firstNumber, unsigned const& _secondNumber) + { + assertDetails(_token, _firstNumber, _secondNumber); + } + + unsigned int firstNumber() const { return m_firstNumber; } + unsigned int secondNumber() const { return m_secondNumber; } + Token token() const { return m_token; } + + ///if tokValue is set to true, then returns the actual token type name, otherwise, returns full type + std::string toString(bool const& tokenValue = false) const + { + std::string name = TokenTraits::toString(m_token); + if (tokenValue || (firstNumber() == 0 && secondNumber() == 0)) + return name; + solAssert(name.size() >= 3, "Token name size should be greater than 3. Should not reach here."); + if (m_token == Token::FixedMxN || m_token == Token::UFixedMxN) + return name.substr(0, name.size() - 3) + std::to_string(m_firstNumber) + "x" + std::to_string(m_secondNumber); + else + return name.substr(0, name.size() - 1) + std::to_string(m_firstNumber); + } + +private: + Token m_token; + unsigned int m_firstNumber; + unsigned int m_secondNumber; + /// throws if type is not properly sized + void assertDetails(Token _baseType, unsigned const& _first, unsigned const& _second); +}; + +} diff --git a/libsolidity/parsing/UndefMacros.h b/liblangutil/UndefMacros.h similarity index 100% rename from libsolidity/parsing/UndefMacros.h rename to liblangutil/UndefMacros.h diff --git a/liblityc/liblityc.cpp b/liblityc/liblityc.cpp index 3ff2afba0..c78b185ca 100644 --- a/liblityc/liblityc.cpp +++ b/liblityc/liblityc.cpp @@ -21,10 +21,11 @@ */ #include -#include -#include #include #include +#include +#include +#include #include @@ -72,10 +73,10 @@ ReadCallback::Callback wrapReadCallback(CStyleReadFileCallback _readCallback = n return readCallback; } -string compile(string const& _input, CStyleReadFileCallback _readCallback = nullptr) +string compile(string _input, CStyleReadFileCallback _readCallback = nullptr) { StandardCompiler compiler(wrapReadCallback(_readCallback)); - return compiler.compile(_input); + return compiler.compile(std::move(_input)); } } @@ -98,4 +99,11 @@ extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _ s_outputBuffer = compile(_input, _readCallback); return s_outputBuffer.c_str(); } +extern void solidity_free() noexcept +{ + // This is called right before each compilation, but not at the end, so additional memory + // can be freed here. + yul::YulStringRepository::reset(); + s_outputBuffer.clear(); +} } diff --git a/liblityc/liblityc.h b/liblityc/liblityc.h index 4b0ec6398..2c55c2342 100644 --- a/liblityc/liblityc.h +++ b/liblityc/liblityc.h @@ -20,6 +20,8 @@ * Public compiler API. */ +#pragma once + #include #ifdef __cplusplus @@ -32,14 +34,32 @@ extern "C" { #endif -/// Callback used to retrieve additional source files. "Returns" two pointers that should be -/// heap-allocated and are free'd by the caller. +/// Callback used to retrieve additional source files. +/// +/// "Returns" two pointers that should be heap-allocated and are free'd by the caller. typedef void (*CStyleReadFileCallback)(char const* _path, char** o_contents, char** o_error); +/// Returns the complete license document. +/// +/// The pointer returned must not be freed by the caller. char const* solidity_license() SOLC_NOEXCEPT; + +/// Returns the compiler version. +/// +/// The pointer returned must not be freed by the caller. char const* solidity_version() SOLC_NOEXCEPT; + +/// Takes a "Standard Input JSON" and an optional callback (can be set to null). Returns +/// a "Standard Output JSON". Both are to be UTF-8 encoded. +/// +/// The pointer returned must not be freed by the caller. char const* solidity_compile(char const* _input, CStyleReadFileCallback _readCallback) SOLC_NOEXCEPT; +/// Frees up any allocated memory. +/// +/// NOTE: the pointer returned by solidity_compile is invalid after calling this! +void solidity_free() SOLC_NOEXCEPT; + #ifdef __cplusplus } #endif diff --git a/liblll/CMakeLists.txt b/liblll/CMakeLists.txt index 4cdc073a5..c529461b1 100644 --- a/liblll/CMakeLists.txt +++ b/liblll/CMakeLists.txt @@ -1,5 +1,14 @@ -file(GLOB sources "*.cpp") -file(GLOB headers "*.h") +set(sources + CodeFragment.cpp + CodeFragment.h + Compiler.cpp + Compiler.h + CompilerState.cpp + CompilerState.h + Exceptions.h + Parser.cpp + Parser.h +) -add_library(lll ${sources} ${headers}) +add_library(lll ${sources}) target_link_libraries(lll PUBLIC evmasm devcore) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index f37cb8b93..714515d96 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -19,7 +19,11 @@ * @date 2014 */ -#include "CodeFragment.h" +#include +#include +#include +#include +#include #include @@ -34,13 +38,10 @@ #pragma GCC diagnostic pop #endif // defined(__GNUC__) -#include -#include -#include "CompilerState.h" -#include "Parser.h" using namespace std; using namespace dev; +using namespace dev::eth; using namespace dev::lll; void CodeFragment::finalise(CompilerState const& _cs) @@ -66,7 +67,7 @@ bool validAssemblyInstruction(string us) auto it = c_instructions.find(us); return !( it == c_instructions.end() || - solidity::isPushInstruction(it->second) + isPushInstruction(it->second) ); } @@ -76,10 +77,10 @@ bool validFunctionalInstruction(string us) auto it = c_instructions.find(us); return !( it == c_instructions.end() || - solidity::isPushInstruction(it->second) || - solidity::isDupInstruction(it->second) || - solidity::isSwapInstruction(it->second) || - it->second == solidity::Instruction::JUMPDEST + isPushInstruction(it->second) || + isDupInstruction(it->second) || + isSwapInstruction(it->second) || + it->second == Instruction::JUMPDEST ); } } @@ -255,10 +256,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) string contents = m_readFile(fileName); if (contents.empty()) error(std::string("File not found (or empty): ") + fileName); - m_asm.append(CodeFragment::compile(contents, _s, m_readFile).m_asm); + m_asm.append(CodeFragment::compile(std::move(contents), _s, m_readFile).m_asm); } else if (us == "SET") { + // TODO: move this to be a stack variable (and not a memory variable) if (_t.size() != 3) error(us); int c = 0; @@ -268,6 +270,15 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) m_asm.append((u256)varAddress(firstAsString(), true)); m_asm.append(Instruction::MSTORE); } + else if (us == "UNSET") + { + // TODO: this doesn't actually free up anything, since it is a memory variable (see "SET") + if (_t.size() != 2) + error(); + auto it = _s.vars.find(firstAsString()); + if (it != _s.vars.end()) + _s.vars.erase(it); + } else if (us == "GET") { if (_t.size() != 2) @@ -275,6 +286,35 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) m_asm.append((u256)varAddress(firstAsString())); m_asm.append(Instruction::MLOAD); } + else if (us == "WITH") + { + if (_t.size() != 4) + error(); + string key = firstAsString(); + if (_s.vars.find(key) != _s.vars.end()) + error(string("Symbol already used: ") + key); + + // Create variable + // TODO: move this to be a stack variable (and not a memory variable) + size_t c = 0; + for (auto const& i: _t) + if (c++ == 2) + m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); + m_asm.append((u256)varAddress(key, true)); + m_asm.append(Instruction::MSTORE); + + // Insert sub with variable access, but new state + CompilerState ns = _s; + c = 0; + for (auto const& i: _t) + if (c++ == 3) + m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); + + // Remove variable + auto it = _s.vars.find(key); + if (it != _s.vars.end()) + _s.vars.erase(it); + } else if (us == "REF") m_asm.append((u256)varAddress(firstAsString())); else if (us == "DEF") @@ -314,7 +354,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) if (j.tag() || j.which() != sp::utree_type::symbol_type) error(); auto sr = j.get, sp::utree_type::symbol_type>>(); - args.push_back(string(sr.begin(), sr.end())); + args.emplace_back(sr.begin(), sr.end()); } else if (ii == 3) { @@ -425,9 +465,9 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) if (c++) { if (us == "LLL" && c == 1) - code.push_back(CodeFragment(i, ns, m_readFile)); + code.emplace_back(i, ns, m_readFile); else - code.push_back(CodeFragment(i, _s, m_readFile)); + code.emplace_back(i, _s, m_readFile); } auto requireSize = [&](unsigned s) { if (code.size() != s) error(us); }; auto requireMinSize = [&](unsigned s) { if (code.size() < s) error(us); }; @@ -610,22 +650,22 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) auto end = m_asm.newTag(); m_asm.append(Instruction::MSIZE); // Result will be original top of memory - m_asm.append(code[0].m_asm, 1); // The alloc argument N + m_asm.append(code[0].m_asm, 1); // The alloc argument N m_asm.append(Instruction::DUP1); m_asm.append(Instruction::ISZERO);// (alloc 0) does not change MSIZE m_asm.appendJumpI(end); m_asm.append(u256(1)); m_asm.append(Instruction::DUP2); // Copy N - m_asm.append(Instruction::SUB); // N-1 + m_asm.append(Instruction::SUB); // N-1 m_asm.append(u256(0x1f)); // Bit mask - m_asm.append(Instruction::NOT); // Invert - m_asm.append(Instruction::AND); // Align N-1 on 32 byte boundary + m_asm.append(Instruction::NOT); // Invert + m_asm.append(Instruction::AND); // Align N-1 on 32 byte boundary m_asm.append(Instruction::MSIZE); // MSIZE is cheap m_asm.append(Instruction::ADD); m_asm.append(Instruction::MLOAD); // Updates MSIZE - m_asm.append(Instruction::POP); // Discard the result of the MLOAD + m_asm.append(Instruction::POP); // Discard the result of the MLOAD m_asm.append(end); - m_asm.append(Instruction::POP); // Discard duplicate N + m_asm.append(Instruction::POP); // Discard duplicate N _s.usedAlloc = true; } @@ -705,11 +745,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) } } -CodeFragment CodeFragment::compile(string const& _src, CompilerState& _s, ReadCallback const& _readFile) +CodeFragment CodeFragment::compile(string _src, CompilerState& _s, ReadCallback const& _readFile) { CodeFragment ret; sp::utree o; - parseTreeLLL(_src, o); + parseTreeLLL(std::move(_src), o); if (!o.empty()) ret = CodeFragment(o, _s, _readFile); _s.treesToKill.push_back(o); diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index e6e4d3b64..cbdf86766 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -21,10 +21,10 @@ #pragma once -#include +#include #include #include -#include "Exceptions.h" +#include namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; @@ -41,10 +41,10 @@ class CodeFragment public: using ReadCallback = std::function; - CodeFragment() {} + CodeFragment() = default; CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM = false); - static CodeFragment compile(std::string const& _src, CompilerState& _s, ReadCallback const& _readFile); + static CodeFragment compile(std::string _src, CompilerState& _s, ReadCallback const& _readFile); /// Consolidates data and compiles code. eth::Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; } @@ -65,8 +65,7 @@ class CodeFragment ReadCallback m_readFile; }; -static const CodeFragment NullCodeFragment; +static CodeFragment const NullCodeFragment; } } - diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index f944adbd9..2bea6ca6a 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -28,15 +28,15 @@ using namespace std; using namespace dev; using namespace dev::lll; -bytes dev::lll::compileLLL(string const& _src, dev::solidity::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) +bytes dev::lll::compileLLL(string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) { try { CompilerState cs; cs.populateStandard(); - auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); + auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true, _evmVersion); + assembly = assembly.optimise(true, _evmVersion, true, 200); bytes ret = assembly.assemble().bytecode; for (auto i: cs.treesToKill) killBigints(i); @@ -46,35 +46,35 @@ bytes dev::lll::compileLLL(string const& _src, dev::solidity::EVMVersion _evmVer { if (_errors) { - _errors->push_back("Parse error."); - _errors->push_back(boost::diagnostic_information(_e)); + _errors->emplace_back("Parse error."); + _errors->emplace_back(boost::diagnostic_information(_e)); } } catch (std::exception const& _e) { if (_errors) { - _errors->push_back("Parse exception."); - _errors->push_back(boost::diagnostic_information(_e)); + _errors->emplace_back("Parse exception."); + _errors->emplace_back(boost::diagnostic_information(_e)); } } catch (...) { if (_errors) - _errors->push_back("Internal compiler exception."); + _errors->emplace_back("Internal compiler exception."); } return bytes(); } -std::string dev::lll::compileLLLToAsm(std::string const& _src, EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) +std::string dev::lll::compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) { try { CompilerState cs; cs.populateStandard(); - auto assembly = CodeFragment::compile(_src, cs, _readFile).assembly(cs); + auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); if (_opt) - assembly = assembly.optimise(true, _evmVersion); + assembly = assembly.optimise(true, _evmVersion, true, 200); string ret = assembly.assemblyString(); for (auto i: cs.treesToKill) killBigints(i); @@ -84,33 +84,33 @@ std::string dev::lll::compileLLLToAsm(std::string const& _src, EVMVersion _evmVe { if (_errors) { - _errors->push_back("Parse error."); - _errors->push_back(boost::diagnostic_information(_e)); + _errors->emplace_back("Parse error."); + _errors->emplace_back(boost::diagnostic_information(_e)); } } catch (std::exception const& _e) { if (_errors) { - _errors->push_back("Parse exception."); - _errors->push_back(boost::diagnostic_information(_e)); + _errors->emplace_back("Parse exception."); + _errors->emplace_back(boost::diagnostic_information(_e)); } } catch (...) { if (_errors) - _errors->push_back("Internal compiler exception."); + _errors->emplace_back("Internal compiler exception."); } return string(); } -string dev::lll::parseLLL(string const& _src) +string dev::lll::parseLLL(string _src) { sp::utree o; try { - parseTreeLLL(_src, o); + parseTreeLLL(std::move(_src), o); } catch (...) { diff --git a/liblll/Compiler.h b/liblll/Compiler.h index 1ff7d5f81..14b10f750 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -23,7 +23,7 @@ #include -#include +#include #include #include @@ -35,9 +35,9 @@ namespace lll using ReadCallback = std::function; -std::string parseLLL(std::string const& _src); -std::string compileLLLToAsm(std::string const& _src, solidity::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); -bytes compileLLL(std::string const& _src, solidity::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +std::string parseLLL(std::string _src); +std::string compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); +bytes compileLLL(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector* _errors = nullptr, ReadCallback const& _readFile = ReadCallback()); } } diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index 019582d4c..2ae2b0af3 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -19,8 +19,8 @@ * @date 2014 */ -#include "CompilerState.h" -#include "CodeFragment.h" +#include +#include using namespace std; using namespace dev; @@ -44,7 +44,7 @@ CodeFragment const& CompilerState::getDef(std::string const& _s) const void CompilerState::populateStandard() { - static const string s = "{" + static string const s = "{" "(def 'panic () (asm INVALID))" // Alternative macro version of alloc, which is currently implemented in the parser // "(def 'alloc (n) (raw (msize) (when n (pop (mload (+ (msize) (& (- n 1) (~ 0x1f))))))))" diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h index 0fa502884..d7ba38c33 100644 --- a/liblll/CompilerState.h +++ b/liblll/CompilerState.h @@ -21,8 +21,8 @@ #pragma once +#include #include -#include "CodeFragment.h" namespace dev { diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index 854aeecc8..3b68bc2da 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "Parser.h" +#include #if _MSC_VER #pragma warning(disable:4348) diff --git a/liblll/Parser.h b/liblll/Parser.h index cfbec4181..37b39c6b6 100644 --- a/liblll/Parser.h +++ b/liblll/Parser.h @@ -21,10 +21,10 @@ #pragma once +#include +#include #include #include -#include -#include "Exceptions.h" namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index a5c1c04ab..bd1697aa3 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -1,23 +1,141 @@ # Until we have a clear separation, libyul has to be included here -file(GLOB_RECURSE sources "*.cpp" "../libyul/*.cpp") -file(GLOB_RECURSE headers "*.h" "../libyul/*.h") +set(sources + analysis/ConstantEvaluator.cpp + analysis/ConstantEvaluator.h + analysis/ContractLevelChecker.cpp + analysis/ContractLevelChecker.h + analysis/ControlFlowAnalyzer.cpp + analysis/ControlFlowAnalyzer.h + analysis/ControlFlowBuilder.cpp + analysis/ControlFlowBuilder.h + analysis/ControlFlowGraph.cpp + analysis/ControlFlowGraph.h + analysis/DeclarationContainer.cpp + analysis/DeclarationContainer.h + analysis/DocStringAnalyser.cpp + analysis/DocStringAnalyser.h + analysis/GlobalContext.cpp + analysis/GlobalContext.h + analysis/NameAndTypeResolver.cpp + analysis/NameAndTypeResolver.h + analysis/PostTypeChecker.cpp + analysis/PostTypeChecker.h + analysis/ReferencesResolver.cpp + analysis/ReferencesResolver.h + analysis/StaticAnalyzer.cpp + analysis/StaticAnalyzer.h + analysis/SyntaxChecker.cpp + analysis/SyntaxChecker.h + analysis/TypeChecker.cpp + analysis/TypeChecker.h + analysis/ViewPureChecker.cpp + analysis/ViewPureChecker.h + ast/AST.cpp + ast/AST.h + ast/AST_accept.h + ast/ASTAnnotations.cpp + ast/ASTAnnotations.h + ast/ASTEnums.h + ast/ASTForward.h + ast/ASTJsonConverter.cpp + ast/ASTJsonConverter.h + ast/ASTPrinter.cpp + ast/ASTPrinter.h + ast/ASTVisitor.h + ast/ExperimentalFeatures.h + ast/Types.cpp + ast/Types.h + ast/TypeProvider.cpp + ast/TypeProvider.h + codegen/ABIFunctions.cpp + codegen/ABIFunctions.h + codegen/ArrayUtils.cpp + codegen/ArrayUtils.h + codegen/Compiler.cpp + codegen/Compiler.h + codegen/CompilerContext.cpp + codegen/CompilerContext.h + codegen/CompilerUtils.cpp + codegen/CompilerUtils.h + codegen/ContractCompiler.cpp + codegen/ContractCompiler.h + codegen/ExpressionCompiler.cpp + codegen/ExpressionCompiler.h + codegen/LValue.cpp + codegen/LValue.h + codegen/MultiUseYulFunctionCollector.h + codegen/MultiUseYulFunctionCollector.cpp + codegen/YulUtilFunctions.h + codegen/YulUtilFunctions.cpp + codegen/ir/IRGenerator.cpp + codegen/ir/IRGenerator.h + codegen/ir/IRGeneratorForStatements.cpp + codegen/ir/IRGeneratorForStatements.h + codegen/ir/IRGenerationContext.cpp + codegen/ir/IRGenerationContext.h + codegen/ir/IRLValue.cpp + codegen/ir/IRLValue.h + formal/BMC.cpp + formal/BMC.h + formal/CHC.cpp + formal/CHC.h + formal/CHCSolverInterface.h + formal/EncodingContext.cpp + formal/EncodingContext.h + formal/ModelChecker.cpp + formal/ModelChecker.h + formal/SMTEncoder.cpp + formal/SMTEncoder.h + formal/SMTLib2Interface.cpp + formal/SMTLib2Interface.h + formal/SMTPortfolio.cpp + formal/SMTPortfolio.h + formal/SolverInterface.h + formal/SSAVariable.cpp + formal/SSAVariable.h + formal/SymbolicTypes.cpp + formal/SymbolicTypes.h + formal/SymbolicVariables.cpp + formal/SymbolicVariables.h + formal/VariableUsage.cpp + formal/VariableUsage.h + interface/ABI.cpp + interface/ABI.h + interface/CompilerStack.cpp + interface/CompilerStack.h + interface/GasEstimator.cpp + interface/GasEstimator.h + interface/Natspec.cpp + interface/Natspec.h + interface/OptimiserSettings.h + interface/ReadFile.h + interface/StandardCompiler.cpp + interface/StandardCompiler.h + interface/Version.cpp + interface/Version.h + parsing/DocStringParser.cpp + parsing/DocStringParser.h + parsing/Parser.cpp + parsing/Parser.h + parsing/Token.h +) -find_package(Z3 QUIET) +find_package(Z3 4.6.0) if (${Z3_FOUND}) - include_directories(${Z3_INCLUDE_DIR}) add_definitions(-DHAVE_Z3) message("Z3 SMT solver found. This enables optional SMT checking with Z3.") + set(z3_SRCS formal/Z3Interface.cpp formal/Z3Interface.h formal/Z3CHCInterface.cpp formal/Z3CHCInterface.h) else() - list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/Z3Interface.cpp") + set(z3_SRCS) endif() find_package(CVC4 QUIET) if (${CVC4_FOUND}) - include_directories(${CVC4_INCLUDE_DIR}) add_definitions(-DHAVE_CVC4) message("CVC4 SMT solver found. This enables optional SMT checking with CVC4.") + set(cvc4_SRCS formal/CVC4Interface.cpp formal/CVC4Interface.h) else() - list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/formal/CVC4Interface.cpp") + set(cvc4_SRCS) endif() if (NOT (${Z3_FOUND} OR ${CVC4_FOUND})) @@ -25,13 +143,13 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND})) \nPlease install Z3 or CVC4 or remove the option disabling them (USE_Z3, USE_CVC4).") endif() -add_library(lity ${sources} ${headers}) -target_link_libraries(lity PUBLIC evmasm devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY}) +add_library(lity ${sources} ${z3_SRCS} ${cvc4_SRRCS}) +target_link_libraries(lity PUBLIC yul evmasm langutil devcore Boost::boost Boost::filesystem Boost::system) if (${Z3_FOUND}) - target_link_libraries(lity PUBLIC ${Z3_LIBRARY}) + target_link_libraries(lity PUBLIC z3::libz3) endif() if (${CVC4_FOUND}) - target_link_libraries(lity PUBLIC ${CVC4_LIBRARIES}) + target_link_libraries(lity PUBLIC CVC4::CVC4) endif() diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index f9b00927d..c63d1b127 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -21,8 +21,10 @@ */ #include + #include -#include +#include +#include using namespace std; using namespace dev; @@ -41,7 +43,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) auto right = type(_operation.rightExpression()); if (left && right) { - auto commonType = left->binaryOperatorResult(_operation.getOperator(), right); + TypePointer commonType = left->binaryOperatorResult(_operation.getOperator(), right); if (!commonType) m_errorReporter.fatalTypeError( _operation.location(), @@ -55,7 +57,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) setType( _operation, TokenTraits::isCompareOp(_operation.getOperator()) ? - make_shared() : + TypeProvider::boolean() : commonType ); } @@ -63,7 +65,7 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) void ConstantEvaluator::endVisit(Literal const& _literal) { - setType(_literal, Type::forLiteral(_literal)); + setType(_literal, TypeProvider::forLiteral(_literal)); } void ConstantEvaluator::endVisit(Identifier const& _identifier) diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h index ac3a24a1f..23ca36286 100644 --- a/libsolidity/analysis/ConstantEvaluator.h +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -24,12 +24,16 @@ #include +namespace langutil +{ +class ErrorReporter; +} + namespace dev { namespace solidity { -class ErrorReporter; class TypeChecker; /** @@ -39,7 +43,7 @@ class ConstantEvaluator: private ASTConstVisitor { public: ConstantEvaluator( - ErrorReporter& _errorReporter, + langutil::ErrorReporter& _errorReporter, size_t _newDepth = 0, std::shared_ptr> _types = std::make_shared>() ): @@ -61,7 +65,7 @@ class ConstantEvaluator: private ASTConstVisitor void setType(ASTNode const& _node, TypePointer const& _type); TypePointer type(ASTNode const& _node); - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; /// Current recursion depth. size_t m_depth = 0; std::shared_ptr> m_types; diff --git a/libsolidity/analysis/ContractLevelChecker.cpp b/libsolidity/analysis/ContractLevelChecker.cpp new file mode 100644 index 000000000..95626bb2a --- /dev/null +++ b/libsolidity/analysis/ContractLevelChecker.cpp @@ -0,0 +1,512 @@ +/* + 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 . +*/ +/** + * Component that verifies overloads, abstract contracts, function clashes and others + * checks at contract or function level. + */ + +#include + +#include +#include +#include +#include +#include + + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + + +bool ContractLevelChecker::check(ContractDefinition const& _contract) +{ + checkDuplicateFunctions(_contract); + checkDuplicateEvents(_contract); + checkIllegalOverrides(_contract); + checkAbstractFunctions(_contract); + checkBaseConstructorArguments(_contract); + checkConstructor(_contract); + checkFallbackFunction(_contract); + checkExternalTypeClashes(_contract); + checkHashCollisions(_contract); + checkLibraryRequirements(_contract); + checkBaseABICompatibility(_contract); + + return Error::containsOnlyWarnings(m_errorReporter.errors()); +} + +void ContractLevelChecker::checkDuplicateFunctions(ContractDefinition const& _contract) +{ + /// Checks that two functions with the same name defined in this contract have different + /// argument types and that there is at most one constructor. + map> functions; + FunctionDefinition const* constructor = nullptr; + FunctionDefinition const* fallback = nullptr; + for (FunctionDefinition const* function: _contract.definedFunctions()) + if (function->isConstructor()) + { + if (constructor) + m_errorReporter.declarationError( + function->location(), + SecondarySourceLocation().append("Another declaration is here:", constructor->location()), + "More than one constructor defined." + ); + constructor = function; + } + else if (function->isFallback()) + { + if (fallback) + m_errorReporter.declarationError( + function->location(), + SecondarySourceLocation().append("Another declaration is here:", fallback->location()), + "Only one fallback function is allowed." + ); + fallback = function; + } + else + { + solAssert(!function->name().empty(), ""); + functions[function->name()].push_back(function); + } + + findDuplicateDefinitions(functions, "Function with same name and arguments defined twice."); +} + +void ContractLevelChecker::checkDuplicateEvents(ContractDefinition const& _contract) +{ + /// Checks that two events with the same name defined in this contract have different + /// argument types + map> events; + for (EventDefinition const* event: _contract.events()) + events[event->name()].push_back(event); + + findDuplicateDefinitions(events, "Event with same name and arguments defined twice."); +} + +template +void ContractLevelChecker::findDuplicateDefinitions(map> const& _definitions, string _message) +{ + for (auto const& it: _definitions) + { + vector const& overloads = it.second; + set reported; + for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i) + { + SecondarySourceLocation ssl; + + for (size_t j = i + 1; j < overloads.size(); ++j) + if (FunctionType(*overloads[i]).asCallableFunction(false)->hasEqualParameterTypes( + *FunctionType(*overloads[j]).asCallableFunction(false)) + ) + { + ssl.append("Other declaration is here:", overloads[j]->location()); + reported.insert(j); + } + + if (ssl.infos.size() > 0) + { + ssl.limitSize(_message); + + m_errorReporter.declarationError( + overloads[i]->location(), + ssl, + _message + ); + } + } + } +} + +void ContractLevelChecker::checkIllegalOverrides(ContractDefinition const& _contract) +{ + // TODO unify this at a later point. for this we need to put the constness and the access specifier + // into the types + map> functions; + map modifiers; + + // We search from derived to base, so the stored item causes the error. + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + { + for (FunctionDefinition const* function: contract->definedFunctions()) + { + if (function->isConstructor()) + continue; // constructors can neither be overridden nor override anything + string const& name = function->name(); + if (modifiers.count(name)) + m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier."); + + for (FunctionDefinition const* overriding: functions[name]) + checkFunctionOverride(*overriding, *function); + + functions[name].push_back(function); + } + for (ModifierDefinition const* modifier: contract->functionModifiers()) + { + string const& name = modifier->name(); + ModifierDefinition const*& override = modifiers[name]; + if (!override) + override = modifier; + else if (ModifierType(*override) != ModifierType(*modifier)) + m_errorReporter.typeError(override->location(), "Override changes modifier signature."); + if (!functions[name].empty()) + m_errorReporter.typeError(override->location(), "Override changes modifier to function."); + } + } +} + +void ContractLevelChecker::checkFunctionOverride(FunctionDefinition const& _function, FunctionDefinition const& _super) +{ + FunctionTypePointer functionType = FunctionType(_function).asCallableFunction(false); + FunctionTypePointer superType = FunctionType(_super).asCallableFunction(false); + + if (!functionType->hasEqualParameterTypes(*superType)) + return; + if (!functionType->hasEqualReturnTypes(*superType)) + overrideError(_function, _super, "Overriding function return types differ."); + + if (!_function.annotation().superFunction) + _function.annotation().superFunction = &_super; + + if (_function.visibility() != _super.visibility()) + { + // Visibility change from external to public is fine. + // Any other change is disallowed. + if (!( + _super.visibility() == FunctionDefinition::Visibility::External && + _function.visibility() == FunctionDefinition::Visibility::Public + )) + overrideError(_function, _super, "Overriding function visibility differs."); + } + if (_function.stateMutability() != _super.stateMutability()) + overrideError( + _function, + _super, + "Overriding function changes state mutability from \"" + + stateMutabilityToString(_super.stateMutability()) + + "\" to \"" + + stateMutabilityToString(_function.stateMutability()) + + "\"." + ); +} + +void ContractLevelChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) +{ + m_errorReporter.typeError( + function.location(), + SecondarySourceLocation().append("Overridden function is here:", super.location()), + message + ); +} + +void ContractLevelChecker::checkAbstractFunctions(ContractDefinition const& _contract) +{ + // Mapping from name to function definition (exactly one per argument type equality class) and + // flag to indicate whether it is fully implemented. + using FunTypeAndFlag = std::pair; + map> functions; + + auto registerFunction = [&](Declaration const& _declaration, FunctionTypePointer const& _type, bool _implemented) + { + auto& overloads = functions[_declaration.name()]; + auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) + { + return _type->hasEqualParameterTypes(*_funAndFlag.first); + }); + if (it == overloads.end()) + overloads.emplace_back(_type, _implemented); + else if (it->second) + { + if (!_implemented) + m_errorReporter.typeError(_declaration.location(), "Redeclaring an already implemented function as abstract"); + } + else if (_implemented) + it->second = true; + }; + + // Search from base to derived, collect all functions and update + // the 'implemented' flag. + for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) + { + for (VariableDeclaration const* v: contract->stateVariables()) + if (v->isPartOfExternalInterface()) + registerFunction(*v, TypeProvider::function(*v), true); + + for (FunctionDefinition const* function: contract->definedFunctions()) + if (!function->isConstructor()) + registerFunction( + *function, + TypeProvider::function(*function)->asCallableFunction(false), + function->isImplemented() + ); + } + + // Set to not fully implemented if at least one flag is false. + for (auto const& it: functions) + for (auto const& funAndFlag: it.second) + if (!funAndFlag.second) + { + FunctionDefinition const* function = dynamic_cast(&funAndFlag.first->declaration()); + solAssert(function, ""); + _contract.annotation().unimplementedFunctions.push_back(function); + break; + } +} + + +void ContractLevelChecker::checkBaseConstructorArguments(ContractDefinition const& _contract) +{ + vector const& bases = _contract.annotation().linearizedBaseContracts; + + // Determine the arguments that are used for the base constructors. + for (ContractDefinition const* contract: bases) + { + if (FunctionDefinition const* constructor = contract->constructor()) + for (auto const& modifier: constructor->modifiers()) + if (auto baseContract = dynamic_cast( + modifier->name()->annotation().referencedDeclaration + )) + { + if (modifier->arguments()) + { + if (baseContract->constructor()) + annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get()); + } + else + m_errorReporter.declarationError( + modifier->location(), + "Modifier-style base constructor call without arguments." + ); + } + + for (ASTPointer const& base: contract->baseContracts()) + { + ContractDefinition const* baseContract = dynamic_cast( + base->name().annotation().referencedDeclaration + ); + solAssert(baseContract, ""); + + if (baseContract->constructor() && base->arguments() && !base->arguments()->empty()) + annotateBaseConstructorArguments(_contract, baseContract->constructor(), base.get()); + } + } + + // check that we get arguments for all base constructors that need it. + // If not mark the contract as abstract (not fully implemented) + for (ContractDefinition const* contract: bases) + if (FunctionDefinition const* constructor = contract->constructor()) + if (contract != &_contract && !constructor->parameters().empty()) + if (!_contract.annotation().baseConstructorArguments.count(constructor)) + _contract.annotation().unimplementedFunctions.push_back(constructor); +} + +void ContractLevelChecker::annotateBaseConstructorArguments( + ContractDefinition const& _currentContract, + FunctionDefinition const* _baseConstructor, + ASTNode const* _argumentNode +) +{ + solAssert(_baseConstructor, ""); + solAssert(_argumentNode, ""); + + auto insertionResult = _currentContract.annotation().baseConstructorArguments.insert( + std::make_pair(_baseConstructor, _argumentNode) + ); + if (!insertionResult.second) + { + ASTNode const* previousNode = insertionResult.first->second; + + SourceLocation const* mainLocation = nullptr; + SecondarySourceLocation ssl; + + if ( + _currentContract.location().contains(previousNode->location()) || + _currentContract.location().contains(_argumentNode->location()) + ) + { + mainLocation = &previousNode->location(); + ssl.append("Second constructor call is here:", _argumentNode->location()); + } + else + { + mainLocation = &_currentContract.location(); + ssl.append("First constructor call is here:", _argumentNode->location()); + ssl.append("Second constructor call is here:", previousNode->location()); + } + + m_errorReporter.declarationError( + *mainLocation, + ssl, + "Base constructor arguments given twice." + ); + } + +} + +void ContractLevelChecker::checkConstructor(ContractDefinition const& _contract) +{ + FunctionDefinition const* constructor = _contract.constructor(); + if (!constructor) + return; + + if (!constructor->returnParameters().empty()) + m_errorReporter.typeError(constructor->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); + if (constructor->stateMutability() != StateMutability::NonPayable && constructor->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + constructor->location(), + "Constructor must be payable or non-payable, but is \"" + + stateMutabilityToString(constructor->stateMutability()) + + "\"." + ); + if (constructor->visibility() != FunctionDefinition::Visibility::Public && constructor->visibility() != FunctionDefinition::Visibility::Internal) + m_errorReporter.typeError(constructor->location(), "Constructor must be public or internal."); +} + +void ContractLevelChecker::checkFallbackFunction(ContractDefinition const& _contract) +{ + FunctionDefinition const* fallback = _contract.fallbackFunction(); + if (!fallback) + return; + + if (_contract.isLibrary()) + m_errorReporter.typeError(fallback->location(), "Libraries cannot have fallback functions."); + if (fallback->stateMutability() != StateMutability::NonPayable && fallback->stateMutability() != StateMutability::Payable) + m_errorReporter.typeError( + fallback->location(), + "Fallback function must be payable or non-payable, but is \"" + + stateMutabilityToString(fallback->stateMutability()) + + "\"." + ); + if (!fallback->parameters().empty()) + m_errorReporter.typeError(fallback->parameterList().location(), "Fallback function cannot take parameters."); + if (!fallback->returnParameters().empty()) + m_errorReporter.typeError(fallback->returnParameterList()->location(), "Fallback function cannot return values."); + if (fallback->visibility() != FunctionDefinition::Visibility::External) + m_errorReporter.typeError(fallback->location(), "Fallback function must be defined as \"external\"."); +} + +void ContractLevelChecker::checkExternalTypeClashes(ContractDefinition const& _contract) +{ + map>> externalDeclarations; + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + { + for (FunctionDefinition const* f: contract->definedFunctions()) + if (f->isPartOfExternalInterface()) + { + auto functionType = TypeProvider::function(*f); + // under non error circumstances this should be true + if (functionType->interfaceFunctionType()) + externalDeclarations[functionType->externalSignature()].emplace_back( + f, functionType->asCallableFunction(false) + ); + } + for (VariableDeclaration const* v: contract->stateVariables()) + if (v->isPartOfExternalInterface()) + { + auto functionType = TypeProvider::function(*v); + // under non error circumstances this should be true + if (functionType->interfaceFunctionType()) + externalDeclarations[functionType->externalSignature()].emplace_back( + v, functionType->asCallableFunction(false) + ); + } + } + for (auto const& it: externalDeclarations) + for (size_t i = 0; i < it.second.size(); ++i) + for (size_t j = i + 1; j < it.second.size(); ++j) + if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second)) + m_errorReporter.typeError( + it.second[j].first->location(), + "Function overload clash during conversion to external types for arguments." + ); +} + +void ContractLevelChecker::checkHashCollisions(ContractDefinition const& _contract) +{ + set> hashes; + for (auto const& it: _contract.interfaceFunctionList()) + { + FixedHash<4> const& hash = it.first; + if (hashes.count(hash)) + m_errorReporter.typeError( + _contract.location(), + string("Function signature hash collision for ") + it.second->externalSignature() + ); + hashes.insert(hash); + } +} + +void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _contract) +{ + if (!_contract.isLibrary()) + return; + + if (!_contract.baseContracts().empty()) + m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit."); + + for (auto const& var: _contract.stateVariables()) + if (!var->isConstant()) + m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); +} + +void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract) +{ + if (_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) + return; + + if (_contract.isLibrary()) + { + solAssert( + _contract.baseContracts().empty() || m_errorReporter.hasErrors(), + "Library is not allowed to inherit" + ); + return; + } + + SecondarySourceLocation errors; + + // interfaceFunctionList contains all inherited functions as well + for (auto const& func: _contract.interfaceFunctionList()) + { + solAssert(func.second->hasDeclaration(), "Function has no declaration?!"); + + if (!func.second->declaration().sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) + continue; + + auto const& currentLoc = func.second->declaration().location(); + + for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes()) + if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false)) + { + errors.append("Type only supported by the new experimental ABI encoder", currentLoc); + break; + } + } + + if (!errors.infos.empty()) + m_errorReporter.fatalTypeError( + _contract.location(), + errors, + std::string("Contract \"") + + _contract.name() + + "\" does not use the new experimental ABI encoder but wants to inherit from a contract " + + "which uses types that require it. " + + "Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature." + ); + +} diff --git a/libsolidity/analysis/ContractLevelChecker.h b/libsolidity/analysis/ContractLevelChecker.h new file mode 100644 index 000000000..f2cd9887f --- /dev/null +++ b/libsolidity/analysis/ContractLevelChecker.h @@ -0,0 +1,88 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Component that verifies overloads, abstract contracts, function clashes and others + * checks at contract or function level. + */ + +#pragma once + +#include +#include + +namespace langutil +{ +class ErrorReporter; +} + +namespace dev +{ +namespace solidity +{ + +/** + * Component that verifies overloads, abstract contracts, function clashes and others + * checks at contract or function level. + */ +class ContractLevelChecker +{ +public: + /// @param _errorReporter provides the error logging functionality. + explicit ContractLevelChecker(langutil::ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) + {} + + /// Performs checks on the given contract. + /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings + bool check(ContractDefinition const& _contract); + +private: + /// Checks that two functions defined in this contract with the same name have different + /// arguments and that there is at most one constructor. + void checkDuplicateFunctions(ContractDefinition const& _contract); + void checkDuplicateEvents(ContractDefinition const& _contract); + template + void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); + void checkIllegalOverrides(ContractDefinition const& _contract); + /// Reports a type error with an appropriate message if overridden function signature differs. + /// Also stores the direct super function in the AST annotations. + void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); + void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); + void checkAbstractFunctions(ContractDefinition const& _contract); + void checkBaseConstructorArguments(ContractDefinition const& _contract); + void annotateBaseConstructorArguments( + ContractDefinition const& _currentContract, + FunctionDefinition const* _baseConstructor, + ASTNode const* _argumentNode + ); + void checkConstructor(ContractDefinition const& _contract); + void checkFallbackFunction(ContractDefinition const& _contract); + /// Checks that different functions with external visibility end up having different + /// external argument types (i.e. different signature). + void checkExternalTypeClashes(ContractDefinition const& _contract); + /// Checks for hash collisions in external function signatures. + void checkHashCollisions(ContractDefinition const& _contract); + /// Checks that all requirements for a library are fulfilled if this is a library. + void checkLibraryRequirements(ContractDefinition const& _contract); + /// Checks base contracts for ABI compatibility + void checkBaseABICompatibility(ContractDefinition const& _contract); + + langutil::ErrorReporter& m_errorReporter; +}; + +} +} diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index 45c5c0e99..059a39a6c 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -17,7 +17,12 @@ #include +#include +#include +#include + using namespace std; +using namespace langutil; using namespace dev::solidity; bool ControlFlowAnalyzer::analyze(ASTNode const& _astRoot) @@ -31,150 +36,146 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function) if (_function.isImplemented()) { auto const& functionFlow = m_cfg.functionFlow(_function); - checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit); + checkUninitializedAccess(functionFlow.entry, functionFlow.exit); + checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert); } return true; } -set ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node) +void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const { - set result; - for (auto expression: node->block.expressions) + struct NodeInfo { - if (auto const* assignment = dynamic_cast(expression)) + set unassignedVariablesAtEntry; + set unassignedVariablesAtExit; + set uninitializedVariableAccesses; + /// Propagate the information from another node to this node. + /// To be used to propagate information from a node to its exit nodes. + /// Returns true, if new variables were added and thus the current node has + /// to be traversed again. + bool propagateFrom(NodeInfo const& _entryNode) { - stack expressions; - expressions.push(&assignment->leftHandSide()); - while (!expressions.empty()) - { - Expression const* expression = expressions.top(); - expressions.pop(); - - if (auto const *tuple = dynamic_cast(expression)) - for (auto const& component: tuple->components()) - expressions.push(component.get()); - else if (auto const* identifier = dynamic_cast(expression)) - if (auto const* variableDeclaration = dynamic_cast( - identifier->annotation().referencedDeclaration - )) - result.insert(variableDeclaration); - } + size_t previousUnassignedVariablesAtEntry = unassignedVariablesAtEntry.size(); + size_t previousUninitializedVariableAccessess = uninitializedVariableAccesses.size(); + unassignedVariablesAtEntry += _entryNode.unassignedVariablesAtExit; + uninitializedVariableAccesses += _entryNode.uninitializedVariableAccesses; + return + unassignedVariablesAtEntry.size() > previousUnassignedVariablesAtEntry || + uninitializedVariableAccesses.size() > previousUninitializedVariableAccessess + ; } - } - return result; -} - -void ControlFlowAnalyzer::checkUnassignedStorageReturnValues( - FunctionDefinition const& _function, - CFGNode const* _functionEntry, - CFGNode const* _functionExit -) const -{ - if (_function.returnParameterList()->parameters().empty()) - return; - - map> unassigned; - - { - auto& unassignedAtFunctionEntry = unassigned[_functionEntry]; - for (auto const& returnParameter: _function.returnParameterList()->parameters()) - if ( - returnParameter->type()->dataStoredIn(DataLocation::Storage) || - returnParameter->type()->category() == Type::Category::Mapping - ) - unassignedAtFunctionEntry.insert(returnParameter.get()); - } - - stack nodesToTraverse; - nodesToTraverse.push(_functionEntry); - - // walk all paths from entry with maximal set of unassigned return values + }; + map nodeInfos; + set nodesToTraverse; + nodesToTraverse.insert(_entry); + + // Walk all paths starting from the nodes in ``nodesToTraverse`` until ``NodeInfo::propagateFrom`` + // returns false for all exits, i.e. until all paths have been walked with maximal sets of unassigned + // variables and accesses. while (!nodesToTraverse.empty()) { - auto node = nodesToTraverse.top(); - nodesToTraverse.pop(); - - auto& unassignedAtNode = unassigned[node]; + CFGNode const* currentNode = *nodesToTraverse.begin(); + nodesToTraverse.erase(nodesToTraverse.begin()); - if (node->block.returnStatement != nullptr) - if (node->block.returnStatement->expression()) - unassignedAtNode.clear(); - if (!unassignedAtNode.empty()) + auto& nodeInfo = nodeInfos[currentNode]; + auto unassignedVariables = nodeInfo.unassignedVariablesAtEntry; + for (auto const& variableOccurrence: currentNode->variableOccurrences) { - // kill all return values to which a value is assigned - for (auto const* variableDeclaration: variablesAssignedInNode(node)) - unassignedAtNode.erase(variableDeclaration); - - // kill all return values referenced in inline assembly - // a reference is enough, checking whether there actually was an assignment might be overkill - for (auto assembly: node->block.inlineAssemblyStatements) - for (auto const& ref: assembly->annotation().externalReferences) - if (auto variableDeclaration = dynamic_cast(ref.second.declaration)) - unassignedAtNode.erase(variableDeclaration); + switch (variableOccurrence.kind()) + { + case VariableOccurrence::Kind::Assignment: + unassignedVariables.erase(&variableOccurrence.declaration()); + break; + case VariableOccurrence::Kind::InlineAssembly: + // We consider all variables referenced in inline assembly as accessed. + // So far any reference is enough, but we might want to actually analyze + // the control flow in the assembly at some point. + case VariableOccurrence::Kind::Access: + case VariableOccurrence::Kind::Return: + if (unassignedVariables.count(&variableOccurrence.declaration())) + { + if (variableOccurrence.declaration().type()->dataStoredIn(DataLocation::Storage)) + // Merely store the unassigned access. We do not generate an error right away, since this + // path might still always revert. It is only an error if this is propagated to the exit + // node of the function (i.e. there is a path with an uninitialized access). + nodeInfo.uninitializedVariableAccesses.insert(&variableOccurrence); + } + break; + case VariableOccurrence::Kind::Declaration: + unassignedVariables.insert(&variableOccurrence.declaration()); + break; + } } + nodeInfo.unassignedVariablesAtExit = std::move(unassignedVariables); - for (auto const& exit: node->exits) - { - auto& unassignedAtExit = unassigned[exit]; - auto oldSize = unassignedAtExit.size(); - unassignedAtExit.insert(unassignedAtNode.begin(), unassignedAtNode.end()); - // (re)traverse an exit, if we are on a path with new unassigned return values to consider - // this will terminate, since there is only a finite number of unassigned return values - if (unassignedAtExit.size() > oldSize) - nodesToTraverse.push(exit); - } + // Propagate changes to all exits and queue them for traversal, if needed. + for (auto const& exit: currentNode->exits) + if (nodeInfos[exit].propagateFrom(nodeInfo)) + nodesToTraverse.insert(exit); } - if (!unassigned[_functionExit].empty()) + auto const& exitInfo = nodeInfos[_exit]; + if (!exitInfo.uninitializedVariableAccesses.empty()) { - vector unassignedOrdered( - unassigned[_functionExit].begin(), - unassigned[_functionExit].end() + vector uninitializedAccessesOrdered( + exitInfo.uninitializedVariableAccesses.begin(), + exitInfo.uninitializedVariableAccesses.end() ); - sort( - unassignedOrdered.begin(), - unassignedOrdered.end(), - [](VariableDeclaration const* lhs, VariableDeclaration const* rhs) -> bool { - return lhs->id() < rhs->id(); + boost::range::sort( + uninitializedAccessesOrdered, + [](VariableOccurrence const* lhs, VariableOccurrence const* rhs) -> bool + { + return *lhs < *rhs; } ); - for (auto const* returnVal: unassignedOrdered) + + for (auto const* variableOccurrence: uninitializedAccessesOrdered) { SecondarySourceLocation ssl; - for (CFGNode* lastNodeBeforeExit: _functionExit->entries) - if (unassigned[lastNodeBeforeExit].count(returnVal)) - { - if (!!lastNodeBeforeExit->block.returnStatement) - ssl.append("Problematic return:", lastNodeBeforeExit->block.returnStatement->location()); - else - ssl.append("Problematic end of function:", _function.location()); - } + if (variableOccurrence->occurrence()) + ssl.append("The variable was declared here.", variableOccurrence->declaration().location()); m_errorReporter.typeError( - returnVal->location(), + variableOccurrence->occurrence() ? + variableOccurrence->occurrence()->location() : + variableOccurrence->declaration().location(), ssl, - "This variable is of storage pointer type and might be returned without assignment and " - "could be used uninitialized. Assign the variable (potentially from itself) " - "to fix this error." + string("This variable is of storage pointer type and can be ") + + (variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") + + " without prior assignment, which would lead to undefined behaviour." ); } } } -bool ControlFlowAnalyzer::visit(ContractDefinition const& _contract) -{ - m_contract = &_contract; - return true; -} - -void ControlFlowAnalyzer::endVisit(ContractDefinition const&) +void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const { - m_contract = nullptr; -} + // collect all nodes reachable from the entry point + std::set reachable = BreadthFirstSearch{{_entry}}.run( + [](CFGNode const* _node, auto&& _addChild) { + for (CFGNode const* exit: _node->exits) + _addChild(exit); + } + ).visited; + + // traverse all paths backwards from exit and revert + // and extract (valid) source locations of unreachable nodes into sorted set + std::set unreachable; + BreadthFirstSearch{{_exit, _revert}}.run( + [&](CFGNode const* _node, auto&& _addChild) { + if (!reachable.count(_node) && !_node->location.isEmpty()) + unreachable.insert(_node->location); + for (CFGNode const* entry: _node->entries) + _addChild(entry); + } + ); -void ControlFlowAnalyzer::endVisit(FireAllRulesStatement const& _fars) -{ - solAssert(m_contract, "got a statement not in a contract"); - _fars.annotation().contract = m_contract; + for (auto it = unreachable.begin(); it != unreachable.end();) + { + SourceLocation location = *it++; + // Extend the location, as long as the next location overlaps (unreachable is sorted). + for (; it != unreachable.end() && it->start <= location.end; ++it) + location.end = std::max(location.end, it->end); + m_errorReporter.warning(location, "Unreachable code."); + } } - diff --git a/libsolidity/analysis/ControlFlowAnalyzer.h b/libsolidity/analysis/ControlFlowAnalyzer.h index dc5d40c13..8f6d3d478 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.h +++ b/libsolidity/analysis/ControlFlowAnalyzer.h @@ -18,7 +18,6 @@ #pragma once #include - #include namespace dev @@ -29,12 +28,12 @@ namespace solidity class ControlFlowAnalyzer: private ASTConstVisitor { public: - explicit ControlFlowAnalyzer(CFG const& _cfg, ErrorReporter& _errorReporter): + explicit ControlFlowAnalyzer(CFG const& _cfg, langutil::ErrorReporter& _errorReporter): m_cfg(_cfg), m_errorReporter(_errorReporter) {} bool analyze(ASTNode const& _astRoot); - virtual bool visit(FunctionDefinition const& _function) override; + bool visit(FunctionDefinition const& _function) override; // the following 3 function are used to resolve the control flow of rule engine's fireAllRules statement // Currently we can't find a more suitable location for these functions @@ -43,18 +42,14 @@ class ControlFlowAnalyzer: private ASTConstVisitor virtual void endVisit(FireAllRulesStatement const& _contract) override; private: - static std::set variablesAssignedInNode(CFGNode const *node); - void checkUnassignedStorageReturnValues( - FunctionDefinition const& _function, - CFGNode const* _functionEntry, - CFGNode const* _functionExit - ) const; + /// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit. + void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const; + /// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert + /// that can not be reached from @param _entry. + void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const; CFG const& m_cfg; - ErrorReporter& m_errorReporter; - - /// Current contract - ContractDefinition const* m_contract; + langutil::ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 5bd39da39..fb766eea9 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -18,11 +18,15 @@ #include using namespace dev; +using namespace langutil; using namespace solidity; using namespace std; ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow): - m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry) + m_nodeContainer(_nodeContainer), + m_currentNode(_functionFlow.entry), + m_returnNode(_functionFlow.exit), + m_revertNode(_functionFlow.revert) { } @@ -37,26 +41,8 @@ unique_ptr ControlFlowBuilder::createFunctionFlow( functionFlow->revert = _nodeContainer.newNode(); ControlFlowBuilder builder(_nodeContainer, *functionFlow); builder.appendControlFlow(_function); - connect(builder.m_currentNode, functionFlow->exit); - return functionFlow; -} - -unique_ptr ControlFlowBuilder::createModifierFlow( - CFG::NodeContainer& _nodeContainer, - ModifierDefinition const& _modifier -) -{ - auto modifierFlow = unique_ptr(new ModifierFlow()); - modifierFlow->entry = _nodeContainer.newNode(); - modifierFlow->exit = _nodeContainer.newNode(); - modifierFlow->revert = _nodeContainer.newNode(); - modifierFlow->placeholderEntry = _nodeContainer.newNode(); - modifierFlow->placeholderExit = _nodeContainer.newNode(); - ControlFlowBuilder builder(_nodeContainer, *modifierFlow); - builder.appendControlFlow(_modifier); - connect(builder.m_currentNode, modifierFlow->exit); - return modifierFlow; + return functionFlow; } bool ControlFlowBuilder::visit(BinaryOperation const& _operation) @@ -68,6 +54,7 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) case Token::Or: case Token::And: { + visitNode(_operation); appendControlFlow(_operation.leftExpression()); auto nodes = splitFlow<2>(); @@ -77,14 +64,14 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) return false; } default: - break; + return ASTConstVisitor::visit(_operation); } - return ASTConstVisitor::visit(_operation); } bool ControlFlowBuilder::visit(Conditional const& _conditional) { solAssert(!!m_currentNode, ""); + visitNode(_conditional); _conditional.condition().accept(*this); @@ -101,6 +88,7 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional) bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) { solAssert(!!m_currentNode, ""); + visitNode(_ifStatement); _ifStatement.condition().accept(*this); @@ -121,6 +109,7 @@ bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) bool ControlFlowBuilder::visit(ForStatement const& _forStatement) { solAssert(!!m_currentNode, ""); + visitNode(_forStatement); if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); @@ -154,6 +143,7 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement) bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement) { solAssert(!!m_currentNode, ""); + visitNode(_whileStatement); if (_whileStatement.isDoWhile()) { @@ -198,107 +188,72 @@ bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement) return false; } -bool ControlFlowBuilder::visit(Break const&) +bool ControlFlowBuilder::visit(Break const& _break) { solAssert(!!m_currentNode, ""); solAssert(!!m_breakJump, ""); + visitNode(_break); connect(m_currentNode, m_breakJump); m_currentNode = newLabel(); return false; } -bool ControlFlowBuilder::visit(Continue const&) +bool ControlFlowBuilder::visit(Continue const& _continue) { solAssert(!!m_currentNode, ""); solAssert(!!m_continueJump, ""); + visitNode(_continue); connect(m_currentNode, m_continueJump); m_currentNode = newLabel(); return false; } -bool ControlFlowBuilder::visit(Throw const&) +bool ControlFlowBuilder::visit(Throw const& _throw) { solAssert(!!m_currentNode, ""); - solAssert(!!m_currentFunctionFlow.revert, ""); - connect(m_currentNode, m_currentFunctionFlow.revert); + solAssert(!!m_revertNode, ""); + visitNode(_throw); + connect(m_currentNode, m_revertNode); m_currentNode = newLabel(); return false; } -bool ControlFlowBuilder::visit(Block const&) -{ - solAssert(!!m_currentNode, ""); - createLabelHere(); - return true; -} - -void ControlFlowBuilder::endVisit(Block const&) -{ - solAssert(!!m_currentNode, ""); - createLabelHere(); -} - -bool ControlFlowBuilder::visit(Return const& _return) -{ - solAssert(!!m_currentNode, ""); - solAssert(!!m_currentFunctionFlow.exit, ""); - solAssert(!m_currentNode->block.returnStatement, ""); - m_currentNode->block.returnStatement = &_return; - connect(m_currentNode, m_currentFunctionFlow.exit); - m_currentNode = newLabel(); - return true; -} - - bool ControlFlowBuilder::visit(PlaceholderStatement const&) { solAssert(!!m_currentNode, ""); - auto modifierFlow = dynamic_cast(&m_currentFunctionFlow); - solAssert(!!modifierFlow, ""); - - connect(m_currentNode, modifierFlow->placeholderEntry); + solAssert(!!m_placeholderEntry, ""); + solAssert(!!m_placeholderExit, ""); + connect(m_currentNode, m_placeholderEntry); m_currentNode = newLabel(); - - connect(modifierFlow->placeholderExit, m_currentNode); + connect(m_placeholderExit, m_currentNode); return false; } -bool ControlFlowBuilder::visitNode(ASTNode const& node) -{ - solAssert(!!m_currentNode, ""); - if (auto const* expression = dynamic_cast(&node)) - m_currentNode->block.expressions.emplace_back(expression); - else if (auto const* variableDeclaration = dynamic_cast(&node)) - m_currentNode->block.variableDeclarations.emplace_back(variableDeclaration); - else if (auto const* assembly = dynamic_cast(&node)) - m_currentNode->block.inlineAssemblyStatements.emplace_back(assembly); - - return true; -} - bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) { solAssert(!!m_currentNode, ""); solAssert(!!_functionCall.expression().annotation().type, ""); - if (auto functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type)) + if (auto functionType = dynamic_cast(_functionCall.expression().annotation().type)) switch (functionType->kind()) { case FunctionType::Kind::Revert: - solAssert(!!m_currentFunctionFlow.revert, ""); + solAssert(!!m_revertNode, ""); + visitNode(_functionCall); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - connect(m_currentNode, m_currentFunctionFlow.revert); + connect(m_currentNode, m_revertNode); m_currentNode = newLabel(); return false; case FunctionType::Kind::Require: case FunctionType::Kind::Assert: { - solAssert(!!m_currentFunctionFlow.revert, ""); + solAssert(!!m_revertNode, ""); + visitNode(_functionCall); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); - connect(m_currentNode, m_currentFunctionFlow.revert); + connect(m_currentNode, m_revertNode); auto nextNode = newLabel(); connect(m_currentNode, nextNode); m_currentNode = nextNode; @@ -310,6 +265,194 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) return ASTConstVisitor::visit(_functionCall); } +bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation) +{ + if (auto arguments = _modifierInvocation.arguments()) + for (auto& argument: *arguments) + appendControlFlow(*argument); + + auto modifierDefinition = dynamic_cast( + _modifierInvocation.name()->annotation().referencedDeclaration + ); + if (!modifierDefinition) return false; + solAssert(!!modifierDefinition, ""); + solAssert(!!m_returnNode, ""); + + m_placeholderEntry = newLabel(); + m_placeholderExit = newLabel(); + + appendControlFlow(*modifierDefinition); + connect(m_currentNode, m_returnNode); + + m_currentNode = m_placeholderEntry; + m_returnNode = m_placeholderExit; + + m_placeholderEntry = nullptr; + m_placeholderExit = nullptr; + + return false; +} + +bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition) +{ + for (auto const& parameter: _functionDefinition.parameters()) + appendControlFlow(*parameter); + + for (auto const& returnParameter: _functionDefinition.returnParameters()) + { + appendControlFlow(*returnParameter); + m_returnNode->variableOccurrences.emplace_back( + *returnParameter, + VariableOccurrence::Kind::Return, + nullptr + ); + + } + + for (auto const& modifier: _functionDefinition.modifiers()) + appendControlFlow(*modifier); + + appendControlFlow(_functionDefinition.body()); + + connect(m_currentNode, m_returnNode); + m_currentNode = nullptr; + + return false; +} + +bool ControlFlowBuilder::visit(Return const& _return) +{ + solAssert(!!m_currentNode, ""); + solAssert(!!m_returnNode, ""); + visitNode(_return); + if (_return.expression()) + { + appendControlFlow(*_return.expression()); + // Returns with return expression are considered to be assignments to the return parameters. + for (auto returnParameter: _return.annotation().functionReturnParameters->parameters()) + m_currentNode->variableOccurrences.emplace_back( + *returnParameter, + VariableOccurrence::Kind::Assignment, + &_return + ); + } + connect(m_currentNode, m_returnNode); + m_currentNode = newLabel(); + return false; +} + +bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName) +{ + visitNode(_functionTypeName); + // Do not visit the parameters and return values of a function type name. + // We do not want to consider them as variable declarations for the control flow graph. + return false; +} + +bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) +{ + solAssert(!!m_currentNode, ""); + visitNode(_inlineAssembly); + for (auto const& ref: _inlineAssembly.annotation().externalReferences) + { + if (auto variableDeclaration = dynamic_cast(ref.second.declaration)) + m_currentNode->variableOccurrences.emplace_back( + *variableDeclaration, + VariableOccurrence::Kind::InlineAssembly, + &_inlineAssembly + ); + } + return true; +} + +bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(!!m_currentNode, ""); + visitNode(_variableDeclaration); + + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Declaration, + nullptr + ); + + // Handle declaration with immediate assignment. + if (_variableDeclaration.value()) + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Assignment, + _variableDeclaration.value().get() + ); + // Function arguments are considered to be immediately assigned as well (they are "externally assigned"). + else if (_variableDeclaration.isCallableParameter() && !_variableDeclaration.isReturnParameter()) + m_currentNode->variableOccurrences.emplace_back( + _variableDeclaration, + VariableOccurrence::Kind::Assignment, + nullptr + ); + return true; +} + +bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement) +{ + solAssert(!!m_currentNode, ""); + visitNode(_variableDeclarationStatement); + + for (auto const& var: _variableDeclarationStatement.declarations()) + if (var) + var->accept(*this); + if (_variableDeclarationStatement.initialValue()) + { + _variableDeclarationStatement.initialValue()->accept(*this); + for (size_t i = 0; i < _variableDeclarationStatement.declarations().size(); i++) + if (auto const& var = _variableDeclarationStatement.declarations()[i]) + { + auto expression = _variableDeclarationStatement.initialValue(); + if (auto tupleExpression = dynamic_cast(expression)) + if (tupleExpression->components().size() > 1) + { + solAssert(tupleExpression->components().size() > i, ""); + expression = tupleExpression->components()[i].get(); + } + while (auto tupleExpression = dynamic_cast(expression)) + if (tupleExpression->components().size() == 1) + expression = tupleExpression->components().front().get(); + else + break; + m_currentNode->variableOccurrences.emplace_back( + *var, + VariableOccurrence::Kind::Assignment, + expression + ); + } + } + return false; +} + +bool ControlFlowBuilder::visit(Identifier const& _identifier) +{ + solAssert(!!m_currentNode, ""); + visitNode(_identifier); + + if (auto const* variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration)) + m_currentNode->variableOccurrences.emplace_back( + *variableDeclaration, + static_cast(_identifier).annotation().lValueRequested ? + VariableOccurrence::Kind::Assignment : + VariableOccurrence::Kind::Access, + &_identifier + ); + + return true; +} + +bool ControlFlowBuilder::visitNode(ASTNode const& _node) +{ + solAssert(!!m_currentNode, ""); + m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, _node.location()); + return true; +} + void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) { _node.accept(*this); diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index e9d96e5fc..b17483954 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -38,28 +38,38 @@ class ControlFlowBuilder: private ASTConstVisitor CFG::NodeContainer& _nodeContainer, FunctionDefinition const& _function ); - static std::unique_ptr createModifierFlow( - CFG::NodeContainer& _nodeContainer, - ModifierDefinition const& _modifier - ); private: explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow); - virtual bool visit(BinaryOperation const& _operation) override; - virtual bool visit(Conditional const& _conditional) override; - virtual bool visit(IfStatement const& _ifStatement) override; - virtual bool visit(ForStatement const& _forStatement) override; - virtual bool visit(WhileStatement const& _whileStatement) override; - virtual bool visit(Break const&) override; - virtual bool visit(Continue const&) override; - virtual bool visit(Throw const&) override; - virtual bool visit(Block const&) override; - virtual void endVisit(Block const&) override; - virtual bool visit(Return const& _return) override; - virtual bool visit(PlaceholderStatement const&) override; - virtual bool visit(FunctionCall const& _functionCall) override; + // Visits for constructing the control flow. + bool visit(BinaryOperation const& _operation) override; + bool visit(Conditional const& _conditional) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(ForStatement const& _forStatement) override; + bool visit(WhileStatement const& _whileStatement) override; + bool visit(Break const&) override; + bool visit(Continue const&) override; + bool visit(Throw const&) override; + bool visit(PlaceholderStatement const&) override; + bool visit(FunctionCall const& _functionCall) override; + bool visit(ModifierInvocation const& _modifierInvocation) override; + + // Visits for constructing the control flow as well as filling variable occurrences. + bool visit(FunctionDefinition const& _functionDefinition) override; + bool visit(Return const& _return) override; + + // Visits for filling variable occurrences. + bool visit(FunctionTypeName const& _functionTypeName) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(VariableDeclaration const& _variableDeclaration) override; + bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(Identifier const& _identifier) override; + +protected: + bool visitNode(ASTNode const&) override; +private: /// Appends the control flow of @a _node to the current control flow. void appendControlFlow(ASTNode const& _node); @@ -72,12 +82,6 @@ class ControlFlowBuilder: private ASTConstVisitor /// Creates an arc from @a _from to @a _to. static void connect(CFGNode* _from, CFGNode* _to); - -protected: - virtual bool visitNode(ASTNode const& node) override; - -private: - /// Splits the control flow starting at the current node into n paths. /// m_currentNode is set to nullptr and has to be set manually or /// using mergeFlow later. @@ -114,17 +118,18 @@ class ControlFlowBuilder: private ASTConstVisitor CFG::NodeContainer& m_nodeContainer; - /// The control flow of the function that is currently parsed. - /// Note: this can also be a ModifierFlow - FunctionFlow const& m_currentFunctionFlow; - CFGNode* m_currentNode = nullptr; + CFGNode* m_returnNode = nullptr; + CFGNode* m_revertNode = nullptr; /// The current jump destination of break Statements. CFGNode* m_breakJump = nullptr; /// The current jump destination of continue Statements. CFGNode* m_continueJump = nullptr; + CFGNode* m_placeholderEntry = nullptr; + CFGNode* m_placeholderExit = nullptr; + /// Helper class that replaces the break and continue jump destinations for the /// current scope and restores the originals at the end of the scope. class BreakContinueScope diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp index 9b3da0ebc..8960166a0 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -16,32 +16,26 @@ */ #include -#include +#include #include - #include using namespace std; +using namespace langutil; using namespace dev::solidity; bool CFG::constructFlow(ASTNode const& _astRoot) { _astRoot.accept(*this); - applyModifiers(); return Error::containsOnlyWarnings(m_errorReporter.errors()); } -bool CFG::visit(ModifierDefinition const& _modifier) -{ - m_modifierControlFlow[&_modifier] = ControlFlowBuilder::createModifierFlow(m_nodeContainer, _modifier); - return false; -} - bool CFG::visit(FunctionDefinition const& _function) { - m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); + if (_function.isImplemented()) + m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function); return false; } @@ -56,81 +50,3 @@ CFGNode* CFG::NodeContainer::newNode() m_nodes.emplace_back(new CFGNode()); return m_nodes.back().get(); } - -void CFG::applyModifiers() -{ - for (auto const& function: m_functionControlFlow) - { - for (auto const& modifierInvocation: boost::adaptors::reverse(function.first->modifiers())) - { - if (auto modifierDefinition = dynamic_cast( - modifierInvocation->name()->annotation().referencedDeclaration - )) - { - solAssert(m_modifierControlFlow.count(modifierDefinition), ""); - applyModifierFlowToFunctionFlow(*m_modifierControlFlow[modifierDefinition], function.second.get()); - } - } - } -} - -void CFG::applyModifierFlowToFunctionFlow( - ModifierFlow const& _modifierFlow, - FunctionFlow* _functionFlow -) -{ - solAssert(!!_functionFlow, ""); - - map copySrcToCopyDst; - - // inherit the revert node of the function - copySrcToCopyDst[_modifierFlow.revert] = _functionFlow->revert; - - // replace the placeholder nodes by the function entry and exit - copySrcToCopyDst[_modifierFlow.placeholderEntry] = _functionFlow->entry; - copySrcToCopyDst[_modifierFlow.placeholderExit] = _functionFlow->exit; - - stack nodesToCopy; - nodesToCopy.push(_modifierFlow.entry); - - // map the modifier entry to a new node that will become the new function entry - copySrcToCopyDst[_modifierFlow.entry] = m_nodeContainer.newNode(); - - while (!nodesToCopy.empty()) - { - CFGNode* copySrcNode = nodesToCopy.top(); - nodesToCopy.pop(); - - solAssert(copySrcToCopyDst.count(copySrcNode), ""); - - CFGNode* copyDstNode = copySrcToCopyDst[copySrcNode]; - - copyDstNode->block = copySrcNode->block; - for (auto const& entry: copySrcNode->entries) - { - if (!copySrcToCopyDst.count(entry)) - { - copySrcToCopyDst[entry] = m_nodeContainer.newNode(); - nodesToCopy.push(entry); - } - copyDstNode->entries.emplace_back(copySrcToCopyDst[entry]); - } - for (auto const& exit: copySrcNode->exits) - { - if (!copySrcToCopyDst.count(exit)) - { - copySrcToCopyDst[exit] = m_nodeContainer.newNode(); - nodesToCopy.push(exit); - } - copyDstNode->exits.emplace_back(copySrcToCopyDst[exit]); - } - } - - // if the modifier control flow never reached its exit node, - // we need to create a new (disconnected) exit node now - if (!copySrcToCopyDst.count(_modifierFlow.exit)) - copySrcToCopyDst[_modifierFlow.exit] = m_nodeContainer.newNode(); - - _functionFlow->entry = copySrcToCopyDst[_modifierFlow.entry]; - _functionFlow->exit = copySrcToCopyDst[_modifierFlow.exit]; -} \ No newline at end of file diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h index c646e4f18..506cce03f 100644 --- a/libsolidity/analysis/ControlFlowGraph.h +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -19,7 +19,8 @@ #include #include -#include +#include +#include #include #include @@ -31,32 +32,66 @@ namespace dev namespace solidity { -/** Basic Control Flow Block. - * Basic block of control flow. Consists of a set of (unordered) AST nodes - * for which control flow is always linear. A basic control flow block - * encompasses at most one scope. Reverts are considered to break the control - * flow. - * @todo Handle function calls correctly. So far function calls are not considered - * to change the control flow. +/** + * Occurrence of a variable in a block of control flow. + * Stores the declaration of the referenced variable, the + * kind of the occurrence and possibly the node at which + * it occurred. */ -struct ControlFlowBlock +class VariableOccurrence { - /// All variable declarations inside this control flow block. - std::vector variableDeclarations; - /// All expressions inside this control flow block (this includes all subexpressions!). - std::vector expressions; - /// All inline assembly statements inside in this control flow block. - std::vector inlineAssemblyStatements; - /// If control flow returns in this node, the return statement is stored in returnStatement, - /// otherwise returnStatement is nullptr. - Return const* returnStatement = nullptr; +public: + enum class Kind + { + Declaration, + Access, + Return, + Assignment, + InlineAssembly + }; + VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence): + m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence) + { + } + + /// Defines a deterministic order on variable occurrences. + bool operator<(VariableOccurrence const& _rhs) const + { + if (m_occurrence && _rhs.m_occurrence) + { + if (m_occurrence->id() < _rhs.m_occurrence->id()) return true; + if (_rhs.m_occurrence->id() < m_occurrence->id()) return false; + } + else if (_rhs.m_occurrence) + return true; + else if (m_occurrence) + return false; + + using KindCompareType = std::underlying_type::type; + return + std::make_pair(m_declaration.id(), static_cast(m_occurrenceKind)) < + std::make_pair(_rhs.m_declaration.id(), static_cast(_rhs.m_occurrenceKind)) + ; + } + + VariableDeclaration const& declaration() const { return m_declaration; } + Kind kind() const { return m_occurrenceKind; }; + ASTNode const* occurrence() const { return m_occurrence; } +private: + /// Declaration of the occurring variable. + VariableDeclaration const& m_declaration; + /// Kind of occurrence. + Kind m_occurrenceKind = Kind::Access; + /// AST node at which the variable occurred, if available (may be nullptr). + ASTNode const* m_occurrence = nullptr; }; -/** Node of the Control Flow Graph. - * The control flow is a directed graph connecting control flow blocks. - * An arc between two nodes indicates that the control flow can possibly - * move from its start node to its end node during execution. - */ +/** + * Node of the Control Flow Graph. + * The control flow is a directed graph connecting control flow blocks. + * An arc between two nodes indicates that the control flow can possibly + * move from its start node to its end node during execution. + */ struct CFGNode { /// Entry nodes. All CFG nodes from which control flow may move into this node. @@ -64,14 +99,17 @@ struct CFGNode /// Exit nodes. All CFG nodes to which control flow may continue after this node. std::vector exits; - /// Control flow in the node. - ControlFlowBlock block; + /// Variable occurrences in the node. + std::vector variableOccurrences; + // Source location of this control flow block. + langutil::SourceLocation location; }; /** Describes the control flow of a function. */ struct FunctionFlow { - virtual ~FunctionFlow() {} + virtual ~FunctionFlow() = default; + /// Entry node. Control flow of the function starts here. /// This node is empty and does not have any entries. CFGNode* entry = nullptr; @@ -85,28 +123,14 @@ struct FunctionFlow CFGNode* revert = nullptr; }; -/** Describes the control flow of a modifier. - * Every placeholder breaks the control flow. The node preceding the - * placeholder is assigned placeholderEntry as exit and the node - * following the placeholder is assigned placeholderExit as entry. - */ -struct ModifierFlow: FunctionFlow -{ - /// Control flow leading towards a placeholder exit in placeholderEntry. - CFGNode* placeholderEntry = nullptr; - /// Control flow coming from a placeholder enter from placeholderExit. - CFGNode* placeholderExit = nullptr; -}; - class CFG: private ASTConstVisitor { public: - explicit CFG(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + explicit CFG(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool constructFlow(ASTNode const& _astRoot); - virtual bool visit(ModifierDefinition const& _modifier) override; - virtual bool visit(FunctionDefinition const& _function) override; + bool visit(FunctionDefinition const& _function) override; FunctionFlow const& functionFlow(FunctionDefinition const& _function) const; @@ -118,22 +142,8 @@ class CFG: private ASTConstVisitor std::vector> m_nodes; }; private: - /// Initially the control flow for all functions *ignoring* modifiers and for - /// all modifiers is constructed. Afterwards the control flow of functions - /// is adjusted by applying all modifiers. - void applyModifiers(); - - /// Creates a copy of the modifier flow @a _modifierFlow, while replacing the - /// placeholder entry and exit with the function entry and exit, as well as - /// replacing the modifier revert node with the function's revert node. - /// The resulting control flow is the new function flow with the modifier applied. - /// @a _functionFlow is updated in-place. - void applyModifierFlowToFunctionFlow( - ModifierFlow const& _modifierFlow, - FunctionFlow* _functionFlow - ); - - ErrorReporter& m_errorReporter; + + langutil::ErrorReporter& m_errorReporter; /// Node container. /// All nodes allocated during the construction of the control flow graph @@ -141,7 +151,6 @@ class CFG: private ASTConstVisitor NodeContainer m_nodeContainer; std::map> m_functionControlFlow; - std::map> m_modifierControlFlow; }; } diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp index cf12a49d5..463510436 100644 --- a/libsolidity/analysis/DeclarationContainer.cpp +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -21,6 +21,7 @@ */ #include + #include #include #include diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h index 9d7a17a3b..9ef5e1c43 100644 --- a/libsolidity/analysis/DeclarationContainer.h +++ b/libsolidity/analysis/DeclarationContainer.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,11 +22,10 @@ #pragma once +#include +#include #include #include -#include - -#include namespace dev { diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index c1b97defc..108a18564 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,12 +22,14 @@ */ #include + #include -#include #include +#include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) @@ -40,7 +42,7 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) bool DocStringAnalyser::visit(ContractDefinition const& _contract) { - static const set validTags = set{"author", "title", "dev", "notice"}; + static set const validTags = set{"author", "title", "dev", "notice"}; parseDocStrings(_contract, _contract.annotation(), validTags, "contracts"); return true; @@ -97,7 +99,7 @@ void DocStringAnalyser::handleConstructor( DocumentedAnnotation& _annotation ) { - static const set validTags = set{"author", "dev", "notice", "param"}; + static set const validTags = set{"author", "dev", "notice", "param"}; parseDocStrings(_node, _annotation, validTags, "constructor"); checkParameters(_callable, _annotation); } @@ -108,7 +110,7 @@ void DocStringAnalyser::handleCallable( DocumentedAnnotation& _annotation ) { - static const set validTags = set{"author", "dev", "notice", "return", "param"}; + static set const validTags = set{"author", "dev", "notice", "return", "param"}; parseDocStrings(_node, _annotation, validTags, "functions"); checkParameters(_callable, _annotation); } diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index 5d339428d..8d3658ac8 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -25,13 +25,16 @@ #include +namespace langutil +{ +class ErrorReporter; +} + namespace dev { namespace solidity { -class ErrorReporter; - /** * Parses and analyses the doc strings. * Stores the parsing results in the AST annotations and reports errors. @@ -39,14 +42,14 @@ class ErrorReporter; class DocStringAnalyser: private ASTConstVisitor { public: - DocStringAnalyser(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + DocStringAnalyser(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool analyseDocStrings(SourceUnit const& _sourceUnit); private: - virtual bool visit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual bool visit(ModifierDefinition const& _modifier) override; - virtual bool visit(EventDefinition const& _event) override; + bool visit(ContractDefinition const& _contract) override; + bool visit(FunctionDefinition const& _function) override; + bool visit(ModifierDefinition const& _modifier) override; + bool visit(EventDefinition const& _event) override; void checkParameters( CallableDeclaration const& _callable, @@ -75,7 +78,7 @@ class DocStringAnalyser: private ASTConstVisitor void appendError(std::string const& _description); bool m_errorOccured = false; - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 422f01672..f5becfcfc 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -21,10 +21,12 @@ * Container of the (implicit and explicit) global objects. */ -#include #include + #include +#include #include +#include using namespace std; @@ -33,38 +35,53 @@ namespace dev namespace solidity { -GlobalContext::GlobalContext(): -m_magicVariables(vector>{ - make_shared("abi", make_shared(MagicType::Kind::ABI)), - make_shared("addmod", make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("assert", make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Assert, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("block", make_shared(MagicType::Kind::Block)), - make_shared("blockhash", make_shared(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, FunctionType::SpecialModifier::Default, false, StateMutability::View)), - make_shared("ecrecover", make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("eni", make_shared(strings(), strings{"string memory"}, FunctionType::Kind::ENI, FunctionType::SpecialModifier::Default, true, StateMutability::Pure)), - make_shared("isValidator", make_shared(strings{"address"}, strings{"bool"}, FunctionType::Kind::IsValidator, FunctionType::SpecialModifier::Default, true, StateMutability::Pure)), - make_shared("gasleft", make_shared(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, FunctionType::SpecialModifier::Default, false, StateMutability::View)), - make_shared("keccak256", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("log0", make_shared(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), - make_shared("log1", make_shared(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), - make_shared("log2", make_shared(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), - make_shared("log3", make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), - make_shared("log4", make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), - make_shared("msg", make_shared(MagicType::Kind::Message)), - make_shared("mulmod", make_shared(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("now", make_shared(256)), - make_shared("rand", make_shared(strings(), strings{"uint256"}, FunctionType::Kind::Rand, FunctionType::SpecialModifier::Default, true, StateMutability::NonPayable)), - make_shared("require", make_shared(strings{"bool"}, strings{}, FunctionType::Kind::Require, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("require", make_shared(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("revert", make_shared(strings(), strings(), FunctionType::Kind::Revert, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("revert", make_shared(strings{"string memory"}, strings(), FunctionType::Kind::Revert, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("ripemd160", make_shared(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("selfdestruct", make_shared(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared("sha256", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, FunctionType::SpecialModifier::Default, false, StateMutability::Pure)), - make_shared("sha3", make_shared(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, FunctionType::SpecialModifier::Default, true, StateMutability::Pure)), - make_shared("suicide", make_shared(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared("tx", make_shared(MagicType::Kind::Transaction)) -}) +inline vector> constructMagicVariables() +{ + static auto const magicVarDecl = [](string const& _name, Type const* _type) { + return make_shared(_name, _type); + }; + + return { + magicVarDecl("abi", TypeProvider::magic(MagicType::Kind::ABI)), + magicVarDecl("addmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), + magicVarDecl("assert", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), + magicVarDecl("block", TypeProvider::magic(MagicType::Kind::Block)), + magicVarDecl("blockhash", TypeProvider::function(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)), + magicVarDecl("ecrecover", TypeProvider::function(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), + magicVarDecl("eni", TypeProvider::function(strings(), strings{"string memory"}, FunctionType::Kind::ENI, FunctionType::SpecialModifier::Default, true, StateMutability::Pure)), + magicVarDecl("gasleft", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), + magicVarDecl("isValidator", TypeProvider::function(strings{"address"}, strings{"bool"}, FunctionType::Kind::IsValidator, FunctionType::SpecialModifier::Default, true, StateMutability::Pure)), + magicVarDecl("keccak256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), + magicVarDecl("log0", TypeProvider::function(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), + magicVarDecl("log1", TypeProvider::function(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), + magicVarDecl("log2", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log2)), + magicVarDecl("log3", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log3)), + magicVarDecl("log4", TypeProvider::function(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)), + magicVarDecl("msg", TypeProvider::magic(MagicType::Kind::Message)), + magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), + magicVarDecl("now", TypeProvider::uint256()), + magicVarDecl("rand", TypeProvider::function(strings(), strings{"uint256"}, FunctionType::Kind::Rand, FunctionType::SpecialModifier::Default, true, StateMutability::NonPayable)), + magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), + magicVarDecl("require", TypeProvider::function(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), + magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), + magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), + magicVarDecl("sha3", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), + magicVarDecl("suicide", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), + magicVarDecl("tx", TypeProvider::magic(MagicType::Kind::Transaction)), + magicVarDecl("type", TypeProvider::function( + strings{"address"} /* accepts any contract type, handled by the type checker */, + strings{} /* returns a MagicType, handled by the type checker */, + FunctionType::Kind::MetaType, + false, + StateMutability::Pure + )), + }; +} + +GlobalContext::GlobalContext(): m_magicVariables{constructMagicVariables()} { } @@ -85,7 +102,7 @@ vector GlobalContext::declarations() const MagicVariableDeclaration const* GlobalContext::currentThis() const { if (!m_thisPointer[m_currentContract]) - m_thisPointer[m_currentContract] = make_shared("this", make_shared(*m_currentContract)); + m_thisPointer[m_currentContract] = make_shared("this", TypeProvider::contract(*m_currentContract)); return m_thisPointer[m_currentContract].get(); } @@ -93,7 +110,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const MagicVariableDeclaration const* GlobalContext::currentSuper() const { if (!m_superPointer[m_currentContract]) - m_superPointer[m_currentContract] = make_shared("super", make_shared(*m_currentContract, true)); + m_superPointer[m_currentContract] = make_shared("super", TypeProvider::contract(*m_currentContract, true)); return m_superPointer[m_currentContract].get(); } diff --git a/libsolidity/analysis/GlobalContext.h b/libsolidity/analysis/GlobalContext.h index 4ed08711c..09611c41f 100644 --- a/libsolidity/analysis/GlobalContext.h +++ b/libsolidity/analysis/GlobalContext.h @@ -22,12 +22,12 @@ #pragma once -#include -#include +#include +#include #include #include -#include -#include +#include +#include namespace dev { diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index 03ef40f8b..385d4065e 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,14 +22,14 @@ #include -#include #include -#include +#include +#include #include - #include using namespace std; +using namespace langutil; namespace dev { @@ -37,16 +37,17 @@ namespace solidity { NameAndTypeResolver::NameAndTypeResolver( - vector const& _globals, + GlobalContext& _globalContext, map>& _scopes, ErrorReporter& _errorReporter ) : m_scopes(_scopes), - m_errorReporter(_errorReporter) + m_errorReporter(_errorReporter), + m_globalContext(_globalContext) { if (!m_scopes[nullptr]) m_scopes[nullptr].reset(new DeclarationContainer()); - for (Declaration const* declaration: _globals) + for (Declaration const* declaration: _globalContext.declarations()) { solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration."); } @@ -57,9 +58,9 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode // The helper registers all declarations in m_scopes as a side-effect of its construction. try { - DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope); + DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, m_globalContext, _currentScope); } - catch (FatalError const&) + catch (langutil::FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. @@ -90,13 +91,13 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, mapsymbolAliases().empty()) for (auto const& alias: imp->symbolAliases()) { - auto declarations = scope->second->resolveName(alias.first->name(), false); + auto declarations = scope->second->resolveName(alias.symbol->name(), false); if (declarations.empty()) { m_errorReporter.declarationError( imp->location(), "Declaration \"" + - alias.first->name() + + alias.symbol->name() + "\" not found in \"" + path + "\" (referenced as \"" + @@ -108,7 +109,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, maplocation(), true, false, m_errorReporter + target, *declaration, alias.alias.get(), &alias.location, true, false, m_errorReporter )) error = true; } @@ -157,7 +158,7 @@ bool NameAndTypeResolver::resolveNamesAndTypes(ASTNode& _node, bool _resolveInsi { return resolveNamesAndTypesInternal(_node, _resolveInsideCode); } - catch (FatalError const&) + catch (langutil::FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. @@ -172,7 +173,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) m_scopes[nullptr]->registerDeclaration(_declaration, nullptr, false, true); solAssert(_declaration.scope() == nullptr, "Updated declaration outside global scope."); } - catch (FatalError const&) + catch (langutil::FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. @@ -256,7 +257,7 @@ vector NameAndTypeResolver::cleanedDeclarations( uniqueFunctions.end(), [&](Declaration const* d) { - shared_ptr newFunctionType { d->functionType(false) }; + FunctionType const* newFunctionType = d->functionType(false); if (!newFunctionType) newFunctionType = d->functionType(true); return newFunctionType && functionType->hasEqualParameterTypes(*newFunctionType); @@ -269,7 +270,7 @@ vector NameAndTypeResolver::cleanedDeclarations( void NameAndTypeResolver::warnVariablesNamedLikeInstructions() { - for (auto const& instruction: c_instructions) + for (auto const& instruction: dev::eth::c_instructions) { string const instructionName{boost::algorithm::to_lower_copy(instruction.first)}; auto declarations = nameFromCurrentScope(instructionName, true); @@ -300,6 +301,10 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res setScope(contract->scope()); solAssert(!!m_currentScope, ""); + m_globalContext.setCurrentContract(*contract); + updateDeclaration(*m_globalContext.currentSuper()); + updateDeclaration(*m_globalContext.currentThis()); + for (ASTPointer const& baseContract: contract->baseContracts()) if (!resolveNamesAndTypes(*baseContract, true)) success = false; @@ -480,11 +485,13 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper( map>& _scopes, ASTNode& _astRoot, ErrorReporter& _errorReporter, + GlobalContext& _globalContext, ASTNode const* _currentScope ): m_scopes(_scopes), m_currentScope(_currentScope), - m_errorReporter(_errorReporter) + m_errorReporter(_errorReporter), + m_globalContext(_globalContext) { _astRoot.accept(*this); solAssert(m_currentScope == _currentScope, "Scopes not correctly closed."); @@ -519,9 +526,9 @@ bool DeclarationRegistrationHelper::registerDeclaration( Declaration const* conflictingDeclaration = _container.conflictingDeclaration(_declaration, _name); solAssert(conflictingDeclaration, ""); bool const comparable = - _errorLocation->sourceName && - conflictingDeclaration->location().sourceName && - *_errorLocation->sourceName == *conflictingDeclaration->location().sourceName; + _errorLocation->source && + conflictingDeclaration->location().source && + _errorLocation->source->name() == conflictingDeclaration->location().source->name(); if (comparable && _errorLocation->start < conflictingDeclaration->location().start) { firstDeclarationLocation = *_errorLocation; @@ -544,7 +551,7 @@ bool DeclarationRegistrationHelper::registerDeclaration( { if (dynamic_cast(shadowedDeclaration)) _errorReporter.warning( - _declaration.location(), + *_errorLocation, "This declaration shadows a builtin symbol." ); else @@ -588,6 +595,10 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import) bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) { + m_globalContext.setCurrentContract(_contract); + m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true); + m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true); + registerDeclaration(_contract, true); _contract.annotation().canonicalName = currentCanonicalName(); return true; diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index 852a999b7..e493bf984 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,23 +22,29 @@ #pragma once -#include -#include -#include #include +#include #include #include #include -#include #include +#include + +#include + +#include +#include + +namespace langutil +{ +class ErrorReporter; +} namespace dev { namespace solidity { -class ErrorReporter; - /** * Resolves name references, typenames and sets the (explicitly given) types for all variable * declarations. @@ -50,9 +56,9 @@ class NameAndTypeResolver: private boost::noncopyable /// @param _scopes mapping of scopes to be used (usually default constructed), these /// are filled during the lifetime of this object. NameAndTypeResolver( - std::vector const& _globals, + GlobalContext& _globalContext, std::map>& _scopes, - ErrorReporter& _errorReporter + langutil::ErrorReporter& _errorReporter ); /// Registers all declarations found in the AST node, usually a source unit. /// @returns false in case of error. @@ -133,7 +139,8 @@ class NameAndTypeResolver: private boost::noncopyable std::map m_rules; DeclarationContainer* m_currentScope = nullptr; - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; + GlobalContext& m_globalContext; }; /** @@ -150,7 +157,8 @@ class DeclarationRegistrationHelper: private ASTVisitor DeclarationRegistrationHelper( std::map>& _scopes, ASTNode& _astRoot, - ErrorReporter& _errorReporter, + langutil::ErrorReporter& _errorReporter, + GlobalContext& _globalContext, ASTNode const* _currentScope = nullptr ); @@ -158,10 +166,10 @@ class DeclarationRegistrationHelper: private ASTVisitor DeclarationContainer& _container, Declaration const& _declaration, std::string const* _name, - SourceLocation const* _errorLocation, + langutil::SourceLocation const* _errorLocation, bool _warnOnShadow, bool _inactive, - ErrorReporter& _errorReporter + langutil::ErrorReporter& _errorReporter ); private: @@ -206,7 +214,8 @@ class DeclarationRegistrationHelper: private ASTVisitor std::map>& m_scopes; ASTNode const* m_currentScope = nullptr; VariableScope* m_currentFunction = nullptr; - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; + GlobalContext& m_globalContext; }; } diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index 240d79739..936e133ca 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -16,19 +16,19 @@ */ #include + #include -#include -#include #include - +#include +#include #include #include - #include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h index 4f9dac6e7..e428b81ae 100644 --- a/libsolidity/analysis/PostTypeChecker.h +++ b/libsolidity/analysis/PostTypeChecker.h @@ -23,13 +23,17 @@ #include #include +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + namespace dev { namespace solidity { -class ErrorReporter; - /** * This module performs analyses on the AST that are done after type checking and assignments of types: * - whether there are circular references in constant state variables @@ -39,25 +43,25 @@ class PostTypeChecker: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + PostTypeChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} bool check(ASTNode const& _astRoot); private: /// Adds a new error to the list of errors. - void typeError(SourceLocation const& _location, std::string const& _description); + void typeError(langutil::SourceLocation const& _location, std::string const& _description); - virtual bool visit(ContractDefinition const& _contract) override; - virtual void endVisit(ContractDefinition const& _contract) override; + bool visit(ContractDefinition const& _contract) override; + void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(VariableDeclaration const& _variable) override; - virtual void endVisit(VariableDeclaration const& _variable) override; + bool visit(VariableDeclaration const& _variable) override; + void endVisit(VariableDeclaration const& _variable) override; - virtual bool visit(Identifier const& _identifier) override; + bool visit(Identifier const& _identifier) override; VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom); - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; VariableDeclaration const* m_currentConstVariable = nullptr; std::vector m_constVariables; ///< Required for determinism. diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 2adc8e775..a1df47abe 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -21,14 +21,18 @@ */ #include -#include #include -#include #include -#include -#include -#include -#include +#include +#include + +#include +#include +#include +#include + +#include +#include #include @@ -36,9 +40,12 @@ #include using namespace std; -using namespace dev; -using namespace dev::solidity; +using namespace langutil; +namespace dev +{ +namespace solidity +{ bool ReferencesResolver::resolve(ASTNode const& _root) { @@ -114,7 +121,7 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName) { if (!_typeName.annotation().type) { - _typeName.annotation().type = Type::fromElementaryTypeName(_typeName.typeName()); + _typeName.annotation().type = TypeProvider::fromElementaryTypeName(_typeName.typeName()); if (_typeName.stateMutability().is_initialized()) { // for non-address types this was already caught by the parser @@ -122,8 +129,10 @@ bool ReferencesResolver::visit(ElementaryTypeName const& _typeName) switch(*_typeName.stateMutability()) { case StateMutability::Payable: + _typeName.annotation().type = TypeProvider::payableAddress(); + break; case StateMutability::NonPayable: - _typeName.annotation().type = make_shared(*_typeName.stateMutability()); + _typeName.annotation().type = TypeProvider::address(); break; default: m_errorReporter.typeError( @@ -173,14 +182,14 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) _typeName.annotation().referencedDeclaration = declaration; if (StructDefinition const* structDef = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*structDef); + _typeName.annotation().type = TypeProvider::structType(*structDef, DataLocation::Storage); else if (EnumDefinition const* enumDef = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*enumDef); + _typeName.annotation().type = TypeProvider::enumType(*enumDef); else if (ContractDefinition const* contract = dynamic_cast(declaration)) - _typeName.annotation().type = make_shared(*contract); + _typeName.annotation().type = TypeProvider::contract(*contract); else { - _typeName.annotation().type = make_shared(); + _typeName.annotation().type = TypeProvider::emptyTuple(); typeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } } @@ -207,14 +216,14 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); - if (!t->annotation().type->canBeUsedExternally(false)) + if (!t->annotation().type->interfaceType(false).get()) { fatalTypeError(t->location(), "Internal type cannot be used for external function type."); return; } } - _typeName.annotation().type = make_shared(_typeName); + _typeName.annotation().type = TypeProvider::function(_typeName); } void ReferencesResolver::endVisit(Mapping const& _typeName) @@ -222,10 +231,10 @@ void ReferencesResolver::endVisit(Mapping const& _typeName) TypePointer keyType = _typeName.keyType().annotation().type; TypePointer valueType = _typeName.valueType().annotation().type; // Convert key type to memory. - keyType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, keyType); + keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType); // Convert value type to storage reference. - valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType); - _typeName.annotation().type = make_shared(keyType, valueType); + valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType); + _typeName.annotation().type = TypeProvider::mapping(keyType, valueType); } void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) @@ -240,10 +249,10 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) fatalTypeError(_typeName.baseType().location(), "Illegal base type of storage size zero for array."); if (Expression const* length = _typeName.length()) { - TypePointer lengthTypeGeneric = length->annotation().type; + TypePointer& lengthTypeGeneric = length->annotation().type; if (!lengthTypeGeneric) lengthTypeGeneric = ConstantEvaluator(m_errorReporter).evaluate(*length); - RationalNumberType const* lengthType = dynamic_cast(lengthTypeGeneric.get()); + RationalNumberType const* lengthType = dynamic_cast(lengthTypeGeneric); if (!lengthType || !lengthType->mobileType()) fatalTypeError(length->location(), "Invalid array length, expected integer literal or constant expression."); else if (lengthType->isZero()) @@ -253,10 +262,10 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) else if (lengthType->isNegative()) fatalTypeError(length->location(), "Array with negative length specified."); else - _typeName.annotation().type = make_shared(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); + _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); } else - _typeName.annotation().type = make_shared(DataLocation::Storage, baseType); + _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType); } bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) @@ -270,7 +279,7 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) ErrorList errors; ErrorReporter errorsIgnored(errors); yul::ExternalIdentifierAccess::Resolver resolver = - [&](assembly::Identifier const& _identifier, yul::IdentifierContext, bool _crossesFunctionBoundary) { + [&](yul::Identifier const& _identifier, yul::IdentifierContext, bool _crossesFunctionBoundary) { auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot"); bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset"); @@ -292,11 +301,13 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) } declarations = m_resolver.nameFromCurrentScope(realName); } - if (declarations.size() != 1) + if (declarations.size() > 1) { declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported."); return size_t(-1); } + else if (declarations.size() == 0) + return size_t(-1); if (auto var = dynamic_cast(declarations.front())) if (var->isLocalVariable() && _crossesFunctionBoundary) { @@ -311,9 +322,15 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) // Will be re-generated later with correct information // We use the latest EVM version because we will re-run it anyway. - assembly::AsmAnalysisInfo analysisInfo; + yul::AsmAnalysisInfo analysisInfo; boost::optional errorTypeForLoose = Error::Type::SyntaxError; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), errorTypeForLoose, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); + yul::AsmAnalyzer( + analysisInfo, + errorsIgnored, + errorTypeForLoose, + yul::EVMDialect::looseAssemblyForEVM(EVMVersion{}), + resolver + ).analyze(_inlineAssembly.operations()); return false; } @@ -422,10 +439,10 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) } TypePointer type = _variable.typeName()->annotation().type; - if (auto ref = dynamic_cast(type.get())) + if (auto ref = dynamic_cast(type)) { bool isPointer = !_variable.isStateVariable(); - type = ref->copyForLocation(typeLoc, isPointer); + type = TypeProvider::withLocation(ref, typeLoc, isPointer); } _variable.annotation().type = type; @@ -454,3 +471,6 @@ void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, m_errorOccurred = true; m_errorReporter.fatalDeclarationError(_location, _description); } + +} +} diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 24ec46430..5631fb801 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,18 +22,24 @@ #pragma once -#include -#include -#include #include #include +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + namespace dev { namespace solidity { -class ErrorReporter; class NameAndTypeResolver; /** @@ -44,7 +50,7 @@ class ReferencesResolver: private ASTConstVisitor { public: ReferencesResolver( - ErrorReporter& _errorReporter, + langutil::ErrorReporter& _errorReporter, NameAndTypeResolver& _resolver, bool _resolveInsideCode = false ): @@ -57,38 +63,38 @@ class ReferencesResolver: private ASTConstVisitor bool resolve(ASTNode const& _root); private: - virtual bool visit(Block const& _block) override; - virtual void endVisit(Block const& _block) override; - virtual bool visit(ForStatement const& _for) override; - virtual void endVisit(ForStatement const& _for) override; - virtual void endVisit(VariableDeclarationStatement const& _varDeclStatement) override; - virtual bool visit(Identifier const& _identifier) override; - virtual bool visit(ElementaryTypeName const& _typeName) override; - virtual bool visit(FunctionDefinition const& _functionDefinition) override; - virtual void endVisit(FunctionDefinition const& _functionDefinition) override; - virtual bool visit(ModifierDefinition const& _modifierDefinition) override; - virtual void endVisit(ModifierDefinition const& _modifierDefinition) override; - virtual void endVisit(UserDefinedTypeName const& _typeName) override; - virtual void endVisit(FunctionTypeName const& _typeName) override; - virtual void endVisit(Mapping const& _typeName) override; - virtual void endVisit(ArrayTypeName const& _typeName) override; - virtual bool visit(InlineAssembly const& _inlineAssembly) override; - virtual bool visit(Return const& _return) override; - virtual void endVisit(VariableDeclaration const& _variable) override; + bool visit(Block const& _block) override; + void endVisit(Block const& _block) override; + bool visit(ForStatement const& _for) override; + void endVisit(ForStatement const& _for) override; + void endVisit(VariableDeclarationStatement const& _varDeclStatement) override; + bool visit(Identifier const& _identifier) override; + bool visit(ElementaryTypeName const& _typeName) override; + bool visit(FunctionDefinition const& _functionDefinition) override; + void endVisit(FunctionDefinition const& _functionDefinition) override; + bool visit(ModifierDefinition const& _modifierDefinition) override; + void endVisit(ModifierDefinition const& _modifierDefinition) override; + void endVisit(UserDefinedTypeName const& _typeName) override; + void endVisit(FunctionTypeName const& _typeName) override; + void endVisit(Mapping const& _typeName) override; + void endVisit(ArrayTypeName const& _typeName) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(Return const& _return) override; + void endVisit(VariableDeclaration const& _variable) override; /// Adds a new error to the list of errors. - void typeError(SourceLocation const& _location, std::string const& _description); + void typeError(langutil::SourceLocation const& _location, std::string const& _description); /// Adds a new error to the list of errors and throws to abort reference resolving. - void fatalTypeError(SourceLocation const& _location, std::string const& _description); + void fatalTypeError(langutil::SourceLocation const& _location, std::string const& _description); /// Adds a new error to the list of errors. - void declarationError(SourceLocation const& _location, std::string const& _description); + void declarationError(langutil::SourceLocation const& _location, std::string const& _description); /// Adds a new error to the list of errors and throws to abort reference resolving. - void fatalDeclarationError(SourceLocation const& _location, std::string const& _description); + void fatalDeclarationError(langutil::SourceLocation const& _location, std::string const& _description); - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; NameAndTypeResolver& m_resolver; /// Stack of return parameters. std::vector m_returnParameters; diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index 13f8d908b..9b957c3c4 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -21,15 +21,67 @@ */ #include + #include #include -#include +#include #include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; +/** + * Helper class that determines whether a contract's constructor uses inline assembly. + */ +class dev::solidity::ConstructorUsesAssembly +{ +public: + /// @returns true if and only if the contract's or any of its bases' constructors + /// use inline assembly. + bool check(ContractDefinition const& _contract) + { + for (auto const* base: _contract.annotation().linearizedBaseContracts) + if (checkInternal(*base)) + return true; + return false; + } + + +private: + class Checker: public ASTConstVisitor + { + public: + Checker(FunctionDefinition const& _f) { _f.accept(*this); } + bool visit(InlineAssembly const&) override { assemblySeen = true; return false; } + bool assemblySeen = false; + }; + + bool checkInternal(ContractDefinition const& _contract) + { + if (!m_usesAssembly.count(&_contract)) + { + bool usesAssembly = false; + if (_contract.constructor()) + usesAssembly = Checker{*_contract.constructor()}.assemblySeen; + m_usesAssembly[&_contract] = usesAssembly; + } + return m_usesAssembly[&_contract]; + } + + map m_usesAssembly; +}; + +StaticAnalyzer::StaticAnalyzer(ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) +{ +} + +StaticAnalyzer::~StaticAnalyzer() +{ +} + bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); @@ -62,21 +114,21 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function) void StaticAnalyzer::endVisit(FunctionDefinition const&) { - m_currentFunction = nullptr; - m_constructor = false; - for (auto const& var: m_localVarUseCount) - if (var.second == 0) - { - if (var.first.second->isCallableParameter()) - m_errorReporter.warning( - var.first.second->location(), - "Unused function parameter. Remove or comment out the variable name to silence this warning." - ); - else - m_errorReporter.warning(var.first.second->location(), "Unused local variable."); - } - + if (m_currentFunction && !m_currentFunction->body().statements().empty()) + for (auto const& var: m_localVarUseCount) + if (var.second == 0) + { + if (var.first.second->isCallableParameter()) + m_errorReporter.warning( + var.first.second->location(), + "Unused function parameter. Remove or comment out the variable name to silence this warning." + ); + else + m_errorReporter.warning(var.first.second->location(), "Unused local variable."); + } m_localVarUseCount.clear(); + m_constructor = false; + m_currentFunction = nullptr; } bool StaticAnalyzer::visit(Identifier const& _identifier) @@ -138,7 +190,7 @@ bool StaticAnalyzer::visit(ExpressionStatement const& _statement) bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) { - if (MagicType const* type = dynamic_cast(_memberAccess.expression().annotation().type.get())) + if (MagicType const* type = dynamic_cast(_memberAccess.expression().annotation().type)) { if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas") m_errorReporter.typeError( @@ -150,10 +202,22 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" ); + else if (type->kind() == MagicType::Kind::MetaType && _memberAccess.memberName() == "runtimeCode") + { + if (!m_constructorUsesAssembly) + m_constructorUsesAssembly = make_unique(); + ContractType const& contract = dynamic_cast(*type->typeArgument()); + if (m_constructorUsesAssembly->check(contract.contractDefinition())) + m_errorReporter.warning( + _memberAccess.location(), + "The constructor of the contract (or its base) uses inline assembly. " + "Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode." + ); + } } if (_memberAccess.memberName() == "callcode") - if (auto const* type = dynamic_cast(_memberAccess.annotation().type.get())) + if (auto const* type = dynamic_cast(_memberAccess.annotation().type)) if (type->kind() == FunctionType::Kind::BareCallCode) m_errorReporter.typeError( _memberAccess.location(), @@ -224,7 +288,7 @@ bool StaticAnalyzer::visit(BinaryOperation const& _operation) _operation.rightExpression().annotation().isPure && (_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod) ) - if (auto rhs = dynamic_pointer_cast( + if (auto rhs = dynamic_cast( ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression()) )) if (rhs->isZero()) @@ -240,13 +304,13 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) { if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall) { - auto functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type); + auto functionType = dynamic_cast(_functionCall.expression().annotation().type); solAssert(functionType, ""); if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod) { solAssert(_functionCall.arguments().size() == 3, ""); if (_functionCall.arguments()[2]->annotation().isPure) - if (auto lastArg = dynamic_pointer_cast( + if (auto lastArg = dynamic_cast( ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2]) )) if (lastArg->isZero()) @@ -255,6 +319,19 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall) "Arithmetic modulo zero." ); } + if ( + m_currentContract->isLibrary() && + functionType->kind() == FunctionType::Kind::DelegateCall && + functionType->declaration().scope() == m_currentContract + ) + m_errorReporter.typeError( + _functionCall.location(), + SecondarySourceLocation().append( + "The function declaration is here:", + functionType->declaration().scope()->location() + ), + "Libraries cannot call their own functions externally." + ); } return true; } diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index 7f5c743a5..3daf83b31 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -28,11 +28,18 @@ #include #include +namespace langutil +{ +class ErrorReporter; +} + namespace dev { namespace solidity { +class ConstructorUsesAssembly; + /** * The module that performs static analysis on the AST. @@ -44,7 +51,8 @@ class StaticAnalyzer: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter); + ~StaticAnalyzer(); /// Performs static analysis on the given source unit and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -52,25 +60,25 @@ class StaticAnalyzer: private ASTConstVisitor private: - virtual bool visit(ContractDefinition const& _contract) override; - virtual void endVisit(ContractDefinition const& _contract) override; + bool visit(ContractDefinition const& _contract) override; + void endVisit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual void endVisit(FunctionDefinition const& _function) override; + bool visit(FunctionDefinition const& _function) override; + void endVisit(FunctionDefinition const& _function) override; - virtual bool visit(ExpressionStatement const& _statement) override; - virtual bool visit(VariableDeclaration const& _variable) override; - virtual bool visit(Identifier const& _identifier) override; - virtual bool visit(Return const& _return) override; - virtual bool visit(MemberAccess const& _memberAccess) override; - virtual bool visit(InlineAssembly const& _inlineAssembly) override; - virtual bool visit(BinaryOperation const& _operation) override; - virtual bool visit(FunctionCall const& _functionCall) override; + bool visit(ExpressionStatement const& _statement) override; + bool visit(VariableDeclaration const& _variable) override; + bool visit(Identifier const& _identifier) override; + bool visit(Return const& _return) override; + bool visit(MemberAccess const& _memberAccess) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(BinaryOperation const& _operation) override; + bool visit(FunctionCall const& _functionCall) override; /// @returns the size of this type in storage, including all sub-types. static bigint structureSizeEstimate(Type const& _type, std::set& _structsSeen); - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; /// Flag that indicates whether the current contract definition is a library. bool m_library = false; @@ -80,6 +88,10 @@ class StaticAnalyzer: private ASTConstVisitor /// when traversing. std::map, int> m_localVarUseCount; + /// Cache that holds information about whether a contract's constructor + /// uses inline assembly. + std::unique_ptr m_constructorUsesAssembly; + FunctionDefinition const* m_currentFunction = nullptr; /// Flag that indicates a constructor. diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index a71a07dcd..5ee40841e 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -16,19 +16,26 @@ */ #include -#include + #include #include -#include -#include #include -#include +#include +#include + +#include +#include + +#include #include + +#include #include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; @@ -276,6 +283,20 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation) return true; } +bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly) +{ + if (!m_useYulOptimizer) + return false; + + if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations())) + m_errorReporter.syntaxError( + _inlineAssembly.location(), + "The msize instruction cannot be used when the Yul optimizer is activated because " + "it can change its semantics. Either disable the Yul optimizer or do not use the instruction." + ); + return false; +} + bool SyntaxChecker::visit(PlaceholderStatement const&) { m_placeholderFound = true; @@ -284,7 +305,7 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(ContractDefinition const& _contract) { - m_isInterface = _contract.contractKind() == ContractDefinition::ContractKind::Interface; + m_isInterface = _contract.isInterface(); ASTString const& contractName = _contract.name(); for (FunctionDefinition const* function: _contract.definedFunctions()) diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h index f5716bf93..bf2cf9ff6 100644 --- a/libsolidity/analysis/SyntaxChecker.h +++ b/libsolidity/analysis/SyntaxChecker.h @@ -23,6 +23,11 @@ #include #include +namespace langutil +{ +class ErrorReporter; +} + namespace dev { namespace solidity @@ -34,54 +39,62 @@ namespace solidity * - whether a modifier contains at least one '_' * - issues deprecation warnings for unary '+' * - issues deprecation warning for throw + * - whether the msize instruction is used and the Yul optimizer is enabled at the same time. */ class SyntaxChecker: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + SyntaxChecker(langutil::ErrorReporter& _errorReporter, bool _useYulOptimizer): + m_errorReporter(_errorReporter), + m_useYulOptimizer(_useYulOptimizer) + {} bool checkSyntax(ASTNode const& _astRoot); private: - virtual bool visit(SourceUnit const& _sourceUnit) override; - virtual void endVisit(SourceUnit const& _sourceUnit) override; - virtual bool visit(PragmaDirective const& _pragma) override; + bool visit(SourceUnit const& _sourceUnit) override; + void endVisit(SourceUnit const& _sourceUnit) override; + bool visit(PragmaDirective const& _pragma) override; - virtual bool visit(ModifierDefinition const& _modifier) override; - virtual void endVisit(ModifierDefinition const& _modifier) override; + bool visit(ModifierDefinition const& _modifier) override; + void endVisit(ModifierDefinition const& _modifier) override; /// Reports an error if _statement is a VariableDeclarationStatement. /// Used by if/while/for to check for single statement variable declarations /// without a block. void checkSingleStatementVariableDeclaration(ASTNode const& _statement); - virtual bool visit(IfStatement const& _ifStatement) override; - virtual bool visit(WhileStatement const& _whileStatement) override; - virtual void endVisit(WhileStatement const& _whileStatement) override; - virtual bool visit(ForStatement const& _forStatement) override; - virtual void endVisit(ForStatement const& _forStatement) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(WhileStatement const& _whileStatement) override; + void endVisit(WhileStatement const& _whileStatement) override; + bool visit(ForStatement const& _forStatement) override; + void endVisit(ForStatement const& _forStatement) override; + + bool visit(Continue const& _continueStatement) override; + bool visit(Break const& _breakStatement) override; + + bool visit(Throw const& _throwStatement) override; - virtual bool visit(Continue const& _continueStatement) override; - virtual bool visit(Break const& _breakStatement) override; + bool visit(UnaryOperation const& _operation) override; - virtual bool visit(Throw const& _throwStatement) override; + bool visit(InlineAssembly const& _inlineAssembly) override; - virtual bool visit(UnaryOperation const& _operation) override; + bool visit(PlaceholderStatement const& _placeholderStatement) override; - virtual bool visit(PlaceholderStatement const& _placeholderStatement) override; + bool visit(ContractDefinition const& _contract) override; + bool visit(FunctionDefinition const& _function) override; + bool visit(FunctionTypeName const& _node) override; - virtual bool visit(ContractDefinition const& _contract) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual bool visit(FunctionTypeName const& _node) override; + bool visit(VariableDeclarationStatement const& _statement) override; - virtual bool visit(VariableDeclarationStatement const& _statement) override; + bool visit(StructDefinition const& _struct) override; + bool visit(Literal const& _literal) override; - virtual bool visit(StructDefinition const& _struct) override; - virtual bool visit(Literal const& _literal) override; + langutil::ErrorReporter& m_errorReporter; - ErrorReporter& m_errorReporter; + bool m_useYulOptimizer = false; /// Flag that indicates whether a function modifier actually contains '_'. bool m_placeholderFound = false; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index aebe1b184..4ea6ad95c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -21,28 +21,33 @@ */ #include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include +#include + +#include +#include +#include + +#include + #include +#include + +#include +#include +#include + +#include +#include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; -namespace -{ - -bool typeSupportedByOldABIEncoder(Type const& _type) +bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall) { - if (_type.dataStoredIn(DataLocation::Storage)) + if (_isLibraryCall && _type.dataStoredIn(DataLocation::Storage)) return true; if (_type.category() == Type::Category::Struct) return false; @@ -50,23 +55,12 @@ bool typeSupportedByOldABIEncoder(Type const& _type) { auto const& arrayType = dynamic_cast(_type); auto base = arrayType.baseType(); - if (!typeSupportedByOldABIEncoder(*base) || (base->category() == Type::Category::Array && base->isDynamicallySized())) + if (!typeSupportedByOldABIEncoder(*base, _isLibraryCall) || (base->category() == Type::Category::Array && base->isDynamicallySized())) return false; } return true; } -string toStringInChecker(Type const& _type) -{ - if(auto intT = dynamic_cast(&_type)) - if(intT->isSafeUint()) - return "safeuint"; - return _type.toString(); -} - -} - - bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) { _contract.accept(*this); @@ -89,418 +83,15 @@ bool TypeChecker::visit(ContractDefinition const& _contract) { m_scope = &_contract; - // We force our own visiting order here. The structs have to be excluded below. - set visited; - for (auto const& s: _contract.definedStructs()) - visited.insert(s); - ASTNode::listAccept(_contract.definedStructs(), *this); ASTNode::listAccept(_contract.baseContracts(), *this); - checkContractDuplicateFunctions(_contract); - checkContractDuplicateEvents(_contract); - checkContractIllegalOverrides(_contract); - checkContractAbstractFunctions(_contract); - checkContractBaseConstructorArguments(_contract); - - FunctionDefinition const* function = _contract.constructor(); - if (function) - { - if (!function->returnParameters().empty()) - m_errorReporter.typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor."); - if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - function->location(), - "Constructor must be payable or non-payable, but is \"" + - stateMutabilityToString(function->stateMutability()) + - "\"." - ); - if (function->visibility() != FunctionDefinition::Visibility::Public && function->visibility() != FunctionDefinition::Visibility::Internal) - m_errorReporter.typeError(function->location(), "Constructor must be public or internal."); - } - - for (FunctionDefinition const* function: _contract.definedFunctions()) - if (function->isFallback()) - { - if (_contract.isLibrary()) - m_errorReporter.typeError(function->location(), "Libraries cannot have fallback functions."); - if (function->stateMutability() != StateMutability::NonPayable && function->stateMutability() != StateMutability::Payable) - m_errorReporter.typeError( - function->location(), - "Fallback function must be payable or non-payable, but is \"" + - stateMutabilityToString(function->stateMutability()) + - "\"." - ); - if (!function->parameters().empty()) - m_errorReporter.typeError(function->parameterList().location(), "Fallback function cannot take parameters."); - if (!function->returnParameters().empty()) - m_errorReporter.typeError(function->returnParameterList()->location(), "Fallback function cannot return values."); - if (function->visibility() != FunctionDefinition::Visibility::External) - m_errorReporter.typeError(function->location(), "Fallback function must be defined as \"external\"."); - } - for (auto const& n: _contract.subNodes()) - if (!visited.count(n.get())) - n->accept(*this); - - checkContractExternalTypeClashes(_contract); - // check for hash collisions in function signatures - set> hashes; - for (auto const& it: _contract.interfaceFunctionList()) - { - FixedHash<4> const& hash = it.first; - if (hashes.count(hash)) - m_errorReporter.typeError( - _contract.location(), - string("Function signature hash collision for ") + it.second->externalSignature() - ); - hashes.insert(hash); - } + n->accept(*this); - if (_contract.isLibrary()) - checkLibraryRequirements(_contract); return false; } -void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _contract) -{ - /// Checks that two functions with the same name defined in this contract have different - /// argument types and that there is at most one constructor. - map> functions; - FunctionDefinition const* constructor = nullptr; - FunctionDefinition const* fallback = nullptr; - for (FunctionDefinition const* function: _contract.definedFunctions()) - if (function->isConstructor()) - { - if (constructor) - m_errorReporter.declarationError( - function->location(), - SecondarySourceLocation().append("Another declaration is here:", constructor->location()), - "More than one constructor defined." - ); - constructor = function; - } - else if (function->isFallback()) - { - if (fallback) - m_errorReporter.declarationError( - function->location(), - SecondarySourceLocation().append("Another declaration is here:", fallback->location()), - "Only one fallback function is allowed." - ); - fallback = function; - } - else - { - solAssert(!function->name().empty(), ""); - functions[function->name()].push_back(function); - } - - findDuplicateDefinitions(functions, "Function with same name and arguments defined twice."); -} - -void TypeChecker::checkContractDuplicateEvents(ContractDefinition const& _contract) -{ - /// Checks that two events with the same name defined in this contract have different - /// argument types - map> events; - for (EventDefinition const* event: _contract.events()) - events[event->name()].push_back(event); - - findDuplicateDefinitions(events, "Event with same name and arguments defined twice."); -} - -template -void TypeChecker::findDuplicateDefinitions(map> const& _definitions, string _message) -{ - for (auto const& it: _definitions) - { - vector const& overloads = it.second; - set reported; - for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i) - { - SecondarySourceLocation ssl; - - for (size_t j = i + 1; j < overloads.size(); ++j) - if (FunctionType(*overloads[i]).hasEqualParameterTypes(FunctionType(*overloads[j]))) - { - ssl.append("Other declaration is here:", overloads[j]->location()); - reported.insert(j); - } - - if (ssl.infos.size() > 0) - { - ssl.limitSize(_message); - - m_errorReporter.declarationError( - overloads[i]->location(), - ssl, - _message - ); - } - } - } -} - -void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _contract) -{ - // Mapping from name to function definition (exactly one per argument type equality class) and - // flag to indicate whether it is fully implemented. - using FunTypeAndFlag = std::pair; - map> functions; - - // Search from base to derived - for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) - for (FunctionDefinition const* function: contract->definedFunctions()) - { - // Take constructors out of overload hierarchy - if (function->isConstructor()) - continue; - auto& overloads = functions[function->name()]; - FunctionTypePointer funType = make_shared(*function); - auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) - { - return funType->hasEqualParameterTypes(*_funAndFlag.first); - }); - if (it == overloads.end()) - overloads.push_back(make_pair(funType, function->isImplemented())); - else if (it->second) - { - if (!function->isImplemented()) - m_errorReporter.typeError(function->location(), "Redeclaring an already implemented function as abstract"); - } - else if (function->isImplemented()) - it->second = true; - } - - // Set to not fully implemented if at least one flag is false. - for (auto const& it: functions) - for (auto const& funAndFlag: it.second) - if (!funAndFlag.second) - { - FunctionDefinition const* function = dynamic_cast(&funAndFlag.first->declaration()); - solAssert(function, ""); - _contract.annotation().unimplementedFunctions.push_back(function); - break; - } -} - -void TypeChecker::checkContractBaseConstructorArguments(ContractDefinition const& _contract) -{ - vector const& bases = _contract.annotation().linearizedBaseContracts; - - // Determine the arguments that are used for the base constructors. - for (ContractDefinition const* contract: bases) - { - if (FunctionDefinition const* constructor = contract->constructor()) - for (auto const& modifier: constructor->modifiers()) - if (auto baseContract = dynamic_cast(&dereference(*modifier->name()))) - { - if (modifier->arguments()) - { - if (baseContract->constructor()) - annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get()); - } - else - m_errorReporter.declarationError( - modifier->location(), - "Modifier-style base constructor call without arguments." - ); - } - - for (ASTPointer const& base: contract->baseContracts()) - { - auto baseContract = dynamic_cast(&dereference(base->name())); - solAssert(baseContract, ""); - - if (baseContract->constructor() && base->arguments() && !base->arguments()->empty()) - annotateBaseConstructorArguments(_contract, baseContract->constructor(), base.get()); - } - } - - // check that we get arguments for all base constructors that need it. - // If not mark the contract as abstract (not fully implemented) - for (ContractDefinition const* contract: bases) - if (FunctionDefinition const* constructor = contract->constructor()) - if (contract != &_contract && !constructor->parameters().empty()) - if (!_contract.annotation().baseConstructorArguments.count(constructor)) - _contract.annotation().unimplementedFunctions.push_back(constructor); -} - -void TypeChecker::annotateBaseConstructorArguments( - ContractDefinition const& _currentContract, - FunctionDefinition const* _baseConstructor, - ASTNode const* _argumentNode -) -{ - solAssert(_baseConstructor, ""); - solAssert(_argumentNode, ""); - - auto insertionResult = _currentContract.annotation().baseConstructorArguments.insert( - std::make_pair(_baseConstructor, _argumentNode) - ); - if (!insertionResult.second) - { - ASTNode const* previousNode = insertionResult.first->second; - - SourceLocation const* mainLocation = nullptr; - SecondarySourceLocation ssl; - - if ( - _currentContract.location().contains(previousNode->location()) || - _currentContract.location().contains(_argumentNode->location()) - ) - { - mainLocation = &previousNode->location(); - ssl.append("Second constructor call is here:", _argumentNode->location()); - } - else - { - mainLocation = &_currentContract.location(); - ssl.append("First constructor call is here: ", _argumentNode->location()); - ssl.append("Second constructor call is here: ", previousNode->location()); - } - - m_errorReporter.declarationError( - *mainLocation, - ssl, - "Base constructor arguments given twice." - ); - } - -} - -void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) -{ - // TODO unify this at a later point. for this we need to put the constness and the access specifier - // into the types - map> functions; - map modifiers; - - // We search from derived to base, so the stored item causes the error. - for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) - { - for (FunctionDefinition const* function: contract->definedFunctions()) - { - if (function->isConstructor()) - continue; // constructors can neither be overridden nor override anything - string const& name = function->name(); - if (modifiers.count(name)) - m_errorReporter.typeError(modifiers[name]->location(), "Override changes function to modifier."); - - for (FunctionDefinition const* overriding: functions[name]) - checkFunctionOverride(*overriding, *function); - - functions[name].push_back(function); - } - for (ModifierDefinition const* modifier: contract->functionModifiers()) - { - string const& name = modifier->name(); - ModifierDefinition const*& override = modifiers[name]; - if (!override) - override = modifier; - else if (ModifierType(*override) != ModifierType(*modifier)) - m_errorReporter.typeError(override->location(), "Override changes modifier signature."); - if (!functions[name].empty()) - m_errorReporter.typeError(override->location(), "Override changes modifier to function."); - } - } -} - -void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super) -{ - FunctionType functionType(function); - FunctionType superType(super); - - if (!functionType.hasEqualParameterTypes(superType)) - return; - - if (!function.annotation().superFunction) - function.annotation().superFunction = &super; - - if (function.visibility() != super.visibility()) - { - // visibility is enforced to be external in interfaces, but a contract can override that with public - if ( - super.inContractKind() == ContractDefinition::ContractKind::Interface && - function.inContractKind() != ContractDefinition::ContractKind::Interface && - function.visibility() == FunctionDefinition::Visibility::Public - ) - return; - overrideError(function, super, "Overriding function visibility differs."); - } - - else if (function.stateMutability() != super.stateMutability()) - overrideError( - function, - super, - "Overriding function changes state mutability from \"" + - stateMutabilityToString(super.stateMutability()) + - "\" to \"" + - stateMutabilityToString(function.stateMutability()) + - "\"." - ); - - else if (functionType != superType) - overrideError(function, super, "Overriding function return types differ."); -} - -void TypeChecker::overrideError(FunctionDefinition const& function, FunctionDefinition const& super, string message) -{ - m_errorReporter.typeError( - function.location(), - SecondarySourceLocation().append("Overridden function is here:", super.location()), - message - ); -} - -void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract) -{ - map>> externalDeclarations; - for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) - { - for (FunctionDefinition const* f: contract->definedFunctions()) - if (f->isPartOfExternalInterface()) - { - auto functionType = make_shared(*f); - // under non error circumstances this should be true - if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(f, functionType) - ); - } - for (VariableDeclaration const* v: contract->stateVariables()) - if (v->isPartOfExternalInterface()) - { - auto functionType = make_shared(*v); - // under non error circumstances this should be true - if (functionType->interfaceFunctionType()) - externalDeclarations[functionType->externalSignature()].push_back( - make_pair(v, functionType) - ); - } - } - for (auto const& it: externalDeclarations) - for (size_t i = 0; i < it.second.size(); ++i) - for (size_t j = i + 1; j < it.second.size(); ++j) - if (!it.second[i].second->hasEqualParameterTypes(*it.second[j].second)) - m_errorReporter.typeError( - it.second[j].first->location(), - "Function overload clash during conversion to external types for arguments." - ); -} - -void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) -{ - solAssert(_contract.isLibrary(), ""); - if (!_contract.baseContracts().empty()) - m_errorReporter.typeError(_contract.location(), "Library is not allowed to inherit."); - - for (auto const& var: _contract.stateVariables()) - if (!var->isConstant()) - m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables"); -} - void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) { TupleType const& lhs = dynamic_cast(*type(_assignment.leftHandSide())); @@ -516,7 +107,7 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment) size_t toStorageCopies = 0; for (size_t i = 0; i < lhs.components().size(); ++i) { - ReferenceType const* ref = dynamic_cast(lhs.components()[i].get()); + ReferenceType const* ref = dynamic_cast(lhs.components()[i]); if (!ref || !ref->dataStoredIn(DataLocation::Storage) || ref->isPointer()) continue; toStorageCopies++; @@ -543,14 +134,21 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c toString(arguments.size()) + " were provided." ); - if (arguments.size() >= 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType::bytesMemory())) - m_errorReporter.typeError( - arguments.front()->location(), - "Invalid type for argument in function call. " - "Invalid implicit conversion from " + - type(*arguments.front())->toString() + - " to bytes memory requested." - ); + + if (arguments.size() >= 1) + { + BoolResult result = type(*arguments.front())->isImplicitlyConvertibleTo(*TypeProvider::bytesMemory()); + + if (!result) + m_errorReporter.typeErrorConcatenateDescriptions( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*arguments.front())->toString() + + " to bytes memory requested.", + result.message() + ); + } if (arguments.size() < 2) return {}; @@ -571,17 +169,17 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c for (auto const& typeArgument: tupleExpression->components()) { solAssert(typeArgument, ""); - if (TypeType const* argTypeType = dynamic_cast(type(*typeArgument).get())) + if (TypeType const* argTypeType = dynamic_cast(type(*typeArgument))) { TypePointer actualType = argTypeType->actualType(); solAssert(actualType, ""); // We force memory because the parser currently cannot handle // data locations. Furthermore, storage can be a little dangerous and // calldata is not really implemented anyway. - actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType); + actualType = TypeProvider::withLocationIfReference(DataLocation::Memory, actualType); // We force address payable for address types. if (actualType->category() == Type::Category::Address) - actualType = make_shared(StateMutability::Payable); + actualType = TypeProvider::payableAddress(); solAssert( !actualType->dataStoredIn(DataLocation::CallData) && !actualType->dataStoredIn(DataLocation::Storage), @@ -597,18 +195,50 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c else { m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name."); - components.push_back(make_shared()); + components.push_back(TypeProvider::emptyTuple()); } } return components; } +TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall) +{ + vector> arguments = _functionCall.arguments(); + if (arguments.size() != 1) + { + m_errorReporter.typeError( + _functionCall.location(), + "This function takes one argument, but " + + toString(arguments.size()) + + " were provided." + ); + return {}; + } + TypePointer firstArgType = type(*arguments.front()); + if ( + firstArgType->category() != Type::Category::TypeType || + dynamic_cast(*firstArgType).actualType()->category() != TypeType::Category::Contract + ) + { + m_errorReporter.typeError( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Contract type required, but " + + type(*arguments.front())->toString(true) + + " provided." + ); + return {}; + } + + return {TypeProvider::meta(dynamic_cast(*firstArgType).actualType())}; +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + if (m_scope->isInterface()) m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit."); if (base->isLibrary()) @@ -616,7 +246,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto const& arguments = _inheritance.arguments(); TypePointers parameterTypes; - if (base->contractKind() != ContractDefinition::ContractKind::Interface) + if (!base->isInterface()) // Interfaces do not have constructors, so there are zero parameters. parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); @@ -634,16 +264,20 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) ); } for (size_t i = 0; i < std::min(arguments->size(), parameterTypes.size()); ++i) - if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) - m_errorReporter.typeError( + { + BoolResult result = type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i]); + if (!result) + m_errorReporter.typeErrorConcatenateDescriptions( (*arguments)[i]->location(), "Invalid type for argument in constructor call. " "Invalid implicit conversion from " + toStringInChecker(*type(*(*arguments)[i])) + " to " + - toStringInChecker(*parameterTypes[i]) + - " requested." + parameterTypes[i]->toString() + + " requested.", + result.message() ); + } } } @@ -659,8 +293,7 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) bool TypeChecker::visit(StructDefinition const& _struct) { for (ASTPointer const& member: _struct.members()) - if (!type(*member)->canBeStored()) - m_errorReporter.typeError(member->location(), "Type cannot be used in struct."); + solAssert(type(*member)->canBeStored(), "Type cannot be used in struct."); // Check recursion, fatal error if detected. auto visitor = [&](StructDefinition const& _struct, CycleDetector& _cycleDetector, size_t _depth) @@ -670,12 +303,12 @@ bool TypeChecker::visit(StructDefinition const& _struct) for (ASTPointer const& member: _struct.members()) { - Type const* memberType = type(*member).get(); + Type const* memberType = type(*member); while (auto arrayType = dynamic_cast(memberType)) { if (arrayType->isDynamicallySized()) break; - memberType = arrayType->baseType().get(); + memberType = arrayType->baseType(); } if (auto structType = dynamic_cast(memberType)) if (_cycleDetector.run(structType->structDefinition())) @@ -703,31 +336,55 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface()) m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); } - for (ASTPointer const& var: _function.parameters() + _function.returnParameters()) - { - if ( - type(*var)->category() == Type::Category::Mapping && - !type(*var)->dataStoredIn(DataLocation::Storage) - ) - m_errorReporter.typeError(var->location(), "Mapping types can only have a data location of \"storage\"."); - else if ( - !type(*var)->canLiveOutsideStorage() && - _function.visibility() > FunctionDefinition::Visibility::Internal - ) - m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); - if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) - m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); + auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) { + if (type(var)->category() == Type::Category::Mapping) + { + if (var.referenceLocation() != VariableDeclaration::Location::Storage) + { + if (!isLibraryFunction && _function.isPublic()) + m_errorReporter.typeError(var.location(), "Mapping types can only have a data location of \"storage\" and thus only be parameters or return variables for internal or library functions."); + else + m_errorReporter.typeError(var.location(), "Mapping types can only have a data location of \"storage\"." ); + } + else + { + solAssert(isLibraryFunction || !_function.isPublic(), "Mapping types for parameters or return variables can only be used in internal or library functions."); + } + } + else + { + if (!type(var)->canLiveOutsideStorage() && _function.isPublic()) + m_errorReporter.typeError(var.location(), "Type is required to live outside storage."); + if (_function.isPublic()) + { + auto iType = type(var)->interfaceType(isLibraryFunction); + + if (!iType) + { + solAssert(!iType.message().empty(), "Expected detailed error message!"); + m_errorReporter.fatalTypeError(var.location(), iType.message()); + } + } + } if ( - _function.visibility() > FunctionDefinition::Visibility::Internal && + _function.isPublic() && !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && - !typeSupportedByOldABIEncoder(*type(*var)) + !typeSupportedByOldABIEncoder(*type(var), isLibraryFunction) ) m_errorReporter.typeError( - var->location(), + var.location(), "This type is only supported in the new experimental ABI encoder. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); - + }; + for (ASTPointer const& var: _function.parameters()) + { + checkArgumentAndReturnParameter(*var); + var->accept(*this); + } + for (ASTPointer const& var: _function.returnParameters()) + { + checkArgumentAndReturnParameter(*var); var->accept(*this); } set modifiers; @@ -748,7 +405,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) else modifiers.insert(decl); } - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + if (m_scope->isInterface()) { if (_function.isImplemented()) m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation."); @@ -777,21 +434,19 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) // * a function's input/output parameters, // * or inside of a struct definition. if ( - m_scope->contractKind() == ContractDefinition::ContractKind::Interface + m_scope->isInterface() && !_variable.isCallableParameter() && !m_insideStruct ) m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces."); - // Variables can be declared without type (with "var"), in which case the first assignment - // sets the type. - // Note that assignments before the first declaration are legal because of the special scoping - // rules inherited from JavaScript. + if (_variable.typeName()) + _variable.typeName()->accept(*this); // type is filled either by ReferencesResolver directly from the type name or by // TypeChecker at the VariableDeclarationStatement level. TypePointer varType = _variable.annotation().type; - solAssert(!!varType, "Failed to infer variable type."); + solAssert(!!varType, "Variable type not provided."); if (_variable.value()) expectType(*_variable.value(), *varType); @@ -800,7 +455,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!_variable.type()->isValueType()) { bool allowed = false; - if (auto arrayType = dynamic_cast(_variable.type().get())) + if (auto arrayType = dynamic_cast(_variable.type())) allowed = arrayType->isByteArray(); if (!allowed) m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented."); @@ -820,16 +475,30 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!varType->canLiveOutsideStorage()) m_errorReporter.typeError(_variable.location(), "Type " + varType->toString() + " is only valid in storage."); } - else if ( - _variable.visibility() >= VariableDeclaration::Visibility::Public && - !FunctionType(_variable).interfaceFunctionType() - ) - m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables."); + else if (_variable.visibility() >= VariableDeclaration::Visibility::Public) + { + FunctionType getter(_variable); + if (!_variable.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)) + { + vector unsupportedTypes; + for (auto const& param: getter.parameterTypes() + getter.returnParameterTypes()) + if (!typeSupportedByOldABIEncoder(*param, false /* isLibrary */)) + unsupportedTypes.emplace_back(param->toString()); + if (!unsupportedTypes.empty()) + m_errorReporter.typeError(_variable.location(), + "The following types are only supported for getters in the new experimental ABI encoder: " + + joinHumanReadable(unsupportedTypes) + + ". Either remove \"public\" or use \"pragma experimental ABIEncoderV2;\" to enable the feature." + ); + } + if (!getter.interfaceFunctionType()) + m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables."); + } switch (varType->category()) { case Type::Category::Array: - if (auto arrayType = dynamic_cast(varType.get())) + if (auto arrayType = dynamic_cast(varType)) if ( ((arrayType->location() == DataLocation::Memory) || (arrayType->location() == DataLocation::CallData)) && @@ -837,14 +506,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) ) m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded."); break; - case Type::Category::Mapping: - if (auto mappingType = dynamic_cast(varType.get())) - if ( - mappingType->keyType()->isDynamicallySized() && - _variable.visibility() == Declaration::Visibility::Public - ) - m_errorReporter.typeError(_variable.location(), "Dynamically-sized keys for public mappings are not supported."); - break; default: break; } @@ -897,16 +558,20 @@ void TypeChecker::visitManually( return; } for (size_t i = 0; i < arguments.size(); ++i) - if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i]))) - m_errorReporter.typeError( + { + BoolResult result = type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i])); + if (!result) + m_errorReporter.typeErrorConcatenateDescriptions( arguments[i]->location(), "Invalid type for argument in modifier invocation. " "Invalid implicit conversion from " + type(*arguments[i])->toString() + " to " + type(*(*parameters)[i])->toString() + - " requested." + " requested.", + result.message() ); + } } bool TypeChecker::visit(EventDefinition const& _eventDef) @@ -916,24 +581,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) for (ASTPointer const& var: _eventDef.parameters()) { if (var->isIndexed()) - { numIndexed++; - if ( - _eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && - dynamic_cast(type(*var).get()) - ) - m_errorReporter.typeError( - var->location(), - "Indexed reference types cannot yet be used with ABIEncoderV2." - ); - } if (!type(*var)->canLiveOutsideStorage()) m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); if (!type(*var)->interfaceType(false)) m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type."); if ( !_eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && - !typeSupportedByOldABIEncoder(*type(*var)) + !typeSupportedByOldABIEncoder(*type(*var), false /* isLibrary */) ) m_errorReporter.typeError( var->location(), @@ -952,8 +607,7 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) { FunctionType const& fun = dynamic_cast(*_funType.annotation().type); if (fun.kind() == FunctionType::Kind::External) - if (!fun.canBeUsedExternally(false)) - m_errorReporter.typeError(_funType.location(), "External function type uses internal types."); + solAssert(fun.interfaceType(false), "External function type uses internal types."); } bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) @@ -961,7 +615,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) // External references have already been resolved in a prior stage and stored in the annotation. // We run the resolve step again regardless. yul::ExternalIdentifierAccess::Resolver identifierAccess = [&]( - assembly::Identifier const& _identifier, + yul::Identifier const& _identifier, yul::IdentifierContext _context, bool ) @@ -974,12 +628,35 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) bool requiresStorage = ref->second.isSlot || ref->second.isOffset; if (auto var = dynamic_cast(declaration)) { + solAssert(var->type(), "Expected variable type!"); if (var->isConstant()) { - m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly."); - return size_t(-1); + if (!var->value()) + { + m_errorReporter.typeError(_identifier.location, "Constant has no value."); + return size_t(-1); + } + else if (!type(*var)->isValueType() || ( + dynamic_cast(var->value().get()) == nullptr && + type(*var->value())->category() != Type::Category::RationalNumber + )) + { + m_errorReporter.typeError(_identifier.location, "Only direct number constants are supported by inline assembly."); + return size_t(-1); + } + else if (_context == yul::IdentifierContext::LValue) + { + m_errorReporter.typeError(_identifier.location, "Constant variables cannot be assigned to."); + return size_t(-1); + } + else if (requiresStorage) + { + m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables."); + return size_t(-1); + } } - else if (requiresStorage) + + if (requiresStorage) { if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) { @@ -992,7 +669,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) return size_t(-1); } } - else if (!var->isLocalVariable()) + else if (!var->isConstant() && var->isStateVariable()) { m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); return size_t(-1); @@ -1046,13 +723,12 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) return size_t(1); }; solAssert(!_inlineAssembly.annotation().analysisInfo, ""); - _inlineAssembly.annotation().analysisInfo = make_shared(); - assembly::AsmAnalyzer analyzer( + _inlineAssembly.annotation().analysisInfo = make_shared(); + yul::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, - m_evmVersion, Error::Type::SyntaxError, - assembly::AsmFlavour::Loose, + _inlineAssembly.dialect(), identifierAccess ); if (!analyzer.analyze(_inlineAssembly.operations())) @@ -1062,7 +738,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) bool TypeChecker::visit(IfStatement const& _ifStatement) { - expectType(_ifStatement.condition(), BoolType()); + expectType(_ifStatement.condition(), *TypeProvider::boolean()); _ifStatement.trueStatement().accept(*this); if (_ifStatement.falseStatement()) _ifStatement.falseStatement()->accept(*this); @@ -1071,7 +747,7 @@ bool TypeChecker::visit(IfStatement const& _ifStatement) bool TypeChecker::visit(WhileStatement const& _whileStatement) { - expectType(_whileStatement.condition(), BoolType()); + expectType(_whileStatement.condition(), *TypeProvider::boolean()); _whileStatement.body().accept(*this); return false; } @@ -1081,7 +757,7 @@ bool TypeChecker::visit(ForStatement const& _forStatement) if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); if (_forStatement.condition()) - expectType(*_forStatement.condition(), BoolType()); + expectType(*_forStatement.condition(), *TypeProvider::boolean()); if (_forStatement.loopExpression()) _forStatement.loopExpression()->accept(*this); _forStatement.body().accept(*this); @@ -1105,33 +781,38 @@ void TypeChecker::endVisit(Return const& _return) TypePointers returnTypes; for (auto const& var: params->parameters()) returnTypes.push_back(type(*var)); - if (auto tupleType = dynamic_cast(type(*_return.expression()).get())) + if (auto tupleType = dynamic_cast(type(*_return.expression()))) { if (tupleType->components().size() != params->parameters().size()) m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); - else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes))) - m_errorReporter.typeError( - _return.expression()->location(), - "Return argument type " + - toStringInChecker(*type(*_return.expression())) + - " is not implicitly convertible to expected type " + - toStringInChecker(TupleType(returnTypes)) + - "." - ); + else + { + BoolResult result = tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes)); + if (!result) + m_errorReporter.typeErrorConcatenateDescriptions( + _return.expression()->location(), + "Return argument type " + + type(*_return.expression())->toString() + + " is not implicitly convertible to expected type " + + TupleType(returnTypes).toString(false) + ".", + result.message() + ); + } } else if (params->parameters().size() != 1) m_errorReporter.typeError(_return.location(), "Different number of arguments in return statement than in returns declaration."); else { TypePointer const& expected = type(*params->parameters().front()); - if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) - m_errorReporter.typeError( + BoolResult result = type(*_return.expression())->isImplicitlyConvertibleTo(*expected); + if (!result) + m_errorReporter.typeErrorConcatenateDescriptions( _return.expression()->location(), "Return argument type " + toStringInChecker(*type(*_return.expression())) + " is not implicitly convertible to expected type (type of first return variable) " + - toStringInChecker(*expected) + - "." + expected->toString() + ".", + result.message() ); } } @@ -1182,7 +863,7 @@ bool typeCanBeExpressed(vector> const& decls) if (!decl->annotation().type) return false; - if (auto functionType = dynamic_cast(decl->annotation().type.get())) + if (auto functionType = dynamic_cast(decl->annotation().type)) if ( functionType->kind() != FunctionType::Kind::Internal && functionType->kind() != FunctionType::Kind::External @@ -1219,18 +900,17 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) if (!varDecl.annotation().type) m_errorReporter.fatalTypeError(_statement.location(), "Use of the \"var\" keyword is disallowed."); - if (auto ref = dynamic_cast(type(varDecl).get())) + if (auto ref = dynamic_cast(type(varDecl))) { if (ref->dataStoredIn(DataLocation::Storage)) { string errorText{"Uninitialized storage pointer."}; - if (varDecl.referenceLocation() == VariableDeclaration::Location::Unspecified) - errorText += " Did you mean ' memory " + varDecl.name() + "'?"; + solAssert(varDecl.referenceLocation() != VariableDeclaration::Location::Unspecified, "Expected a specified location at this point"); solAssert(m_scope, ""); m_errorReporter.declarationError(varDecl.location(), errorText); } } - else if (dynamic_cast(type(varDecl).get())) + else if (dynamic_cast(type(varDecl))) m_errorReporter.typeError( varDecl.location(), "Uninitialized mapping. Mappings cannot be created dynamically, you have to assign them from a state variable." @@ -1244,7 +924,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) _statement.initialValue()->accept(*this); TypePointers valueTypes; - if (auto tupleType = dynamic_cast(type(*_statement.initialValue()).get())) + if (auto tupleType = dynamic_cast(type(*_statement.initialValue()))) valueTypes = tupleType->components(); else valueTypes = TypePointers{type(*_statement.initialValue())}; @@ -1292,16 +972,13 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else solAssert(false, ""); } - else if (*var.annotation().type == TupleType()) - m_errorReporter.typeError( - var.location(), - "Cannot declare variable with void (empty tuple) type." - ); + else if (*var.annotation().type == *TypeProvider::emptyTuple()) + solAssert(false, "Cannot declare variable with void (empty tuple) type."); else if (valueComponentType->category() == Type::Category::RationalNumber) { string typeName = var.annotation().type->toString(true); string extension; - if (auto type = dynamic_cast(var.annotation().type.get())) + if (auto type = dynamic_cast(var.annotation().type)) { unsigned numBits = type->numBits(); bool isSigned = type->isSigned(); @@ -1319,7 +996,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) extension = ", which can hold values between " + minValue + " and " + maxValue; } else - solAssert(dynamic_cast(var.annotation().type.get()), "Unknown type."); + solAssert(dynamic_cast(var.annotation().type), "Unknown type."); } var.accept(*this); @@ -1327,36 +1004,51 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) else { var.accept(*this); - if (!valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type)) + BoolResult result = valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type); + if (!result) { + auto errorMsg = "Type " + + valueComponentType->toString() + + " is not implicitly convertible to expected type " + + var.annotation().type->toString(); if ( valueComponentType->category() == Type::Category::RationalNumber && dynamic_cast(*valueComponentType).isFractional() && valueComponentType->mobileType() ) - m_errorReporter.typeError( - _statement.location(), - "Type " + - toStringInChecker(*valueComponentType) + - " is not implicitly convertible to expected type " + - toStringInChecker(*var.annotation().type) + - ". Try converting to type " + - toStringInChecker(*valueComponentType->mobileType()) + - " or use an explicit conversion." - ); + { + if (var.annotation().type->operator==(*valueComponentType->mobileType())) + m_errorReporter.typeError( + _statement.location(), + errorMsg + ", but it can be explicitly converted." + ); + else + m_errorReporter.typeError( + _statement.location(), + errorMsg + + ". Try converting to type " + + valueComponentType->mobileType()->toString() + + " or use an explicit conversion." + ); + } else - m_errorReporter.typeError( + m_errorReporter.typeErrorConcatenateDescriptions( _statement.location(), - "Type " + - toStringInChecker(*valueComponentType) + - " is not implicitly convertible to expected type " + - toStringInChecker(*var.annotation().type) + - "." + errorMsg + ".", + result.message() ); } } } + if (valueTypes.size() != variables.size()) + { + solAssert(m_errorReporter.hasErrors(), "Should have errors!"); + for (auto const& var: variables) + if (var && !var->annotation().type) + BOOST_THROW_EXCEPTION(FatalError()); + } + if (autoTypeDeductionNeeded) { if (!typeCanBeExpressed(variables)) @@ -1384,7 +1076,7 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) if (auto call = dynamic_cast(&_statement.expression())) { - if (auto callType = dynamic_cast(type(call->expression()).get())) + if (auto callType = dynamic_cast(type(call->expression()))) { auto kind = callType->kind(); if ( @@ -1402,32 +1094,46 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement) bool TypeChecker::visit(Conditional const& _conditional) { - expectType(_conditional.condition(), BoolType()); + expectType(_conditional.condition(), *TypeProvider::boolean()); _conditional.trueExpression().accept(*this); _conditional.falseExpression().accept(*this); TypePointer trueType = type(_conditional.trueExpression())->mobileType(); TypePointer falseType = type(_conditional.falseExpression())->mobileType(); + + TypePointer commonType = nullptr; + if (!trueType) - m_errorReporter.fatalTypeError(_conditional.trueExpression().location(), "Invalid mobile type."); + m_errorReporter.typeError(_conditional.trueExpression().location(), "Invalid mobile type in true expression."); + else + commonType = trueType; + if (!falseType) - m_errorReporter.fatalTypeError(_conditional.falseExpression().location(), "Invalid mobile type."); + m_errorReporter.typeError(_conditional.falseExpression().location(), "Invalid mobile type in false expression."); + else + commonType = falseType; - TypePointer commonType = Type::commonType(trueType, falseType); - if (!commonType) + if (!trueType && !falseType) + BOOST_THROW_EXCEPTION(FatalError()); + else if (trueType && falseType) { - m_errorReporter.typeError( - _conditional.location(), - "True expression's type " + - trueType->toString() + - " doesn't match false expression's type " + - falseType->toString() + - "." - ); - // even we can't find a common type, we have to set a type here, - // otherwise the upper statement will not be able to check the type. - commonType = trueType; + commonType = Type::commonType(trueType, falseType); + + if (!commonType) + { + m_errorReporter.typeError( + _conditional.location(), + "True expression's type " + + trueType->toString() + + " doesn't match false expression's type " + + falseType->toString() + + "." + ); + // even we can't find a common type, we have to set a type here, + // otherwise the upper statement will not be able to check the type. + commonType = trueType; + } } _conditional.annotation().type = commonType; @@ -1450,7 +1156,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& if (auto const* tupleExpression = dynamic_cast(&_expression)) { auto const* tupleType = dynamic_cast(&_type); - auto const& types = tupleType ? tupleType->components() : vector { _type.shared_from_this() }; + auto const& types = tupleType ? tupleType->components() : vector { &_type }; solAssert( tupleExpression->components().size() == types.size() || m_errorReporter.hasErrors(), @@ -1484,7 +1190,7 @@ bool TypeChecker::visit(Assignment const& _assignment) checkExpressionAssignment(*t, _assignment.leftHandSide()); - if (TupleType const* tupleType = dynamic_cast(t.get())) + if (TupleType const* tupleType = dynamic_cast(t)) { if (_assignment.assignmentOperator() != Token::Assign) m_errorReporter.typeError( @@ -1492,12 +1198,12 @@ bool TypeChecker::visit(Assignment const& _assignment) "Compound assignment is not allowed for tuple types." ); // Sequenced assignments of tuples is not valid, make the result a "void" type. - _assignment.annotation().type = make_shared(); + _assignment.annotation().type = TypeProvider::emptyTuple(); expectType(_assignment.rightHandSide(), *tupleType); // expectType does not cause fatal errors, so we have to check again here. - if (dynamic_cast(type(_assignment.rightHandSide()).get())) + if (dynamic_cast(type(_assignment.rightHandSide()))) checkDoubleStorageAssignment(_assignment); } else if (_assignment.assignmentOperator() == Token::Assign) @@ -1544,14 +1250,14 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (components.size() == 1) _tuple.annotation().type = type(*components[0]); else - _tuple.annotation().type = make_shared(types); + _tuple.annotation().type = TypeProvider::tuple(move(types)); // If some of the components are not LValues, the error is reported above. _tuple.annotation().isLValue = true; } else { bool isPure = true; - TypePointer inlineArrayType; + TypePointer inlineArrayType = nullptr; for (size_t i = 0; i < components.size(); ++i) { @@ -1575,10 +1281,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple) if (!dynamic_cast(*types[i]).mobileType()) m_errorReporter.fatalTypeError(components[i]->location(), "Invalid rational number."); - if (_tuple.isInlineArray()) - solAssert(!!types[i], "Inline array cannot have empty components"); if (_tuple.isInlineArray()) { + solAssert(!!types[i], "Inline array cannot have empty components"); + if ((i == 0 || inlineArrayType) && !types[i]->mobileType()) m_errorReporter.fatalTypeError(components[i]->location(), "Invalid mobile type."); @@ -1598,14 +1304,17 @@ bool TypeChecker::visit(TupleExpression const& _tuple) { if (!inlineArrayType) m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements."); - _tuple.annotation().type = make_shared(DataLocation::Memory, inlineArrayType, types.size()); + else if (!inlineArrayType->canLiveOutsideStorage()) + m_errorReporter.fatalTypeError(_tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage."); + + _tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size()); } else { if (components.size() == 1) _tuple.annotation().type = type(*components[0]); else - _tuple.annotation().type = make_shared(types); + _tuple.annotation().type = TypeProvider::tuple(move(types)); } } @@ -1648,7 +1357,8 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) { TypePointer const& leftType = type(_operation.leftExpression()); TypePointer const& rightType = type(_operation.rightExpression()); - TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + TypeResult result = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + TypePointer commonType = result.get(); if (!commonType) { m_errorReporter.typeError( @@ -1658,14 +1368,15 @@ void TypeChecker::endVisit(BinaryOperation const& _operation) " not compatible with types " + leftType->toString() + " and " + - rightType->toString() + rightType->toString() + + (!result.message().empty() ? ". " + result.message() : "") ); commonType = leftType; } _operation.annotation().commonType = commonType; _operation.annotation().type = TokenTraits::isCompareOp(_operation.getOperator()) ? - make_shared() : + TypeProvider::boolean() : commonType; _operation.annotation().isPure = _operation.leftExpression().annotation().isPure && @@ -1717,20 +1428,20 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( ); else { - TypePointer const& argType = type(*arguments.front()); + Type const* argType = type(*arguments.front()); // Resulting data location is memory unless we are converting from a reference // type with a different data location. // (data location cannot yet be specified for type conversions) DataLocation dataLoc = DataLocation::Memory; - if (auto argRefType = dynamic_cast(argType.get())) + if (auto argRefType = dynamic_cast(argType)) dataLoc = argRefType->location(); - if (auto type = dynamic_cast(resultType.get())) - resultType = type->copyForLocation(dataLoc, type->isPointer()); + if (auto type = dynamic_cast(resultType)) + resultType = TypeProvider::withLocation(type, dataLoc, type->isPointer()); if (argType->isExplicitlyConvertibleTo(*resultType)) { - if (auto argArrayType = dynamic_cast(argType.get())) + if (auto argArrayType = dynamic_cast(argType)) { - auto resultArrayType = dynamic_cast(resultType.get()); + auto resultArrayType = dynamic_cast(resultType); solAssert(!!resultArrayType, ""); solAssert( argArrayType->location() != DataLocation::Storage || @@ -1752,9 +1463,9 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( argType->category() == Type::Category::Address ) { - solAssert(dynamic_cast(resultType.get())->isPayable(), ""); + solAssert(dynamic_cast(resultType)->isPayable(), ""); solAssert( - dynamic_cast(argType.get())->stateMutability() < + dynamic_cast(argType)->stateMutability() < StateMutability::Payable, "" ); @@ -1790,10 +1501,8 @@ TypePointer TypeChecker::typeCheckTypeConversionAndRetrieveReturnType( } if (resultType->category() == Type::Category::Address) { - bool const payable = argType->isExplicitlyConvertibleTo(AddressType::addressPayable()); - resultType = make_shared( - payable ? StateMutability::Payable : StateMutability::NonPayable - ); + bool const payable = argType->isExplicitlyConvertibleTo(*TypeProvider::payableAddress()); + resultType = payable ? TypeProvider::payableAddress() : TypeProvider::address(); } } return resultType; @@ -1819,26 +1528,6 @@ void TypeChecker::typeCheckFunctionCall( "\"staticcall\" is not supported by the VM version." ); - // Check for deprecated function names - if (_functionType->kind() == FunctionType::Kind::KECCAK256) - { - if (auto functionName = dynamic_cast(&_functionCall.expression())) - if (functionName->name() == "sha3") - m_errorReporter.typeError( - _functionCall.location(), - "\"sha3\" has been deprecated in favour of \"keccak256\"" - ); - } - else if (_functionType->kind() == FunctionType::Kind::Selfdestruct) - { - if (auto functionName = dynamic_cast(&_functionCall.expression())) - if (functionName->name() == "suicide") - m_errorReporter.typeError( - _functionCall.location(), - "\"suicide\" has been deprecated in favour of \"selfdestruct\"" - ); - } - // Check for event outside of emit statement if (!m_insideEmitStatement && _functionType->kind() == FunctionType::Kind::Event) m_errorReporter.typeError( @@ -1893,7 +1582,16 @@ void TypeChecker::typeCheckABIEncodeFunctions( if (argType->category() == Type::Category::RationalNumber) { - if (!argType->mobileType()) + auto const& rationalType = dynamic_cast(*argType); + if (rationalType.isFractional()) + { + m_errorReporter.typeError( + arguments[i]->location(), + "Fractional numbers cannot yet be encoded." + ); + continue; + } + else if (!argType->mobileType()) { m_errorReporter.typeError( arguments[i]->location(), @@ -1912,6 +1610,15 @@ void TypeChecker::typeCheckABIEncodeFunctions( } } + if (isPacked && !typeSupportedByOldABIEncoder(*argType, false /* isLibrary */)) + { + m_errorReporter.typeError( + arguments[i]->location(), + "Type not supported in packed mode." + ); + continue; + } + if (!argType->fullEncodingType(false, abiEncoderV2, !_functionType->padArguments())) m_errorReporter.typeError( arguments[i]->location(), @@ -2030,17 +1737,10 @@ void TypeChecker::typeCheckFunctionGeneralChecks( { auto const& parameterNames = _functionType->parameterNames(); - // Check for expected number of named arguments - if (parameterNames.size() != argumentNames.size()) - { - m_errorReporter.typeError( - _functionCall.location(), - parameterNames.size() > argumentNames.size() ? - "Some argument names are missing." : - "Too many arguments." - ); - return; - } + solAssert( + parameterNames.size() == argumentNames.size(), + "Unexpected parameter length mismatch!" + ); // Check for duplicate argument names { @@ -2140,28 +1840,31 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) argumentsArePure = false; } - // For positional calls only, store argument types - if (_functionCall.names().empty()) + // Store argument types - and names if given - for overload resolution { - shared_ptr argumentTypes = make_shared(); + FuncCallArguments funcCallArgs; + + funcCallArgs.names = _functionCall.names(); + for (ASTPointer const& argument: arguments) - argumentTypes->push_back(type(*argument)); - _functionCall.expression().annotation().argumentTypes = move(argumentTypes); + funcCallArgs.types.push_back(type(*argument)); + + _functionCall.expression().annotation().arguments = std::move(funcCallArgs); } _functionCall.expression().accept(*this); - TypePointer const& expressionType = type(_functionCall.expression()); + Type const* expressionType = type(_functionCall.expression()); // Determine function call kind and function type for this FunctionCall node FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); - FunctionTypePointer functionType; + FunctionTypePointer functionType = nullptr; // Determine and assign function call kind, purity and function type for this FunctionCall node switch (expressionType->category()) { case Type::Category::Function: - functionType = dynamic_pointer_cast(expressionType); + functionType = dynamic_cast(expressionType); funcCallAnno.kind = FunctionCallKind::FunctionCall; // Purity for function calls also depends upon the callee and its FunctionType @@ -2233,6 +1936,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } + case FunctionType::Kind::MetaType: + returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall); + break; default: { typeCheckFunctionCall(_functionCall, functionType); @@ -2245,7 +1951,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) funcCallAnno.type = returnTypes.size() == 1 ? move(returnTypes.front()) : - make_shared(move(returnTypes)); + TypeProvider::tuple(move(returnTypes)); break; } @@ -2255,7 +1961,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // for non-callables, ensure error reported and annotate node to void function solAssert(m_errorReporter.hasErrors(), ""); funcCallAnno.kind = FunctionCallKind::FunctionCall; - funcCallAnno.type = make_shared(); + funcCallAnno.type = TypeProvider::emptyTuple(); break; } @@ -2273,7 +1979,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (!contract) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); - if (contract->contractKind() == ContractDefinition::ContractKind::Interface) + if (contract->isInterface()) m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface."); if (!contract->annotation().unimplementedFunctions.empty()) { @@ -2317,12 +2023,12 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) _newExpression.typeName().location(), "Length has to be placed in parentheses after the array type for new expression." ); - type = ReferenceType::copyForLocationIfReference(DataLocation::Memory, type); - _newExpression.annotation().type = make_shared( - TypePointers{make_shared(256)}, + type = TypeProvider::withLocationIfReference(DataLocation::Memory, type); + _newExpression.annotation().type = TypeProvider::function( + TypePointers{TypeProvider::uint256()}, TypePointers{type}, - strings(), - strings(), + strings(1, ""), + strings(1, ""), FunctionType::Kind::ObjectCreation, FunctionType::SpecialModifier::Default, false, @@ -2341,16 +2047,16 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) ASTString const& memberName = _memberAccess.memberName(); // Retrieve the types of the arguments if this is used to call a function. - auto const& argumentTypes = _memberAccess.annotation().argumentTypes; + auto const& arguments = _memberAccess.annotation().arguments; MemberList::MemberMap possibleMembers = exprType->members(m_scope).membersByName(memberName); size_t const initialMemberCount = possibleMembers.size(); - if (initialMemberCount > 1 && argumentTypes) + if (initialMemberCount > 1 && arguments) { // do overload resolution for (auto it = possibleMembers.begin(); it != possibleMembers.end();) if ( it->type->category() == Type::Category::Function && - !dynamic_cast(*it->type).canTakeArguments(*argumentTypes, exprType) + !dynamic_cast(*it->type).canTakeArguments(*arguments, exprType) ) it = possibleMembers.erase(it); else @@ -2364,7 +2070,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (initialMemberCount == 0) { // Try to see if the member was removed because it is only available for storage types. - auto storageType = ReferenceType::copyForLocationIfReference( + auto storageType = TypeProvider::withLocationIfReference( DataLocation::Storage, exprType ); @@ -2378,26 +2084,33 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } string errorMsg = "Member \"" + memberName + "\" not found or not visible " "after argument-dependent lookup in " + exprType->toString() + "."; - if (memberName == "value") - { - errorMsg.pop_back(); - errorMsg += " - did you forget the \"payable\" modifier?"; - } - else if (exprType->category() == Type::Category::Function) + + if (auto const& funType = dynamic_cast(exprType)) { - if (auto const& funType = dynamic_pointer_cast(exprType)) + auto const& t = funType->returnParameterTypes(); + + if (memberName == "value") { - auto const& t = funType->returnParameterTypes(); - if (t.size() == 1) - if ( - t.front()->category() == Type::Category::Contract || - t.front()->category() == Type::Category::Struct - ) - errorMsg += " Did you intend to call the function?"; + 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."; } + else if ( + t.size() == 1 && + (t.front()->category() == Type::Category::Struct || + t.front()->category() == Type::Category::Contract) + ) + errorMsg += " Did you intend to call the function?"; } - if (exprType->category() == Type::Category::Contract) - for (auto const& addressMember: AddressType::addressPayable().nativeMembers(nullptr)) + else if (exprType->category() == Type::Category::Contract) + { + for (auto const& addressMember: TypeProvider::payableAddress()->nativeMembers(nullptr)) if (addressMember.name == memberName) { Identifier const* var = dynamic_cast(&_memberAccess.expression()); @@ -2405,6 +2118,21 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) errorMsg += " Use \"address(" + varName + ")." + memberName + "\" to access this address member."; break; } + } + else if (auto addressType = dynamic_cast(exprType)) + { + // Trigger error when using send or transfer with a non-payable fallback function. + if (memberName == "send" || memberName == "transfer") + { + solAssert( + addressType->stateMutability() != StateMutability::Payable, + "Expected address not-payable as members were not found" + ); + + errorMsg = "\"send\" and \"transfer\" are only available for objects of type \"address payable\", not \"" + exprType->toString() + "\"."; + } + } + m_errorReporter.fatalTypeError( _memberAccess.location(), errorMsg @@ -2421,16 +2149,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) annotation.referencedDeclaration = possibleMembers.front().declaration; annotation.type = possibleMembers.front().type; - if (auto funType = dynamic_cast(annotation.type.get())) - if (funType->bound() && !exprType->isImplicitlyConvertibleTo(*funType->selfType())) - m_errorReporter.typeError( - _memberAccess.location(), - "Function \"" + memberName + "\" cannot be called on an object of type " + - exprType->toString() + " (expected " + funType->selfType()->toString() + ")." - ); + if (auto funType = dynamic_cast(annotation.type)) + solAssert( + !funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()), + "Function \"" + memberName + "\" cannot be called on an object of type " + + exprType->toString() + " (expected " + funType->selfType()->toString() + ")." + ); - if (exprType->category() == Type::Category::Struct) - annotation.isLValue = true; + if (auto const* structType = dynamic_cast(exprType)) + annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); else if (exprType->category() == Type::Category::Array) { auto const& arrayType(dynamic_cast(*exprType)); @@ -2442,40 +2169,38 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) } else if (exprType->category() == Type::Category::FixedBytes) annotation.isLValue = false; - else if (TypeType const* typeType = dynamic_cast(exprType.get())) + else if (TypeType const* typeType = dynamic_cast(exprType)) { - if (ContractType const* contractType = dynamic_cast(typeType->actualType().get())) + if (ContractType const* contractType = dynamic_cast(typeType->actualType())) annotation.isLValue = annotation.referencedDeclaration->isLValue(); } - if (exprType->category() == Type::Category::Contract) + // TODO some members might be pure, but for example `address(0x123).balance` is not pure + // although every subexpression is, so leaving this limited for now. + if (auto tt = dynamic_cast(exprType)) + if (tt->actualType()->category() == Type::Category::Enum) + annotation.isPure = true; + if (auto magicType = dynamic_cast(exprType)) { - // Warn about using send or transfer with a non-payable fallback function. - if (auto callType = dynamic_cast(type(_memberAccess).get())) + if (magicType->kind() == MagicType::Kind::ABI) + annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && ( + memberName == "creationCode" || memberName == "runtimeCode" + )) { - auto kind = callType->kind(); - auto contractType = dynamic_cast(exprType.get()); - solAssert(!!contractType, "Should be contract type."); - - if ( - (kind == FunctionType::Kind::Send || kind == FunctionType::Kind::Transfer) && - !contractType->isPayable() - ) + annotation.isPure = true; + m_scope->annotation().contractDependencies.insert( + &dynamic_cast(*magicType->typeArgument()).contractDefinition() + ); + if (contractDependenciesAreCyclic(*m_scope)) m_errorReporter.typeError( _memberAccess.location(), - "Value transfer to a contract without a payable fallback function." + "Circular reference for contract code access." ); } - } - - // TODO some members might be pure, but for example `address(0x123).balance` is not pure - // although every subexpression is, so leaving this limited for now. - if (auto tt = dynamic_cast(exprType.get())) - if (tt->actualType()->category() == Type::Category::Enum) - annotation.isPure = true; - if (auto magicType = dynamic_cast(exprType.get())) - if (magicType->kind() == MagicType::Kind::ABI) + else if (magicType->kind() == MagicType::Kind::MetaType && memberName == "name") annotation.isPure = true; + } return false; } @@ -2484,7 +2209,7 @@ bool TypeChecker::visit(IndexAccess const& _access) { _access.baseExpression().accept(*this); TypePointer baseType = type(_access.baseExpression()); - TypePointer resultType; + TypePointer resultType = nullptr; bool isLValue = false; bool isPure = _access.baseExpression().annotation().isPure; Expression const* index = _access.indexExpression(); @@ -2502,9 +2227,9 @@ bool TypeChecker::visit(IndexAccess const& _access) } else { - expectType(*index, IntegerType(256)); + expectType(*index, *TypeProvider::uint256()); if (!m_errorReporter.hasErrors()) - if (auto numberType = dynamic_cast(type(*index).get())) + if (auto numberType = dynamic_cast(type(*index))) { solAssert(!numberType->isFractional(), ""); if (!actualType.isDynamicallySized() && actualType.length() <= numberType->literalValue(nullptr)) @@ -2529,19 +2254,28 @@ bool TypeChecker::visit(IndexAccess const& _access) case Type::Category::TypeType: { TypeType const& typeType = dynamic_cast(*baseType); + if (dynamic_cast(typeType.actualType())) + m_errorReporter.typeError(_access.location(), "Index access for contracts or libraries is not possible."); if (!index) - resultType = make_shared(make_shared(DataLocation::Memory, typeType.actualType())); + resultType = TypeProvider::typeType(TypeProvider::array(DataLocation::Memory, typeType.actualType())); else { - expectType(*index, IntegerType(256)); - if (auto length = dynamic_cast(type(*index).get())) - resultType = make_shared(make_shared( - DataLocation::Memory, - typeType.actualType(), - length->literalValue(nullptr) - )); + u256 length = 1; + if (expectType(*index, *TypeProvider::uint256())) + { + if (auto indexValue = dynamic_cast(type(*index))) + length = indexValue->literalValue(nullptr); + else + m_errorReporter.fatalTypeError(index->location(), "Integer constant expected."); + } else - m_errorReporter.fatalTypeError(index->location(), "Integer constant expected."); + solAssert(m_errorReporter.hasErrors(), "Expected errors as expectType returned false"); + + resultType = TypeProvider::typeType(TypeProvider::array( + DataLocation::Memory, + typeType.actualType(), + length + )); } break; } @@ -2552,13 +2286,13 @@ bool TypeChecker::visit(IndexAccess const& _access) m_errorReporter.typeError(_access.location(), "Index expression cannot be omitted."); else { - if (!expectType(*index, IntegerType(256))) + if (!expectType(*index, *TypeProvider::uint256())) m_errorReporter.fatalTypeError(_access.location(), "Index expression cannot be represented as an unsigned integer."); - if (auto integerType = dynamic_cast(type(*index).get())) + if (auto integerType = dynamic_cast(type(*index))) if (bytesType.numBytes() <= integerType->literalValue(nullptr)) m_errorReporter.typeError(_access.location(), "Out of bounds array access."); } - resultType = make_shared(1); + resultType = TypeProvider::fixedBytes(1); isLValue = false; // @todo this heavily depends on how it is embedded break; } @@ -2568,7 +2302,7 @@ bool TypeChecker::visit(IndexAccess const& _access) "Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")" ); } - _access.annotation().type = move(resultType); + _access.annotation().type = resultType; _access.annotation().isLValue = isLValue; if (index && !index->annotation().isPure) isPure = false; @@ -2582,7 +2316,7 @@ bool TypeChecker::visit(Identifier const& _identifier) IdentifierAnnotation& annotation = _identifier.annotation(); if (!annotation.referencedDeclaration) { - if (!annotation.argumentTypes) + if (!annotation.arguments) { // The identifier should be a public state variable shadowing other functions vector candidates; @@ -2612,7 +2346,7 @@ bool TypeChecker::visit(Identifier const& _identifier) // TODO: Check if we need to send Special Modifier here. FunctionTypePointer functionType = declaration->functionType(true); solAssert(!!functionType, "Requested type not present."); - if (functionType->canTakeArguments(*annotation.argumentTypes)) + if (functionType->canTakeArguments(*annotation.arguments)) candidates.push_back(declaration); } if (candidates.empty()) @@ -2634,14 +2368,36 @@ bool TypeChecker::visit(Identifier const& _identifier) if (auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast(annotation.referencedDeclaration)) - if (dynamic_cast(annotation.type.get())) + { + if (dynamic_cast(annotation.type)) annotation.isPure = true; + } + else if (dynamic_cast(annotation.type)) + annotation.isPure = true; + + + // Check for deprecated function names. + // The check is done here for the case without an actual function call. + if (FunctionType const* fType = dynamic_cast(_identifier.annotation().type)) + { + if (_identifier.name() == "sha3" && fType->kind() == FunctionType::Kind::KECCAK256) + m_errorReporter.typeError( + _identifier.location(), + "\"sha3\" has been deprecated in favour of \"keccak256\"" + ); + else if (_identifier.name() == "suicide" && fType->kind() == FunctionType::Kind::Selfdestruct) + m_errorReporter.typeError( + _identifier.location(), + "\"suicide\" has been deprecated in favour of \"selfdestruct\"" + ); + } + return false; } void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) { - _expr.annotation().type = make_shared(Type::fromElementaryTypeName(_expr.typeName())); + _expr.annotation().type = TypeProvider::typeType(TypeProvider::fromElementaryTypeName(_expr.typeName())); _expr.annotation().isPure = true; } @@ -2650,7 +2406,7 @@ void TypeChecker::endVisit(Literal const& _literal) if (_literal.looksLikeAddress()) { // Assign type here if it even looks like an address. This prevents double errors for invalid addresses - _literal.annotation().type = make_shared(StateMutability::Payable); + _literal.annotation().type = TypeProvider::payableAddress(); string msg; if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits @@ -2689,7 +2445,7 @@ void TypeChecker::endVisit(Literal const& _literal) ); if (!_literal.annotation().type) - _literal.annotation().type = Type::forLiteral(_literal); + _literal.annotation().type = TypeProvider::forLiteral(_literal); if (!_literal.annotation().type) m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value."); @@ -2730,30 +2486,32 @@ bool TypeChecker::expectType(Expression const& _expression, Type const& _expecte _expression.accept(*this); if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType)) { + auto errorMsg = "Type " + + type(_expression)->toString() + + " is not implicitly convertible to expected type " + + _expectedType.toString(); if ( type(_expression)->category() == Type::Category::RationalNumber && - dynamic_pointer_cast(type(_expression))->isFractional() && + dynamic_cast(type(_expression))->isFractional() && type(_expression)->mobileType() ) - m_errorReporter.typeError( - _expression.location(), - "Type " + - toStringInChecker(*type(_expression)) + - " is not implicitly convertible to expected type " + - toStringInChecker(_expectedType) + - ". Try converting to type " + - toStringInChecker(*type(_expression)->mobileType()) + - " or use an explicit conversion." - ); + { + if (_expectedType.operator==(*type(_expression)->mobileType())) + m_errorReporter.typeError( + _expression.location(), + errorMsg + ", but it can be explicitly converted." + ); + else + m_errorReporter.typeError( + _expression.location(), + errorMsg + + ". Try converting to type " + + type(_expression)->mobileType()->toString() + + " or use an explicit conversion." + ); + } else - m_errorReporter.typeError( - _expression.location(), - "Type " + - toStringInChecker(*type(_expression)) + - " is not implicitly convertible to expected type " + - toStringInChecker(_expectedType) + - "." - ); + m_errorReporter.typeError(_expression.location(), errorMsg + "."); return false; } return true; @@ -2764,9 +2522,48 @@ void TypeChecker::requireLValue(Expression const& _expression) _expression.annotation().lValueRequested = true; _expression.accept(*this); - if (_expression.annotation().isConstant) - m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable."); - else if (!_expression.annotation().isLValue) - m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue."); + if (_expression.annotation().isLValue) + return; + + return m_errorReporter.typeError(_expression.location(), [&]() { + if (_expression.annotation().isConstant) + return "Cannot assign to a constant variable."; + + if (auto indexAccess = dynamic_cast(&_expression)) + { + if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes) + return "Single bytes in fixed bytes arrays cannot be modified."; + else if (auto arrayType = dynamic_cast(type(indexAccess->baseExpression()))) + if (arrayType->dataStoredIn(DataLocation::CallData)) + return "Calldata arrays are read-only."; + } + + if (auto memberAccess = dynamic_cast(&_expression)) + { + if (auto structType = dynamic_cast(type(memberAccess->expression()))) + { + if (structType->dataStoredIn(DataLocation::CallData)) + return "Calldata structs are read-only."; + } + else if (auto arrayType = dynamic_cast(type(memberAccess->expression()))) + if (memberAccess->memberName() == "length") + switch (arrayType->location()) + { + case DataLocation::Memory: + return "Memory arrays cannot be resized."; + case DataLocation::CallData: + return "Calldata arrays cannot be resized."; + case DataLocation::Storage: + break; + } + } + + if (auto identifier = dynamic_cast(&_expression)) + if (auto varDecl = dynamic_cast(identifier->annotation().referencedDeclaration)) + if (varDecl->isExternalCallableParameter() && dynamic_cast(identifier->annotation().type)) + return "External function arguments of reference type are read-only."; + + return "Expression has to be an lvalue."; + }()); } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index c76fa4667..8f532a89c 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -22,20 +22,23 @@ #pragma once -#include +#include -#include #include #include #include +#include + +namespace langutil +{ +class ErrorReporter; +} namespace dev { namespace solidity { -class ErrorReporter; - /** * The module that performs type analysis on the AST, checks the applicability of operations on * those types and stores errors for invalid operations. @@ -45,7 +48,7 @@ class TypeChecker: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - TypeChecker(EVMVersion _evmVersion, ErrorReporter& _errorReporter): + TypeChecker(langutil::EVMVersion _evmVersion, langutil::ErrorReporter& _errorReporter): m_evmVersion(_evmVersion), m_errorReporter(_errorReporter) {} @@ -60,30 +63,11 @@ class TypeChecker: private ASTConstVisitor /// (this can happen for variables with non-explicit types before their types are resolved) TypePointer const& type(VariableDeclaration const& _variable) const; + static bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall); + private: - virtual bool visit(ContractDefinition const& _contract) override; - /// Checks that two functions defined in this contract with the same name have different - /// arguments and that there is at most one constructor. - void checkContractDuplicateFunctions(ContractDefinition const& _contract); - void checkContractDuplicateEvents(ContractDefinition const& _contract); - void checkContractIllegalOverrides(ContractDefinition const& _contract); - /// Reports a type error with an appropriate message if overridden function signature differs. - /// Also stores the direct super function in the AST annotations. - void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); - void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); - void checkContractAbstractFunctions(ContractDefinition const& _contract); - void checkContractBaseConstructorArguments(ContractDefinition const& _contract); - void annotateBaseConstructorArguments( - ContractDefinition const& _currentContract, - FunctionDefinition const* _baseConstructor, - ASTNode const* _argumentNode - ); - /// Checks that different functions with external visibility end up having different - /// external argument types (i.e. different signature). - void checkContractExternalTypeClashes(ContractDefinition const& _contract); - /// Checks that all requirements for a library are fulfilled if this is a library. - void checkLibraryRequirements(ContractDefinition const& _contract); + bool visit(ContractDefinition const& _contract) override; /// Checks (and warns) if a tuple assignment might cause unexpected overwrites in storage. /// Should only be called if the left hand side is tuple-typed. void checkDoubleStorageAssignment(Assignment const& _assignment); @@ -99,6 +83,8 @@ class TypeChecker: private ASTConstVisitor bool _abiEncoderV2 ); + TypePointers typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall); + /// Performs type checks and determines result types for type conversion FunctionCall nodes. TypePointer typeCheckTypeConversionAndRetrieveReturnType( FunctionCall const& _functionCall @@ -122,40 +108,37 @@ class TypeChecker: private ASTConstVisitor FunctionTypePointer _functionType ); - virtual void endVisit(InheritanceSpecifier const& _inheritance) override; - virtual void endVisit(UsingForDirective const& _usingFor) override; - virtual bool visit(StructDefinition const& _struct) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual bool visit(VariableDeclaration const& _variable) override; + void endVisit(InheritanceSpecifier const& _inheritance) override; + void endVisit(UsingForDirective const& _usingFor) override; + bool visit(StructDefinition const& _struct) override; + bool visit(FunctionDefinition const& _function) override; + bool visit(VariableDeclaration const& _variable) override; /// We need to do this manually because we want to pass the bases of the current contract in /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector const& _bases); - virtual bool visit(EventDefinition const& _eventDef) override; - virtual void endVisit(FunctionTypeName const& _funType) override; - virtual bool visit(InlineAssembly const& _inlineAssembly) override; - virtual bool visit(IfStatement const& _ifStatement) override; - virtual bool visit(WhileStatement const& _whileStatement) override; - virtual bool visit(ForStatement const& _forStatement) override; - virtual void endVisit(Return const& _return) override; - virtual bool visit(EmitStatement const&) override { m_insideEmitStatement = true; return true; } - virtual void endVisit(EmitStatement const& _emit) override; - virtual bool visit(VariableDeclarationStatement const& _variable) override; - virtual void endVisit(ExpressionStatement const& _statement) override; - virtual bool visit(Conditional const& _conditional) override; - virtual bool visit(Assignment const& _assignment) override; - virtual bool visit(TupleExpression const& _tuple) override; - virtual void endVisit(BinaryOperation const& _operation) override; - virtual bool visit(UnaryOperation const& _operation) override; - virtual bool visit(FunctionCall const& _functionCall) override; - virtual void endVisit(NewExpression const& _newExpression) override; - virtual bool visit(MemberAccess const& _memberAccess) override; - virtual bool visit(IndexAccess const& _indexAccess) override; - virtual bool visit(Identifier const& _identifier) override; - virtual void endVisit(ElementaryTypeNameExpression const& _expr) override; - virtual void endVisit(Literal const& _literal) override; - - template - void findDuplicateDefinitions(std::map> const& _definitions, std::string _message); + bool visit(EventDefinition const& _eventDef) override; + void endVisit(FunctionTypeName const& _funType) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(WhileStatement const& _whileStatement) override; + bool visit(ForStatement const& _forStatement) override; + void endVisit(Return const& _return) override; + bool visit(EmitStatement const&) override { m_insideEmitStatement = true; return true; } + void endVisit(EmitStatement const& _emit) override; + bool visit(VariableDeclarationStatement const& _variable) override; + void endVisit(ExpressionStatement const& _statement) override; + bool visit(Conditional const& _conditional) override; + bool visit(Assignment const& _assignment) override; + bool visit(TupleExpression const& _tuple) override; + void endVisit(BinaryOperation const& _operation) override; + bool visit(UnaryOperation const& _operation) override; + bool visit(FunctionCall const& _functionCall) override; + void endVisit(NewExpression const& _newExpression) override; + bool visit(MemberAccess const& _memberAccess) override; + bool visit(IndexAccess const& _indexAccess) override; + bool visit(Identifier const& _identifier) override; + void endVisit(ElementaryTypeNameExpression const& _expr) override; + void endVisit(Literal const& _literal) override; bool contractDependenciesAreCyclic( ContractDefinition const& _contract, @@ -175,7 +158,7 @@ class TypeChecker: private ASTConstVisitor ContractDefinition const* m_scope = nullptr; - EVMVersion m_evmVersion; + langutil::EVMVersion m_evmVersion; /// Flag indicating whether we are currently inside an EmitStatement. bool m_insideEmitStatement = false; @@ -183,7 +166,7 @@ class TypeChecker: private ASTConstVisitor /// Flag indicating whether we are currently inside a StructDefinition. bool m_insideStruct = false; - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; }; } diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index 6406a6aa2..c335a266d 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -16,16 +16,16 @@ */ #include - -#include - -#include #include - +#include +#include +#include +#include #include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; namespace @@ -34,51 +34,60 @@ namespace class AssemblyViewPureChecker: public boost::static_visitor { public: - explicit AssemblyViewPureChecker(std::function _reportMutability): + explicit AssemblyViewPureChecker( + yul::Dialect const& _dialect, + std::function _reportMutability + ): + m_dialect(_dialect), m_reportMutability(_reportMutability) {} - void operator()(assembly::Label const&) { } - void operator()(assembly::Instruction const& _instruction) + void operator()(yul::Label const&) { } + void operator()(yul::Instruction const& _instruction) { checkInstruction(_instruction.location, _instruction.instruction); } - void operator()(assembly::Literal const&) {} - void operator()(assembly::Identifier const&) {} - void operator()(assembly::FunctionalInstruction const& _instr) + void operator()(yul::Literal const&) {} + void operator()(yul::Identifier const&) {} + void operator()(yul::FunctionalInstruction const& _instr) { checkInstruction(_instr.location, _instr.instruction); for (auto const& arg: _instr.arguments) boost::apply_visitor(*this, arg); } - void operator()(assembly::ExpressionStatement const& _expr) + void operator()(yul::ExpressionStatement const& _expr) { boost::apply_visitor(*this, _expr.expression); } - void operator()(assembly::StackAssignment const&) {} - void operator()(assembly::Assignment const& _assignment) + void operator()(yul::StackAssignment const&) {} + void operator()(yul::Assignment const& _assignment) { boost::apply_visitor(*this, *_assignment.value); } - void operator()(assembly::VariableDeclaration const& _varDecl) + void operator()(yul::VariableDeclaration const& _varDecl) { if (_varDecl.value) boost::apply_visitor(*this, *_varDecl.value); } - void operator()(assembly::FunctionDefinition const& _funDef) + void operator()(yul::FunctionDefinition const& _funDef) { (*this)(_funDef.body); } - void operator()(assembly::FunctionCall const& _funCall) + void operator()(yul::FunctionCall const& _funCall) { + if (yul::EVMDialect const* dialect = dynamic_cast(&m_dialect)) + if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) + if (fun->instruction) + checkInstruction(_funCall.location, *fun->instruction); + for (auto const& arg: _funCall.arguments) boost::apply_visitor(*this, arg); } - void operator()(assembly::If const& _if) + void operator()(yul::If const& _if) { boost::apply_visitor(*this, *_if.condition); (*this)(_if.body); } - void operator()(assembly::Switch const& _switch) + void operator()(yul::Switch const& _switch) { boost::apply_visitor(*this, *_switch.expression); for (auto const& _case: _switch.cases) @@ -88,28 +97,36 @@ class AssemblyViewPureChecker: public boost::static_visitor (*this)(_case.body); } } - void operator()(assembly::ForLoop const& _for) + void operator()(yul::ForLoop const& _for) { (*this)(_for.pre); boost::apply_visitor(*this, *_for.condition); (*this)(_for.body); (*this)(_for.post); } - void operator()(assembly::Block const& _block) + void operator()(yul::Break const&) + { + } + void operator()(yul::Continue const&) + { + } + void operator()(yul::Block const& _block) { for (auto const& s: _block.statements) boost::apply_visitor(*this, s); } private: - std::function m_reportMutability; - void checkInstruction(SourceLocation _location, solidity::Instruction _instruction) + void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction) { if (eth::SemanticInformation::invalidInViewFunctions(_instruction)) m_reportMutability(StateMutability::NonPayable, _location); else if (eth::SemanticInformation::invalidInPureFunctions(_instruction)) m_reportMutability(StateMutability::View, _location); } + + yul::Dialect const& m_dialect; + std::function m_reportMutability; }; } @@ -153,6 +170,7 @@ void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) m_bestMutabilityAndLocation.mutability < _funDef.stateMutability() && _funDef.stateMutability() != StateMutability::Payable && _funDef.isImplemented() && + !_funDef.body().statements().empty() && !_funDef.isConstructor() && !_funDef.isFallback() && !_funDef.annotation().superFunction @@ -215,6 +233,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier) void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) { AssemblyViewPureChecker{ + _inlineAssembly.dialect(), [=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); } }(_inlineAssembly.operations()); } @@ -264,13 +283,13 @@ void ViewPureChecker::reportMutability( if (_nestedLocation) m_errorReporter.typeError( _location, - SecondarySourceLocation().append("\"msg.value\" appears here inside the modifier.", *_nestedLocation), - "This modifier uses \"msg.value\" and thus the function has to be payable or internal." + SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation), + "This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal." ); else m_errorReporter.typeError( _location, - "\"msg.value\" can only be used in payable public functions. Make the function " + "\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function " "\"payable\" or use an internal function to avoid this error." ); m_errors = true; @@ -338,7 +357,10 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::ABI, "encodeWithSignature"}, {MagicType::Kind::Block, "blockhash"}, {MagicType::Kind::Message, "data"}, - {MagicType::Kind::Message, "sig"} + {MagicType::Kind::Message, "sig"}, + {MagicType::Kind::MetaType, "creationCode"}, + {MagicType::Kind::MetaType, "runtimeCode"}, + {MagicType::Kind::MetaType, "name"}, }; set static const payableMembers{ {MagicType::Kind::Message, "value"} @@ -367,8 +389,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) break; } default: + { + if (VariableDeclaration const* varDecl = dynamic_cast( + _memberAccess.annotation().referencedDeclaration + )) + if (varDecl->isStateVariable() && !varDecl->isConstant()) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; break; } + } reportMutability(mutability, _memberAccess.location()); } diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h index d9fa2253e..77142b83e 100644 --- a/libsolidity/analysis/ViewPureChecker.h +++ b/libsolidity/analysis/ViewPureChecker.h @@ -21,11 +21,15 @@ #include #include -#include - #include #include +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + namespace dev { namespace solidity @@ -34,7 +38,7 @@ namespace solidity class ViewPureChecker: private ASTConstVisitor { public: - ViewPureChecker(std::vector> const& _ast, ErrorReporter& _errorReporter): + ViewPureChecker(std::vector> const& _ast, langutil::ErrorReporter& _errorReporter): m_ast(_ast), m_errorReporter(_errorReporter) {} bool check(); @@ -43,36 +47,35 @@ class ViewPureChecker: private ASTConstVisitor struct MutabilityAndLocation { StateMutability mutability; - SourceLocation location; + langutil::SourceLocation location; }; - virtual bool visit(FunctionDefinition const& _funDef) override; - virtual void endVisit(FunctionDefinition const& _funDef) override; - virtual bool visit(ModifierDefinition const& _modifierDef) override; - virtual void endVisit(ModifierDefinition const& _modifierDef) override; - virtual void endVisit(Identifier const& _identifier) override; - virtual bool visit(MemberAccess const& _memberAccess) override; - virtual void endVisit(MemberAccess const& _memberAccess) override; - virtual void endVisit(IndexAccess const& _indexAccess) override; - virtual void endVisit(ModifierInvocation const& _modifier) override; - virtual void endVisit(FunctionCall const& _functionCall) override; - virtual void endVisit(InlineAssembly const& _inlineAssembly) override; - virtual void endVisit(FireAllRulesStatement const& _fars) override; - virtual void endVisit(UnaryOperation const& _op) override; + bool visit(FunctionDefinition const& _funDef) override; + void endVisit(FunctionDefinition const& _funDef) override; + bool visit(ModifierDefinition const& _modifierDef) override; + void endVisit(ModifierDefinition const& _modifierDef) override; + void endVisit(Identifier const& _identifier) override; + bool visit(MemberAccess const& _memberAccess) override; + void endVisit(MemberAccess const& _memberAccess) override; + void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(ModifierInvocation const& _modifier) override; + void endVisit(FunctionCall const& _functionCall) override; + void endVisit(InlineAssembly const& _inlineAssembly) override; + void endVisit(FireAllRulesStatement const& _fars) override; /// Called when an element of mutability @a _mutability is encountered. /// Creates appropriate warnings and errors and sets @a m_currentBestMutability. void reportMutability( StateMutability _mutability, - SourceLocation const& _location, - boost::optional const& _nestedLocation = {} + langutil::SourceLocation const& _location, + boost::optional const& _nestedLocation = {} ); std::vector> const& m_ast; - ErrorReporter& m_errorReporter; + langutil::ErrorReporter& m_errorReporter; bool m_errors = false; - MutabilityAndLocation m_bestMutabilityAndLocation = MutabilityAndLocation{StateMutability::Payable, SourceLocation()}; + MutabilityAndLocation m_bestMutabilityAndLocation = MutabilityAndLocation{StateMutability::Payable, langutil::SourceLocation()}; FunctionDefinition const* m_currentFunction = nullptr; std::map m_inferredMutability; }; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index c3152842f..91d3ea996 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -21,14 +21,13 @@ */ #include + #include #include -#include - +#include #include #include - #include #include @@ -107,7 +106,17 @@ ImportAnnotation& ImportDirective::annotation() const TypePointer ImportDirective::type() const { solAssert(!!annotation().sourceUnit, ""); - return make_shared(*annotation().sourceUnit); + return TypeProvider::module(*annotation().sourceUnit); +} + +vector ContractDefinition::stateVariablesIncludingInherited() const +{ + vector stateVars; + for (auto const& contract: annotation().linearizedBaseContracts) + for (auto var: contract->stateVariables()) + if (*contract == *this || var->isVisibleInDerivedContracts()) + stateVars.push_back(var); + return stateVars; } map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const @@ -140,6 +149,11 @@ bool ContractDefinition::constructorIsPublic() const return !f || f->isPublic(); } +bool ContractDefinition::canBeDeployed() const +{ + return constructorIsPublic() && annotation().unimplementedFunctions.empty(); +} + FunctionDefinition const* ContractDefinition::fallbackFunction() const { for (ContractDefinition const* contract: annotation().linearizedBaseContracts) @@ -185,10 +199,10 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter vector functions; for (FunctionDefinition const* f: contract->definedFunctions()) if (f->isPartOfExternalInterface()) - functions.push_back(make_shared(*f, false, false)); + functions.push_back(TypeProvider::function(*f, false)); for (VariableDeclaration const* v: contract->stateVariables()) if (v->isPartOfExternalInterface()) - functions.push_back(make_shared(*v)); + functions.push_back(TypeProvider::function(*v)); for (FunctionTypePointer const& fun: functions) { if (!fun->interfaceFunctionType()) @@ -199,7 +213,7 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter { signaturesSeen.insert(functionSignature); FixedHash<4> hash(dev::keccak256(functionSignature)); - m_interfaceFunctionList->push_back(make_pair(hash, fun)); + m_interfaceFunctionList->emplace_back(hash, fun); } } } @@ -211,16 +225,12 @@ vector const& ContractDefinition::inheritableMembers() const { if (!m_inheritableMembers) { - set memberSeen; m_inheritableMembers.reset(new vector()); auto addInheritableMember = [&](Declaration const* _decl) { solAssert(_decl, "addInheritableMember got a nullpointer."); - if (memberSeen.count(_decl->name()) == 0 && _decl->isVisibleInDerivedContracts()) - { - memberSeen.insert(_decl->name()); + if (_decl->isVisibleInDerivedContracts()) m_inheritableMembers->push_back(_decl); - } }; for (FunctionDefinition const* f: definedFunctions()) @@ -243,7 +253,7 @@ vector const& ContractDefinition::inheritableMembers() const TypePointer ContractDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::contract(*this)); } ContractDefinitionAnnotation& ContractDefinition::annotation() const @@ -269,7 +279,7 @@ TypeNameAnnotation& TypeName::annotation() const TypePointer StructDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::structType(*this, DataLocation::Storage)); } TypeDeclarationAnnotation& StructDefinition::annotation() const @@ -283,12 +293,12 @@ TypePointer EnumValue::type() const { auto parentDef = dynamic_cast(scope()); solAssert(parentDef, "Enclosing Scope of EnumValue was not set"); - return make_shared(*parentDef); + return TypeProvider::enumType(*parentDef); } TypePointer EnumDefinition::type() const { - return make_shared(make_shared(*this)); + return TypeProvider::typeType(TypeProvider::enumType(*this)); } TypeDeclarationAnnotation& EnumDefinition::annotation() const @@ -316,7 +326,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const case Declaration::Visibility::Private: case Declaration::Visibility::Internal: case Declaration::Visibility::Public: - return make_shared(*this, _internal); + return TypeProvider::function(*this, _internal); case Declaration::Visibility::External: return {}; } @@ -332,7 +342,7 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const return {}; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this, _internal); + return TypeProvider::function(*this, _internal); } } @@ -343,12 +353,12 @@ FunctionTypePointer FunctionDefinition::functionType(bool _internal) const TypePointer FunctionDefinition::type() const { solAssert(visibility() != Declaration::Visibility::External, ""); - return make_shared(*this); + return TypeProvider::function(*this); } string FunctionDefinition::externalSignature() const { - return FunctionType(*this).externalSignature(); + return TypeProvider::function(*this)->externalSignature(); } FunctionDefinitionAnnotation& FunctionDefinition::annotation() const @@ -360,7 +370,7 @@ FunctionDefinitionAnnotation& FunctionDefinition::annotation() const TypePointer ModifierDefinition::type() const { - return make_shared(*this); + return TypeProvider::modifier(*this); } ModifierDefinitionAnnotation& ModifierDefinition::annotation() const @@ -372,15 +382,15 @@ ModifierDefinitionAnnotation& ModifierDefinition::annotation() const TypePointer EventDefinition::type() const { - return make_shared(*this); + return TypeProvider::function(*this); } FunctionTypePointer EventDefinition::functionType(bool _internal) const { if (_internal) - return make_shared(*this); + return TypeProvider::function(*this); else - return {}; + return nullptr; } EventDefinitionAnnotation& EventDefinition::annotation() const @@ -407,6 +417,21 @@ SourceUnit const& Scopable::sourceUnit() const return dynamic_cast(*s); } +CallableDeclaration const* Scopable::functionOrModifierDefinition() const +{ + ASTNode const* s = scope(); + solAssert(s, ""); + while (dynamic_cast(s)) + { + if (auto funDef = dynamic_cast(s)) + return funDef; + if (auto modDef = dynamic_cast(s)) + return modDef; + s = dynamic_cast(s)->scope(); + } + return nullptr; +} + string Scopable::sourceUnitName() const { return sourceUnit().annotation().path; @@ -414,8 +439,13 @@ string Scopable::sourceUnitName() const bool VariableDeclaration::isLValue() const { - // External function parameters and constant declared variables are Read-Only - return !isExternalCallableParameter() && !m_isConstant; + // Constant declared variables are Read-Only + if (m_isConstant) + return false; + // External function arguments of reference type are Read-Only + if (isExternalCallableParameter() && dynamic_cast(type())) + return false; + return true; } bool VariableDeclaration::isLocalVariable() const @@ -512,8 +542,8 @@ bool VariableDeclaration::hasReferenceOrMappingType() const { solAssert(typeName(), ""); solAssert(typeName()->annotation().type, "Can only be called after reference resolution"); - TypePointer const& type = typeName()->annotation().type; - return type->category() == Type::Category::Mapping || dynamic_cast(type.get()); + Type const* type = typeName()->annotation().type; + return type->category() == Type::Category::Mapping || dynamic_cast(type); } set VariableDeclaration::allowedDataLocations() const @@ -561,21 +591,21 @@ TypePointer VariableDeclaration::type() const FunctionTypePointer VariableDeclaration::functionType(bool _internal) const { if (_internal) - return {}; + return nullptr; switch (visibility()) { case Declaration::Visibility::Default: solAssert(false, "visibility() should not return Default"); case Declaration::Visibility::Private: case Declaration::Visibility::Internal: - return {}; + return nullptr; case Declaration::Visibility::Public: case Declaration::Visibility::External: - return make_shared(*this); + return TypeProvider::function(*this); } // To make the compiler happy - return {}; + return nullptr; } VariableDeclarationAnnotation& VariableDeclaration::annotation() const @@ -775,8 +805,8 @@ void NewExpression::replaceChild(Expression*, ASTPointer) } -bool MemberAccess::saveToENISection(ENIHandler& _handler, CompilerContext& _context) const -{ +bool MemberAccess::saveToENISection(ENIHandler& _handler, CompilerContext& _context) const +{ CompilerContext::LocationSetter locationSetter(_context, *this); Declaration const* declaration = this->annotation().referencedDeclaration; auto variable = dynamic_cast(declaration); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 2e4b8073a..3a18b39c6 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,27 +22,30 @@ #pragma once - #include -#include #include #include #include #include +#include -#include +#include #include - #include -#include #include +#include +#include #include #include -#include -#include -#include + +namespace yul +{ +// Forward-declaration to +struct Block; +struct Dialect; +} namespace dev { @@ -61,6 +64,8 @@ class ASTConstVisitor; class ASTNode: private boost::noncopyable { public: + using SourceLocation = langutil::SourceLocation; + explicit ASTNode(SourceLocation const& _location); virtual ~ASTNode(); @@ -75,13 +80,15 @@ class ASTNode: private boost::noncopyable static void listAccept(std::vector const& _list, ASTVisitor& _visitor) { for (T const& element: _list) - element->accept(_visitor); + if (element) + element->accept(_visitor); } template static void listAccept(std::vector const& _list, ASTConstVisitor& _visitor) { for (T const& element: _list) - element->accept(_visitor); + if (element) + element->accept(_visitor); } /// @returns a copy of the vector containing only the nodes which derive from T. @@ -129,9 +136,9 @@ class SourceUnit: public ASTNode SourceUnit(SourceLocation const& _location, std::vector> const& _nodes): ASTNode(_location), m_nodes(_nodes) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; - virtual SourceUnitAnnotation& annotation() const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + SourceUnitAnnotation& annotation() const override; std::vector> nodes() const { return m_nodes; } @@ -158,6 +165,9 @@ class Scopable /// @returns the source unit this scopable is present in. SourceUnit const& sourceUnit() const; + /// @returns the function or modifier definition this scopable is present in or nullptr. + CallableDeclaration const* functionOrModifierDefinition() const; + /// @returns the source name this scopable is present in. /// Can be combined with annotation().canonicalName (if present) to form a globally unique name. std::string sourceUnitName() const; @@ -262,8 +272,8 @@ class PragmaDirective: public ASTNode ): ASTNode(_location), m_tokens(_tokens), m_literals(_literals) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector const& tokens() const { return m_tokens; } std::vector const& literals() const { return m_literals; } @@ -288,35 +298,44 @@ class PragmaDirective: public ASTNode class ImportDirective: public Declaration { public: + struct SymbolAlias + { + ASTPointer symbol; + ASTPointer alias; + SourceLocation location; + }; + + using SymbolAliasList = std::vector; + ImportDirective( SourceLocation const& _location, ASTPointer const& _path, ASTPointer const& _unitAlias, - std::vector, ASTPointer>>&& _symbolAliases + SymbolAliasList _symbolAliases ): Declaration(_location, _unitAlias), m_path(_path), - m_symbolAliases(_symbolAliases) + m_symbolAliases(move(_symbolAliases)) { } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ASTString const& path() const { return *m_path; } - std::vector, ASTPointer>> const& symbolAliases() const + SymbolAliasList const& symbolAliases() const { return m_symbolAliases; } - virtual ImportAnnotation& annotation() const override; + ImportAnnotation& annotation() const override; - virtual TypePointer type() const override; + TypePointer type() const override; private: ASTPointer m_path; /// The aliases for the specific symbols to import. If non-empty import the specific symbols. - /// If the second component is empty, import the identifier unchanged. + /// If the `alias` component is empty, import the identifier unchanged. /// If both m_unitAlias and m_symbolAlias are empty, import all symbols into the current scope. - std::vector, ASTPointer>> m_symbolAliases; + SymbolAliasList m_symbolAliases; }; /** @@ -395,8 +414,8 @@ class ContractDefinition: public Declaration, public Documented m_contractKind(_contractKind) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& baseContracts() const { return m_baseContracts; } std::vector> const& subNodes() const { return m_subNodes; } @@ -404,11 +423,13 @@ class ContractDefinition: public Declaration, public Documented std::vector definedStructs() const { return filteredNodes(m_subNodes); } std::vector definedEnums() const { return filteredNodes(m_subNodes); } std::vector stateVariables() const { return filteredNodes(m_subNodes); } + std::vector stateVariablesIncludingInherited() const; std::vector functionModifiers() const { return filteredNodes(m_subNodes); } std::vector definedFunctions() const { return filteredNodes(m_subNodes); } std::vector events() const { return filteredNodes(m_subNodes); } std::vector rules() const; std::vector const& interfaceEvents() const; + bool isInterface() const { return m_contractKind == ContractKind::Interface; } bool isLibrary() const { return m_contractKind == ContractKind::Library; } /// @returns a map of canonical function signatures to FunctionDefinitions @@ -423,14 +444,18 @@ class ContractDefinition: public Declaration, public Documented FunctionDefinition const* constructor() const; /// @returns true iff the constructor of this contract is public (or non-existing). bool constructorIsPublic() const; + /// @returns true iff the contract can be deployed, i.e. is not abstract and has a + /// public constructor. + /// Should only be called after the type checker has run. + bool canBeDeployed() const; /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } - virtual TypePointer type() const override; + TypePointer type() const override; - virtual ContractDefinitionAnnotation& annotation() const override; + ContractDefinitionAnnotation& annotation() const override; ContractKind contractKind() const { return m_contractKind; } @@ -454,8 +479,8 @@ class InheritanceSpecifier: public ASTNode ): ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; UserDefinedTypeName const& name() const { return *m_baseName; } // Returns nullptr if no argument list was given (``C``). @@ -483,8 +508,8 @@ class UsingForDirective: public ASTNode ): ASTNode(_location), m_libraryName(_libraryName), m_typeName(_typeName) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; UserDefinedTypeName const& libraryName() const { return *m_libraryName; } /// @returns the type name the library is attached to, null for `*`. @@ -505,14 +530,14 @@ class StructDefinition: public Declaration ): Declaration(_location, _name), m_members(_members) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& members() const { return m_members; } - virtual TypePointer type() const override; + TypePointer type() const override; - virtual TypeDeclarationAnnotation& annotation() const override; + TypeDeclarationAnnotation& annotation() const override; private: std::vector> m_members; @@ -527,14 +552,14 @@ class EnumDefinition: public Declaration std::vector> const& _members ): Declaration(_location, _name), m_members(_members) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& members() const { return m_members; } - virtual TypePointer type() const override; + TypePointer type() const override; - virtual TypeDeclarationAnnotation& annotation() const override; + TypeDeclarationAnnotation& annotation() const override; private: std::vector> m_members; @@ -549,10 +574,10 @@ class EnumValue: public Declaration EnumValue(SourceLocation const& _location, ASTPointer const& _name): Declaration(_location, _name) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; - virtual TypePointer type() const override; + TypePointer type() const override; }; /** @@ -568,8 +593,8 @@ class ParameterList: public ASTNode std::vector> const& _parameters ): ASTNode(_location), m_parameters(_parameters) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& parameters() const { return m_parameters; } @@ -598,6 +623,7 @@ class CallableDeclaration: public Declaration, public VariableScope } std::vector> const& parameters() const { return m_parameters->parameters(); } + std::vector> const& returnParameters() const { return m_returnParameters->parameters(); } ParameterList const& parameterList() const { return *m_parameters; } ASTPointer const& returnParameterList() const { return m_returnParameters; } @@ -632,8 +658,8 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public m_body(_body) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; StateMutability stateMutability() const { return m_stateMutability; } bool isConstructor() const { return m_isConstructor; } @@ -641,13 +667,12 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public bool isPayable() const { return m_stateMutability == StateMutability::Payable; } bool isFreeGas() const { return m_specialModifier == SpecialModifier::FreeGas; } std::vector> const& modifiers() const { return m_functionModifiers; } - std::vector> const& returnParameters() const { return m_returnParameters->parameters(); } Block const& body() const { solAssert(m_body, ""); return *m_body; } - virtual bool isVisibleInContract() const override + bool isVisibleInContract() const override { return Declaration::isVisibleInContract() && !isConstructor() && !isFallback(); } - virtual bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); } + bool isPartOfExternalInterface() const override { return isPublic() && !isConstructor() && !isFallback(); } /// @returns the external signature of the function /// That consists of the name of the function followed by the types of the @@ -656,13 +681,13 @@ class FunctionDefinition: public CallableDeclaration, public Documented, public ContractDefinition::ContractKind inContractKind() const; - virtual TypePointer type() const override; + TypePointer type() const override; /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. - virtual FunctionTypePointer functionType(bool /*_internal*/) const override; + FunctionTypePointer functionType(bool /*_internal*/) const override; - virtual FunctionDefinitionAnnotation& annotation() const override; + FunctionDefinitionAnnotation& annotation() const override; private: SpecialModifier m_specialModifier; @@ -701,14 +726,14 @@ class VariableDeclaration: public Declaration m_location(_referenceLocation) { } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; TypeName* typeName() const { return m_typeName.get(); } ASTPointer const& value() const { return m_value; } - virtual bool isLValue() const override; - virtual bool isPartOfExternalInterface() const override { return isPublic(); } + bool isLValue() const override; + bool isPartOfExternalInterface() const override { return isPublic(); } /// @returns true iff this variable is the parameter (or return parameter) of a function /// (or function type name or event) or declared inside a function body. @@ -742,13 +767,13 @@ class VariableDeclaration: public Declaration /// @returns a set of allowed storage locations for the variable. std::set allowedDataLocations() const; - virtual TypePointer type() const override; + TypePointer type() const override; /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @returns null when it is not accessible as a function. - virtual FunctionTypePointer functionType(bool /*_internal*/) const override; + FunctionTypePointer functionType(bool /*_internal*/) const override; - virtual VariableDeclarationAnnotation& annotation() const override; + VariableDeclarationAnnotation& annotation() const override; protected: Visibility defaultVisibility() const override { return Visibility::Internal; } @@ -783,14 +808,14 @@ class ModifierDefinition: public CallableDeclaration, public Documented { } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Block const& body() const { return *m_body; } - virtual TypePointer type() const override; + TypePointer type() const override; - virtual ModifierDefinitionAnnotation& annotation() const override; + ModifierDefinitionAnnotation& annotation() const override; private: ASTPointer m_body; @@ -809,8 +834,8 @@ class ModifierInvocation: public ASTNode ): ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ASTPointer const& name() const { return m_modifierName; } // Returns nullptr if no argument list was given (``mod``). @@ -842,15 +867,15 @@ class EventDefinition: public CallableDeclaration, public Documented { } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; bool isAnonymous() const { return m_anonymous; } - virtual TypePointer type() const override; - virtual FunctionTypePointer functionType(bool /*_internal*/) const override; + TypePointer type() const override; + FunctionTypePointer functionType(bool /*_internal*/) const override; - virtual EventDefinitionAnnotation& annotation() const override; + EventDefinitionAnnotation& annotation() const override; private: bool m_anonymous = false; @@ -863,26 +888,27 @@ class EventDefinition: public CallableDeclaration, public Documented class MagicVariableDeclaration: public Declaration { public: - MagicVariableDeclaration(ASTString const& _name, std::shared_ptr const& _type): + MagicVariableDeclaration(ASTString const& _name, Type const* _type): Declaration(SourceLocation(), std::make_shared(_name)), m_type(_type) {} - virtual void accept(ASTVisitor&) override + + void accept(ASTVisitor&) override { solAssert(false, "MagicVariableDeclaration used inside real AST."); } - virtual void accept(ASTConstVisitor&) const override + void accept(ASTConstVisitor&) const override { solAssert(false, "MagicVariableDeclaration used inside real AST."); } - virtual FunctionTypePointer functionType(bool) const override + FunctionType const* functionType(bool) const override { solAssert(m_type->category() == Type::Category::Function, ""); - return std::dynamic_pointer_cast(m_type); + return dynamic_cast(m_type); } - virtual TypePointer type() const override { return m_type; } + TypePointer type() const override { return m_type; } private: - std::shared_ptr m_type; + Type const* m_type; }; /// Types @@ -897,7 +923,7 @@ class TypeName: public ASTNode explicit TypeName(SourceLocation const& _location): ASTNode(_location) {} public: - virtual TypeNameAnnotation& annotation() const override; + TypeNameAnnotation& annotation() const override; }; /** @@ -916,8 +942,8 @@ class ElementaryTypeName: public TypeName solAssert(!_stateMutability.is_initialized() || _elem.token() == Token::Address, ""); } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ElementaryTypeNameToken const& typeName() const { return m_type; } @@ -936,12 +962,12 @@ class UserDefinedTypeName: public TypeName public: UserDefinedTypeName(SourceLocation const& _location, std::vector const& _namePath): TypeName(_location), m_namePath(_namePath) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector const& namePath() const { return m_namePath; } - virtual UserDefinedTypeNameAnnotation& annotation() const override; + UserDefinedTypeNameAnnotation& annotation() const override; private: std::vector m_namePath; @@ -964,8 +990,8 @@ class FunctionTypeName: public TypeName TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), m_visibility(_visibility), m_specialModifier(_specialModifier), m_stateMutability(_stateMutability) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& parameterTypes() const { return m_parameterTypes->parameters(); } std::vector> const& returnParameterTypes() const { return m_returnTypes->parameters(); } @@ -1004,8 +1030,8 @@ class Mapping: public TypeName ASTPointer const& _valueType ): TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ElementaryTypeName const& keyType() const { return *m_keyType; } TypeName const& valueType() const { return *m_valueType; } @@ -1027,8 +1053,8 @@ class ArrayTypeName: public TypeName ASTPointer const& _length ): TypeName(_location), m_baseType(_baseType), m_length(_length) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; TypeName const& baseType() const { return *m_baseType; } Expression const* length() const { return m_length.get(); } @@ -1056,15 +1082,9 @@ class Statement: public ASTNode, public Documented ): ASTNode(_location), Documented(_docString) { } - virtual StatementAnnotation& annotation() const override; + StatementAnnotation& annotation() const override; }; -namespace assembly -{ -// Forward-declaration to AsmData.h -struct Block; -} - /** * Inline assembly. */ @@ -1074,18 +1094,21 @@ class InlineAssembly: public Statement InlineAssembly( SourceLocation const& _location, ASTPointer const& _docString, - std::shared_ptr const& _operations + yul::Dialect const& _dialect, + std::shared_ptr const& _operations ): - Statement(_location, _docString), m_operations(_operations) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; - assembly::Block const& operations() const { return *m_operations; } + yul::Dialect const& dialect() const { return m_dialect; } + yul::Block const& operations() const { return *m_operations; } - virtual InlineAssemblyAnnotation& annotation() const override; + InlineAssemblyAnnotation& annotation() const override; private: - std::shared_ptr m_operations; + yul::Dialect const& m_dialect; + std::shared_ptr m_operations; }; /** @@ -1100,8 +1123,8 @@ class Block: public Statement, public Scopable std::vector> const& _statements ): Statement(_location, _docString), m_statements(_statements) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& statements() const { return m_statements; } @@ -1121,8 +1144,8 @@ class PlaceholderStatement: public Statement ASTPointer const& _docString ): Statement(_location, _docString) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; }; /** @@ -1144,8 +1167,8 @@ class IfStatement: public Statement m_trueBody(_trueBody), m_falseBody(_falseBody) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& condition() const { return *m_condition; } Statement const& trueStatement() const { return *m_trueBody; } @@ -1182,8 +1205,8 @@ class WhileStatement: public BreakableStatement ): BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body), m_isDoWhile(_isDoWhile) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& condition() const { return *m_condition; } Statement const& body() const { return *m_body; } @@ -1215,8 +1238,8 @@ class ForStatement: public BreakableStatement, public Scopable m_loopExpression(_loopExpression), m_body(_body) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Statement const* initializationExpression() const { return m_initExpression.get(); } Expression const* condition() const { return m_condExpression.get(); } @@ -1256,10 +1279,10 @@ class FireAllRulesStatement: public Statement class Continue: public Statement { public: - explicit Continue(SourceLocation const& _location, ASTPointer const& _docString): + explicit Continue(SourceLocation const& _location, ASTPointer const& _docString): Statement(_location, _docString) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; }; class Break: public Statement @@ -1267,8 +1290,8 @@ class Break: public Statement public: explicit Break(SourceLocation const& _location, ASTPointer const& _docString): Statement(_location, _docString) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; }; class Return: public Statement @@ -1279,12 +1302,12 @@ class Return: public Statement ASTPointer const& _docString, ASTPointer _expression ): Statement(_location, _docString), m_expression(_expression) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const* expression() const { return m_expression.get(); } - virtual ReturnAnnotation& annotation() const override; + ReturnAnnotation& annotation() const override; private: ASTPointer m_expression; ///< value to return, optional @@ -1298,8 +1321,8 @@ class Throw: public Statement public: explicit Throw(SourceLocation const& _location, ASTPointer const& _docString): Statement(_location, _docString) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; }; /** @@ -1314,8 +1337,8 @@ class EmitStatement: public Statement ASTPointer const& _functionCall ): Statement(_location, _docString), m_eventCall(_functionCall) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; FunctionCall const& eventCall() const { return *m_eventCall; } @@ -1340,10 +1363,9 @@ class VariableDeclarationStatement: public Statement std::vector> const& _variables, ASTPointer const& _initialValue ): - Statement(_location, _docString), m_variables(_variables), m_initialValue(_initialValue) { - } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + Statement(_location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& declarations() const { return m_variables; } Expression const* initialValue() const { return m_initialValue.get(); } @@ -1369,10 +1391,9 @@ class ExpressionStatement: public Statement ASTPointer const& _docString, ASTPointer _expression ): - Statement(_location, _docString), m_expression(_expression) { - } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + Statement(_location, _docString), m_expression(_expression) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } @@ -1417,8 +1438,8 @@ class Conditional: public Expression m_trueExpression(_trueExpression), m_falseExpression(_falseExpression) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& condition() const { return *m_condition; } Expression const& trueExpression() const { return *m_trueExpression; } @@ -1457,8 +1478,8 @@ class Assignment: public Expression { solAssert(TokenTraits::isAssignmentOp(_assignmentOperator), ""); } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& leftHandSide() const { return *m_leftHandSide; } Token assignmentOperator() const { return m_assigmentOperator; } @@ -1494,8 +1515,8 @@ class TupleExpression: public Expression Expression(_location), m_components(_components), m_isArray(_isArray) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; std::vector> const& components() const { return m_components; } bool isInlineArray() const { return m_isArray; } @@ -1531,8 +1552,8 @@ class UnaryOperation: public Expression { solAssert(TokenTraits::isUnaryOp(_operator), ""); } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Token getOperator() const { return m_operator; } bool isPrefixOperation() const { return m_isPrefix; } @@ -1568,8 +1589,8 @@ class BinaryOperation: public Expression { solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& leftExpression() const { return *m_left; } Expression const& rightExpression() const { return *m_right; } @@ -1604,16 +1625,14 @@ class FunctionCall: public Expression std::vector> const& _names ): Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } std::vector> arguments() const { return {m_arguments.begin(), m_arguments.end()}; } std::vector> const& names() const { return m_names; } - virtual void replaceChild(Expression *oldExp, ASTPointer newExp) override; - - virtual FunctionCallAnnotation& annotation() const override; + FunctionCallAnnotation& annotation() const override; virtual ASTPointer deepCopy() const override { @@ -1649,8 +1668,8 @@ class NewExpression: public Expression ASTPointer const& _typeName ): Expression(_location), m_typeName(_typeName) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; TypeName const& typeName() const { return *m_typeName; } @@ -1676,16 +1695,12 @@ class MemberAccess: public Expression ASTPointer const& _memberName ): Expression(_location), m_expression(_expression), m_memberName(_memberName) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; - + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } ASTString const& memberName() const { return *m_memberName; } - void setExpression(ASTPointer _expression) { m_expression = _expression; } - virtual void replaceChild(Expression *oldExp, ASTPointer newExp) override; - - virtual MemberAccessAnnotation& annotation() const override; + MemberAccessAnnotation& annotation() const override; virtual ASTPointer deepCopy() const override { @@ -1699,7 +1714,7 @@ class MemberAccess: public Expression }; /** - * Index access to an array. Example: a[2] + * Index access to an array or mapping. Example: a[2] */ class IndexAccess: public Expression { @@ -1710,8 +1725,8 @@ class IndexAccess: public Expression ASTPointer const& _index ): Expression(_location), m_base(_base), m_index(_index) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Expression const& baseExpression() const { return *m_base; } Expression const* indexExpression() const { return m_index.get(); } @@ -1753,14 +1768,13 @@ class Identifier: public PrimaryExpression SourceLocation const& _location, ASTPointer const& _name ): - PrimaryExpression(_location), m_name(_name) { - } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + PrimaryExpression(_location), m_name(_name) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ASTString const& name() const { return *m_name; } - virtual IdentifierAnnotation& annotation() const override; + IdentifierAnnotation& annotation() const override; bool saveToENISection(ENIHandler&, CompilerContext&) const override; @@ -1783,8 +1797,8 @@ class ElementaryTypeNameExpression: public PrimaryExpression ElementaryTypeNameExpression(SourceLocation const& _location, ElementaryTypeNameToken const& _type): PrimaryExpression(_location), m_typeToken(_type) {} - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; ElementaryTypeNameToken const& typeName() const { return m_typeToken; } @@ -1822,10 +1836,9 @@ class Literal: public PrimaryExpression ASTPointer const& _value, SubDenomination _sub = SubDenomination::None ): - PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) { - } - virtual void accept(ASTVisitor& _visitor) override; - virtual void accept(ASTConstVisitor& _visitor) const override; + PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; Token token() const { return m_token; } /// @returns the non-parsed value of the literal diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index a9a0250fb..3e0da89c5 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -23,12 +23,22 @@ #pragma once #include +#include #include +#include + #include #include -#include #include +#include + +namespace yul +{ +struct AsmAnalysisInfo; +struct Identifier; +struct Dialect; +} namespace dev { @@ -36,11 +46,11 @@ namespace solidity { class Type; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; struct ASTAnnotation { - virtual ~ASTAnnotation() {} + virtual ~ASTAnnotation() = default; }; struct DocTag @@ -51,7 +61,7 @@ struct DocTag struct DocumentedAnnotation { - virtual ~DocumentedAnnotation() {} + virtual ~DocumentedAnnotation() = default; /// Mapping docstring tag name -> content. std::multimap docTags; }; @@ -113,19 +123,13 @@ struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation struct VariableDeclarationAnnotation: ASTAnnotation { /// Type of variable (type of identifier referencing this variable). - TypePointer type; + TypePointer type = nullptr; }; struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation { }; -namespace assembly -{ - struct AsmAnalysisInfo; - struct Identifier; -} - struct InlineAssemblyAnnotation: StatementAnnotation { struct ExternalIdentifierInfo @@ -137,9 +141,9 @@ struct InlineAssemblyAnnotation: StatementAnnotation }; /// Mapping containing resolved references to external identifiers and their value size - std::map externalReferences; + std::map externalReferences; /// Information generated during analysis phase. - std::shared_ptr analysisInfo; + std::shared_ptr analysisInfo; }; struct ReturnAnnotation: StatementAnnotation @@ -157,7 +161,7 @@ struct TypeNameAnnotation: ASTAnnotation { /// Type declared by this type name, i.e. type of a variable where this type name is used. /// Set during reference resolution stage. - TypePointer type; + TypePointer type = nullptr; }; struct UserDefinedTypeNameAnnotation: TypeNameAnnotation @@ -172,7 +176,7 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation struct ExpressionAnnotation: ASTAnnotation { /// Inferred type of the expression. - TypePointer type; + TypePointer type = nullptr; /// Whether the expression is a constant variable bool isConstant = false; /// Whether the expression is pure, i.e. compile-time constant. @@ -181,9 +185,10 @@ struct ExpressionAnnotation: ASTAnnotation bool isLValue = false; /// Whether the expression is used in a context where the LValue is actually required. bool lValueRequested = false; - /// Types of arguments if the expression is a function that is called - used - /// for overload resolution. - std::shared_ptr> argumentTypes; + + /// Types and - if given - names of arguments if the expr. is a function + /// that is called, used for overload resoultion + boost::optional arguments; }; struct IdentifierAnnotation: ExpressionAnnotation @@ -204,7 +209,7 @@ struct BinaryOperationAnnotation: ExpressionAnnotation { /// The common type that is used for the operation, not necessarily the result type (which /// e.g. for comparisons is bool). - TypePointer commonType; + TypePointer commonType = nullptr; }; enum class FunctionCallKind diff --git a/libsolidity/ast/ASTEnums.h b/libsolidity/ast/ASTEnums.h index 5ba21907f..a1a36efbd 100644 --- a/libsolidity/ast/ASTEnums.h +++ b/libsolidity/ast/ASTEnums.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @date 2017 @@ -21,7 +21,8 @@ #pragma once -#include +#include +#include #include @@ -50,5 +51,20 @@ inline std::string stateMutabilityToString(StateMutability const& _stateMutabili } } +class Type; + +/// Container for function call parameter types & names +struct FuncCallArguments +{ + /// Types of arguments + std::vector types; + /// Names of the arguments if given, otherwise unset + std::vector> names; + + size_t numArguments() const { return types.size(); } + size_t numNames() const { return names.size(); } + bool hasNamedArguments() const { return !names.empty(); } +}; + } } diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index b33efa7c4..5997eb82e 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,8 +22,8 @@ #pragma once -#include #include +#include #include // Forward-declare all AST node types @@ -38,6 +38,7 @@ class SourceUnit; class PragmaDirective; class ImportDirective; class Declaration; +class CallableDeclaration; class ContractDefinition; class InheritanceSpecifier; class UsingForDirective; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 2d26ce8a0..5424ee5bc 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @date 2017 @@ -20,13 +20,18 @@ */ #include -#include -#include + #include -#include -#include +#include +#include +#include +#include + +#include +#include using namespace std; +using namespace langutil; namespace dev { @@ -121,8 +126,8 @@ void ASTJsonConverter::setJsonNode( string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location) const { int sourceIndex{-1}; - if (_location.sourceName && m_sourceIndices.count(*_location.sourceName)) - sourceIndex = m_sourceIndices.at(*_location.sourceName); + if (_location.source && m_sourceIndices.count(_location.source->name())) + sourceIndex = m_sourceIndices.at(_location.source->name()); int length = -1; if (_location.start >= 0 && _location.end >= 0) length = _location.end - _location.start; @@ -142,12 +147,12 @@ Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short) return typeDescriptions; } -Json::Value ASTJsonConverter::typePointerToJson(std::shared_ptr> _tps) +Json::Value ASTJsonConverter::typePointerToJson(boost::optional const& _tps) { if (_tps) { Json::Value arguments(Json::arrayValue); - for (auto const& tp: *_tps) + for (auto const& tp: _tps->types) appendMove(arguments, typePointerToJson(tp)); return arguments; } @@ -166,12 +171,12 @@ void ASTJsonConverter::appendExpressionAttributes( make_pair("isPure", _annotation.isPure), make_pair("isLValue", _annotation.isLValue), make_pair("lValueRequested", _annotation.lValueRequested), - make_pair("argumentTypes", typePointerToJson(_annotation.argumentTypes)) + make_pair("argumentTypes", typePointerToJson(_annotation.arguments)) }; _attributes += exprAttributes; } -Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair _info) const +Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair _info) const { Json::Value tuple(Json::objectValue); tuple["src"] = sourceLocationToString(_info.first->location); @@ -233,17 +238,17 @@ bool ASTJsonConverter::visit(ImportDirective const& _node) make_pair(m_legacy ? "SourceUnit" : "sourceUnit", nodeId(*_node.annotation().sourceUnit)), make_pair("scope", idOrNull(_node.scope())) }; - attributes.push_back(make_pair("unitAlias", _node.name())); + attributes.emplace_back("unitAlias", _node.name()); Json::Value symbolAliases(Json::arrayValue); for (auto const& symbolAlias: _node.symbolAliases()) { Json::Value tuple(Json::objectValue); - solAssert(symbolAlias.first, ""); - tuple["foreign"] = nodeId(*symbolAlias.first); - tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue; + solAssert(symbolAlias.symbol, ""); + tuple["foreign"] = nodeId(*symbolAlias.symbol); + tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue; symbolAliases.append(tuple); } - attributes.push_back(make_pair("symbolAliases", std::move(symbolAliases))); + attributes.emplace_back("symbolAliases", std::move(symbolAliases)); setJsonNode(_node, "ImportDirective", std::move(attributes)); return false; } @@ -257,7 +262,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)), make_pair("baseContracts", toJson(_node.baseContracts())), - make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)), + make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)), make_pair("nodes", toJson(_node.subNodes())), make_pair("scope", idOrNull(_node.scope())) }); @@ -356,7 +361,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; if (m_inEvent) - attributes.push_back(make_pair("indexed", _node.isIndexed())); + attributes.emplace_back("indexed", _node.isIndexed()); setJsonNode(_node, "VariableDeclaration", std::move(attributes)); return false; } @@ -454,17 +459,15 @@ 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(assembly::AsmPrinter()(_node.operations()))), + make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))), make_pair("externalReferences", std::move(externalReferences)) }); return false; @@ -646,11 +649,11 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) }; if (m_legacy) { - attributes.push_back(make_pair("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall)); - attributes.push_back(make_pair("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion)); + attributes.emplace_back("isStructConstructorCall", _node.annotation().kind == FunctionCallKind::StructConstructorCall); + attributes.emplace_back("type_conversion", _node.annotation().kind == FunctionCallKind::TypeConversion); } else - attributes.push_back(make_pair("kind", functionCallKind(_node.annotation().kind))); + attributes.emplace_back("kind", functionCallKind(_node.annotation().kind)); appendExpressionAttributes(attributes, _node.annotation()); setJsonNode(_node, "FunctionCall", std::move(attributes)); return false; @@ -699,7 +702,7 @@ bool ASTJsonConverter::visit(Identifier const& _node) make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), make_pair("overloadedDeclarations", overloads), make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)), - make_pair("argumentTypes", typePointerToJson(_node.annotation().argumentTypes)) + make_pair("argumentTypes", typePointerToJson(_node.annotation().arguments)) }); return false; } @@ -723,7 +726,7 @@ bool ASTJsonConverter::visit(Literal const& _node) std::vector> attributes = { make_pair(m_legacy ? "token" : "kind", literalTokenKind(_node.token())), make_pair("value", value), - make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(_node.value())), + make_pair(m_legacy ? "hexvalue" : "hexValue", toHex(asBytes(_node.value()))), make_pair( "subdenomination", subdenomination == Token::Illegal ? diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 8429708cb..7cd1dde39 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Lefteris @@ -22,12 +22,20 @@ #pragma once -#include -#include -#include -#include #include +#include +#include + #include +#include +#include +#include +#include + +namespace langutil +{ +struct SourceLocation; +} namespace dev { @@ -120,7 +128,7 @@ class ASTJsonConverter: public ASTConstVisitor std::string const& _nodeName, std::vector>&& _attributes ); - std::string sourceLocationToString(SourceLocation const& _location) const; + std::string sourceLocationToString(langutil::SourceLocation const& _location) const; static std::string namePathToString(std::vector const& _namePath); static Json::Value idOrNull(ASTNode const* _pt) { @@ -130,7 +138,7 @@ class ASTJsonConverter: public ASTConstVisitor { return _node ? toJson(*_node) : Json::nullValue; } - Json::Value inlineAssemblyIdentifierToJson(std::pair _info) const; + Json::Value inlineAssemblyIdentifierToJson(std::pair _info) const; static std::string location(VariableDeclaration::Location _location); static std::string contractKind(ContractDefinition::ContractKind _kind); static std::string functionCallKind(FunctionCallKind _kind); @@ -142,18 +150,26 @@ class ASTJsonConverter: public ASTConstVisitor return _node.id(); } template - static Json::Value getContainerIds(Container const& container) + static Json::Value getContainerIds(Container const& _container, bool _order = false) { - Json::Value tmp(Json::arrayValue); - for (auto const& element: container) + std::vector tmp; + + for (auto const& element: _container) { solAssert(element, ""); - tmp.append(nodeId(*element)); + tmp.push_back(nodeId(*element)); } - return tmp; + if (_order) + std::sort(tmp.begin(), tmp.end()); + Json::Value json(Json::arrayValue); + + for (int val: tmp) + json.append(val); + + return json; } static Json::Value typePointerToJson(TypePointer _tp, bool _short = false); - static Json::Value typePointerToJson(std::shared_ptr> _tps); + static Json::Value typePointerToJson(boost::optional const& _tps); void appendExpressionAttributes( std::vector> &_attributes, ExpressionAnnotation const& _annotation diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index e7189b565..0453484d5 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -21,13 +21,13 @@ */ #include -#include - -#include +#include #include +#include using namespace std; +using namespace langutil; namespace dev { @@ -114,9 +114,13 @@ bool ASTPrinter::visit(ParameterList const& _node) bool ASTPrinter::visit(FunctionDefinition const& _node) { - writeLine("FunctionDefinition \"" + _node.name() + "\"" + - (_node.isPublic() ? " - public" : "") + - (_node.stateMutability() == StateMutability::View ? " - const" : "")); + writeLine( + "FunctionDefinition \"" + + _node.name() + + "\"" + + (_node.isPublic() ? " - public" : "") + + (_node.stateMutability() == StateMutability::View ? " - const" : "") + ); printSourcePart(_node); return goDeeper(); } @@ -313,8 +317,12 @@ bool ASTPrinter::visit(TupleExpression const& _node) bool ASTPrinter::visit(UnaryOperation const& _node) { - writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + - ") " + TokenTraits::toString(_node.getOperator())); + writeLine( + string("UnaryOperation (") + + (_node.isPrefixOperation() ? "prefix" : "postfix") + + ") " + + TokenTraits::toString(_node.getOperator()) + ); printType(_node); printSourcePart(_node); return goDeeper(); diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index 67ec9b0d8..3ecb93279 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,9 +22,9 @@ #pragma once -#include #include #include +#include namespace dev { diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 451add7fb..1eb9d187f 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,10 +22,10 @@ #pragma once -#include +#include #include +#include #include -#include namespace dev { @@ -277,8 +277,8 @@ class SimpleASTVisitor: public ASTConstVisitor ): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {} protected: - virtual bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; } - virtual void endVisitNode(ASTNode const& _n) override { m_onEndVisit(_n); } + bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; } + void endVisitNode(ASTNode const& _n) override { m_onEndVisit(_n); } private: std::function m_onVisit; diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index 882be9a97..9121e0b98 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index 54aad573c..1d94ae67f 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -35,13 +35,13 @@ enum class ExperimentalFeature TestOnlyAnalysis }; -static const std::map ExperimentalFeatureOnlyAnalysis = +static std::map const ExperimentalFeatureOnlyAnalysis = { { ExperimentalFeature::SMTChecker, true }, { ExperimentalFeature::TestOnlyAnalysis, true }, }; -static const std::map ExperimentalFeatureNames = +static std::map const ExperimentalFeatureNames = { { "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 }, { "SMTChecker", ExperimentalFeature::SMTChecker }, diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp new file mode 100644 index 000000000..6e94c011b --- /dev/null +++ b/libsolidity/ast/TypeProvider.cpp @@ -0,0 +1,541 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace solidity; + +BoolType const TypeProvider::m_boolean{}; +InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; + +/// The string and bytes unique_ptrs are initialized when they are first used because +/// they rely on `byte` being available which we cannot guarantee in the static init context. +unique_ptr TypeProvider::m_bytesStorage; +unique_ptr TypeProvider::m_bytesMemory; +unique_ptr TypeProvider::m_stringStorage; +unique_ptr TypeProvider::m_stringMemory; + +TupleType const TypeProvider::m_emptyTuple{}; +AddressType const TypeProvider::m_payableAddress{StateMutability::Payable}; +AddressType const TypeProvider::m_address{StateMutability::NonPayable}; + +array, 32> const TypeProvider::m_intM{{ + {make_unique(8 * 1, IntegerType::Modifier::Signed)}, + {make_unique(8 * 2, IntegerType::Modifier::Signed)}, + {make_unique(8 * 3, IntegerType::Modifier::Signed)}, + {make_unique(8 * 4, IntegerType::Modifier::Signed)}, + {make_unique(8 * 5, IntegerType::Modifier::Signed)}, + {make_unique(8 * 6, IntegerType::Modifier::Signed)}, + {make_unique(8 * 7, IntegerType::Modifier::Signed)}, + {make_unique(8 * 8, IntegerType::Modifier::Signed)}, + {make_unique(8 * 9, IntegerType::Modifier::Signed)}, + {make_unique(8 * 10, IntegerType::Modifier::Signed)}, + {make_unique(8 * 11, IntegerType::Modifier::Signed)}, + {make_unique(8 * 12, IntegerType::Modifier::Signed)}, + {make_unique(8 * 13, IntegerType::Modifier::Signed)}, + {make_unique(8 * 14, IntegerType::Modifier::Signed)}, + {make_unique(8 * 15, IntegerType::Modifier::Signed)}, + {make_unique(8 * 16, IntegerType::Modifier::Signed)}, + {make_unique(8 * 17, IntegerType::Modifier::Signed)}, + {make_unique(8 * 18, IntegerType::Modifier::Signed)}, + {make_unique(8 * 19, IntegerType::Modifier::Signed)}, + {make_unique(8 * 20, IntegerType::Modifier::Signed)}, + {make_unique(8 * 21, IntegerType::Modifier::Signed)}, + {make_unique(8 * 22, IntegerType::Modifier::Signed)}, + {make_unique(8 * 23, IntegerType::Modifier::Signed)}, + {make_unique(8 * 24, IntegerType::Modifier::Signed)}, + {make_unique(8 * 25, IntegerType::Modifier::Signed)}, + {make_unique(8 * 26, IntegerType::Modifier::Signed)}, + {make_unique(8 * 27, IntegerType::Modifier::Signed)}, + {make_unique(8 * 28, IntegerType::Modifier::Signed)}, + {make_unique(8 * 29, IntegerType::Modifier::Signed)}, + {make_unique(8 * 30, IntegerType::Modifier::Signed)}, + {make_unique(8 * 31, IntegerType::Modifier::Signed)}, + {make_unique(8 * 32, IntegerType::Modifier::Signed)} +}}; + +array, 32> const TypeProvider::m_uintM{{ + {make_unique(8 * 1, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 2, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 3, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 4, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 5, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 6, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 7, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 8, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 9, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 10, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 11, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 12, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 13, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 14, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 15, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 16, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 17, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 18, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 19, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 20, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 21, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 22, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 23, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 24, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 25, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 26, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 27, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 28, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 29, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 30, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 31, IntegerType::Modifier::Unsigned)}, + {make_unique(8 * 32, IntegerType::Modifier::Unsigned)} +}}; + +array, 32> const TypeProvider::m_bytesM{{ + {make_unique(1)}, + {make_unique(2)}, + {make_unique(3)}, + {make_unique(4)}, + {make_unique(5)}, + {make_unique(6)}, + {make_unique(7)}, + {make_unique(8)}, + {make_unique(9)}, + {make_unique(10)}, + {make_unique(11)}, + {make_unique(12)}, + {make_unique(13)}, + {make_unique(14)}, + {make_unique(15)}, + {make_unique(16)}, + {make_unique(17)}, + {make_unique(18)}, + {make_unique(19)}, + {make_unique(20)}, + {make_unique(21)}, + {make_unique(22)}, + {make_unique(23)}, + {make_unique(24)}, + {make_unique(25)}, + {make_unique(26)}, + {make_unique(27)}, + {make_unique(28)}, + {make_unique(29)}, + {make_unique(30)}, + {make_unique(31)}, + {make_unique(32)} +}}; + +array, 4> const TypeProvider::m_magics{{ + {make_unique(MagicType::Kind::Block)}, + {make_unique(MagicType::Kind::Message)}, + {make_unique(MagicType::Kind::Transaction)}, + {make_unique(MagicType::Kind::ABI)} + // MetaType is stored separately +}}; + +inline void clearCache(Type const& type) +{ + type.clearCache(); +} + +template +inline void clearCache(unique_ptr const& type) +{ + // Some lazy-initialized types might not exist yet. + if (type) + type->clearCache(); +} + +template +inline void clearCaches(Container& container) +{ + for (auto const& e: container) + clearCache(e); +} + +void TypeProvider::reset() +{ + clearCache(m_boolean); + clearCache(m_inaccessibleDynamic); + clearCache(m_bytesStorage); + clearCache(m_bytesMemory); + clearCache(m_stringStorage); + clearCache(m_stringMemory); + clearCache(m_emptyTuple); + clearCache(m_payableAddress); + clearCache(m_address); + clearCaches(instance().m_intM); + clearCaches(instance().m_uintM); + clearCaches(instance().m_bytesM); + clearCaches(instance().m_magics); + + instance().m_generalTypes.clear(); + instance().m_stringLiteralTypes.clear(); + instance().m_ufixedMxN.clear(); + instance().m_fixedMxN.clear(); +} + +template +inline T const* TypeProvider::createAndGet(Args&& ... _args) +{ + instance().m_generalTypes.emplace_back(make_unique(std::forward(_args)...)); + return static_cast(instance().m_generalTypes.back().get()); +} + +Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& _type) +{ + solAssert( + TokenTraits::isElementaryTypeName(_type.token()), + "Expected an elementary type name but got " + _type.toString() + ); + + unsigned const m = _type.firstNumber(); + unsigned const n = _type.secondNumber(); + + switch (_type.token()) + { + case Token::IntM: + return integer(m, IntegerType::Modifier::Signed); + case Token::UIntM: + return integer(m, IntegerType::Modifier::Unsigned); + case Token::Byte: + return byte(); + case Token::BytesM: + return fixedBytes(m); + case Token::FixedMxN: + return fixedPoint(m, n, FixedPointType::Modifier::Signed); + case Token::UFixedMxN: + return fixedPoint(m, n, FixedPointType::Modifier::Unsigned); + case Token::Int: + return integer(256, IntegerType::Modifier::Signed); + case Token::UInt: + return integer(256, IntegerType::Modifier::Unsigned); + case Token::Fixed: + return fixedPoint(128, 18, FixedPointType::Modifier::Signed); + case Token::UFixed: + return fixedPoint(128, 18, FixedPointType::Modifier::Unsigned); + case Token::Address: + return address(); + case Token::Bool: + return boolean(); + case Token::Bytes: + return bytesStorage(); + case Token::String: + return stringStorage(); + default: + solAssert( + false, + "Unable to convert elementary typename " + _type.toString() + " to type." + ); + } +} + +TypePointer TypeProvider::fromElementaryTypeName(string const& _name) +{ + vector nameParts; + boost::split(nameParts, _name, boost::is_any_of(" ")); + solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name); + + Token token; + unsigned short firstNum, secondNum; + tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); + + auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); + if (auto* ref = dynamic_cast(t)) + { + DataLocation location = DataLocation::Storage; + if (nameParts.size() == 2) + { + if (nameParts[1] == "storage") + location = DataLocation::Storage; + else if (nameParts[1] == "calldata") + location = DataLocation::CallData; + else if (nameParts[1] == "memory") + location = DataLocation::Memory; + else + solAssert(false, "Unknown data location: " + nameParts[1]); + } + return withLocation(ref, location, true); + } + else if (t->category() == Type::Category::Address) + { + if (nameParts.size() == 2) + { + if (nameParts[1] == "payable") + return payableAddress(); + else + solAssert(false, "Invalid state mutability for address type: " + nameParts[1]); + } + return address(); + } + else + { + solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types"); + return t; + } +} + +ArrayType const* TypeProvider::bytesStorage() +{ + if (!m_bytesStorage) + m_bytesStorage = make_unique(DataLocation::Storage, false); + return m_bytesStorage.get(); +} + +ArrayType const* TypeProvider::bytesMemory() +{ + if (!m_bytesMemory) + m_bytesMemory = make_unique(DataLocation::Memory, false); + return m_bytesMemory.get(); +} + +ArrayType const* TypeProvider::stringStorage() +{ + if (!m_stringStorage) + m_stringStorage = make_unique(DataLocation::Storage, true); + return m_stringStorage.get(); +} + +ArrayType const* TypeProvider::stringMemory() +{ + if (!m_stringMemory) + m_stringMemory = make_unique(DataLocation::Memory, true); + return m_stringMemory.get(); +} + +TypePointer TypeProvider::forLiteral(Literal const& _literal) +{ + switch (_literal.token()) + { + case Token::TrueLiteral: + case Token::FalseLiteral: + return boolean(); + case Token::Number: + return rationalNumber(_literal); + case Token::StringLiteral: + return stringLiteral(_literal.value()); + default: + return nullptr; + } +} + +RationalNumberType const* TypeProvider::rationalNumber(Literal const& _literal) +{ + solAssert(_literal.token() == Token::Number, ""); + std::tuple validLiteral = RationalNumberType::isValidLiteral(_literal); + if (std::get<0>(validLiteral)) + { + TypePointer compatibleBytesType = nullptr; + if (_literal.isHexNumber()) + { + size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2; + if (digitCount % 2 == 0 && (digitCount / 2) <= 32) + compatibleBytesType = fixedBytes(digitCount / 2); + } + + return rationalNumber(std::get<1>(validLiteral), compatibleBytesType); + } + return nullptr; +} + +StringLiteralType const* TypeProvider::stringLiteral(string const& literal) +{ + auto i = instance().m_stringLiteralTypes.find(literal); + if (i != instance().m_stringLiteralTypes.end()) + return i->second.get(); + else + return instance().m_stringLiteralTypes.emplace(literal, make_unique(literal)).first->second.get(); +} + +FixedPointType const* TypeProvider::fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier) +{ + auto& map = _modifier == FixedPointType::Modifier::Unsigned ? instance().m_ufixedMxN : instance().m_fixedMxN; + + auto i = map.find(make_pair(m, n)); + if (i != map.end()) + return i->second.get(); + + return map.emplace( + make_pair(m, n), + make_unique(m, n, _modifier) + ).first->second.get(); +} + +TupleType const* TypeProvider::tuple(vector members) +{ + if (members.empty()) + return &m_emptyTuple; + + return createAndGet(move(members)); +} + +ReferenceType const* TypeProvider::withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer) +{ + if (_type->location() == _location && _type->isPointer() == _isPointer) + return _type; + + instance().m_generalTypes.emplace_back(_type->copyForLocation(_location, _isPointer)); + return static_cast(instance().m_generalTypes.back().get()); +} + +FunctionType const* TypeProvider::function(FunctionDefinition const& _function, bool _isInternal) +{ + return createAndGet(_function, _isInternal); +} + +FunctionType const* TypeProvider::function(VariableDeclaration const& _varDecl) +{ + return createAndGet(_varDecl); +} + +FunctionType const* TypeProvider::function(EventDefinition const& _def) +{ + return createAndGet(_def); +} + +FunctionType const* TypeProvider::function(FunctionTypeName const& _typeName) +{ + return createAndGet(_typeName); +} + +FunctionType const* TypeProvider::function( + strings const& _parameterTypes, + strings const& _returnParameterTypes, + FunctionType::Kind _kind, + bool _arbitraryParameters, + StateMutability _stateMutability +) +{ + return createAndGet( + _parameterTypes, _returnParameterTypes, + _kind, _arbitraryParameters, _stateMutability + ); +} + +FunctionType const* TypeProvider::function( + TypePointers const& _parameterTypes, + TypePointers const& _returnParameterTypes, + strings _parameterNames, + strings _returnParameterNames, + FunctionType::Kind _kind, + bool _arbitraryParameters, + StateMutability _stateMutability, + Declaration const* _declaration, + bool _gasSet, + bool _valueSet, + bool _bound +) +{ + return createAndGet( + _parameterTypes, + _returnParameterTypes, + _parameterNames, + _returnParameterNames, + _kind, + _arbitraryParameters, + _stateMutability, + _declaration, + _gasSet, + _valueSet, + _bound + ); +} + +RationalNumberType const* TypeProvider::rationalNumber(rational const& _value, Type const* _compatibleBytesType) +{ + return createAndGet(_value, _compatibleBytesType); +} + +ArrayType const* TypeProvider::array(DataLocation _location, bool _isString) +{ + if (_isString) + { + if (_location == DataLocation::Storage) + return stringStorage(); + if (_location == DataLocation::Memory) + return stringMemory(); + } + else + { + if (_location == DataLocation::Storage) + return bytesStorage(); + if (_location == DataLocation::Memory) + return bytesMemory(); + } + return createAndGet(_location, _isString); +} + +ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType) +{ + return createAndGet(_location, _baseType); +} + +ArrayType const* TypeProvider::array(DataLocation _location, Type const* _baseType, u256 const& _length) +{ + return createAndGet(_location, _baseType, _length); +} + +ContractType const* TypeProvider::contract(ContractDefinition const& _contractDef, bool _isSuper) +{ + return createAndGet(_contractDef, _isSuper); +} + +EnumType const* TypeProvider::enumType(EnumDefinition const& _enumDef) +{ + return createAndGet(_enumDef); +} + +ModuleType const* TypeProvider::module(SourceUnit const& _source) +{ + return createAndGet(_source); +} + +TypeType const* TypeProvider::typeType(Type const* _actualType) +{ + return createAndGet(_actualType); +} + +StructType const* TypeProvider::structType(StructDefinition const& _struct, DataLocation _location) +{ + return createAndGet(_struct, _location); +} + +ModifierType const* TypeProvider::modifier(ModifierDefinition const& _def) +{ + return createAndGet(_def); +} + +MagicType const* TypeProvider::magic(MagicType::Kind _kind) +{ + solAssert(_kind != MagicType::Kind::MetaType, "MetaType is handled separately"); + return m_magics.at(static_cast(_kind)).get(); +} + +MagicType const* TypeProvider::meta(Type const* _type) +{ + solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now."); + return createAndGet(_type); +} + +MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valueType) +{ + return createAndGet(_keyType, _valueType); +} diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h new file mode 100644 index 000000000..4b18f52e6 --- /dev/null +++ b/libsolidity/ast/TypeProvider.h @@ -0,0 +1,224 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +/** + * API for accessing the Solidity Type System. + * + * This is the Solidity Compiler's type provider. Use it to request for types. The caller does + * not own the types. + * + * It is not recommended to explicitly instantiate types unless you really know what and why + * you are doing it. + */ +class TypeProvider +{ +public: + TypeProvider() = default; + TypeProvider(TypeProvider&&) = default; + TypeProvider(TypeProvider const&) = delete; + TypeProvider& operator=(TypeProvider&&) = default; + TypeProvider& operator=(TypeProvider const&) = delete; + ~TypeProvider() = default; + + /// Resets state of this TypeProvider to initial state, wiping all mutable types. + /// This invalidates all dangling pointers to types provided by this TypeProvider. + static void reset(); + + /// @name Factory functions + /// Factory functions that convert an AST @ref TypeName to a Type. + static Type const* fromElementaryTypeName(ElementaryTypeNameToken const& _type); + + /// Converts a given elementary type name with optional data location + /// suffix " storage", " calldata" or " memory" to a type pointer. If suffix not given, defaults to " storage". + static TypePointer fromElementaryTypeName(std::string const& _name); + + /// @returns boolean type. + static BoolType const* boolean() noexcept { return &m_boolean; } + + static FixedBytesType const* byte() { return fixedBytes(1); } + static FixedBytesType const* fixedBytes(unsigned m) { return m_bytesM.at(m - 1).get(); } + + static ArrayType const* bytesStorage(); + static ArrayType const* bytesMemory(); + static ArrayType const* stringStorage(); + static ArrayType const* stringMemory(); + + /// Constructor for a byte array ("bytes") and string. + static ArrayType const* array(DataLocation _location, bool _isString = false); + + /// Constructor for a dynamically sized array type ("type[]") + static ArrayType const* array(DataLocation _location, Type const* _baseType); + + /// Constructor for a fixed-size array type ("type[20]") + static ArrayType const* array(DataLocation _location, Type const* _baseType, u256 const& _length); + + static AddressType const* payableAddress() noexcept { return &m_payableAddress; } + static AddressType const* address() noexcept { return &m_address; } + + static IntegerType const* integer(unsigned _bits, IntegerType::Modifier _modifier) + { + solAssert((_bits % 8) == 0, ""); + if (_modifier == IntegerType::Modifier::Unsigned) + return m_uintM.at(_bits / 8 - 1).get(); + else + return m_intM.at(_bits / 8 - 1).get(); + } + static IntegerType const* uint(unsigned _bits) { return integer(_bits, IntegerType::Modifier::Unsigned); } + + static IntegerType const* uint256() { return uint(256); } + + static FixedPointType const* fixedPoint(unsigned m, unsigned n, FixedPointType::Modifier _modifier); + + static StringLiteralType const* stringLiteral(std::string const& literal); + + /// @param members the member types the tuple type must contain. This is passed by value on purspose. + /// @returns a tuple type with the given members. + static TupleType const* tuple(std::vector members); + + static TupleType const* emptyTuple() noexcept { return &m_emptyTuple; } + + static ReferenceType const* withLocation(ReferenceType const* _type, DataLocation _location, bool _isPointer); + + /// @returns a copy of @a _type having the same location as this (and is not a pointer type) + /// if _type is a reference type and an unmodified copy of _type otherwise. + /// This function is mostly useful to modify inner types appropriately. + static Type const* withLocationIfReference(DataLocation _location, Type const* _type) + { + if (auto refType = dynamic_cast(_type)) + return withLocation(refType, _location, false); + + return _type; + } + + /// @returns the internally-facing or externally-facing type of a function. + static FunctionType const* function(FunctionDefinition const& _function, bool _isInternal = true); + + /// @returns the accessor function type of a state variable. + static FunctionType const* function(VariableDeclaration const& _varDecl); + + /// @returns the function type of an event. + static FunctionType const* function(EventDefinition const& _event); + + /// @returns the type of a function type name. + static FunctionType const* function(FunctionTypeName const& _typeName); + + /// @returns the function type to be used for a plain type (not derived from a declaration). + static FunctionType const* function( + strings const& _parameterTypes, + strings const& _returnParameterTypes, + FunctionType::Kind _kind = FunctionType::Kind::Internal, + bool _arbitraryParameters = false, + StateMutability _stateMutability = StateMutability::NonPayable + ); + + /// @returns a highly customized FunctionType, use with care. + static FunctionType const* function( + TypePointers const& _parameterTypes, + TypePointers const& _returnParameterTypes, + strings _parameterNames = strings{}, + strings _returnParameterNames = strings{}, + FunctionType::Kind _kind = FunctionType::Kind::Internal, + bool _arbitraryParameters = false, + StateMutability _stateMutability = StateMutability::NonPayable, + Declaration const* _declaration = nullptr, + bool _gasSet = false, + bool _valueSet = false, + bool _bound = false + ); + + /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does + /// not fit any type. + static TypePointer forLiteral(Literal const& _literal); + static RationalNumberType const* rationalNumber(Literal const& _literal); + + static RationalNumberType const* rationalNumber( + rational const& _value, + Type const* _compatibleBytesType = nullptr + ); + + static ContractType const* contract(ContractDefinition const& _contract, bool _isSuper = false); + + static InaccessibleDynamicType const* inaccessibleDynamic() noexcept { return &m_inaccessibleDynamic; } + + /// @returns the type of an enum instance for given definition, there is one distinct type per enum definition. + static EnumType const* enumType(EnumDefinition const& _enum); + + /// @returns special type for imported modules. These mainly give access to their scope via members. + static ModuleType const* module(SourceUnit const& _source); + + static TypeType const* typeType(Type const* _actualType); + + static StructType const* structType(StructDefinition const& _struct, DataLocation _location); + + static ModifierType const* modifier(ModifierDefinition const& _modifierDef); + + static MagicType const* magic(MagicType::Kind _kind); + + static MagicType const* meta(Type const* _type); + + static MappingType const* mapping(Type const* _keyType, Type const* _valueType); + +private: + /// Global TypeProvider instance. + static TypeProvider& instance() + { + static TypeProvider _provider; + return _provider; + } + + template + static inline T const* createAndGet(Args&& ... _args); + + static BoolType const m_boolean; + static InaccessibleDynamicType const m_inaccessibleDynamic; + + /// These are lazy-initialized because they depend on `byte` being available. + static std::unique_ptr m_bytesStorage; + static std::unique_ptr m_bytesMemory; + static std::unique_ptr m_stringStorage; + static std::unique_ptr m_stringMemory; + + static TupleType const m_emptyTuple; + static AddressType const m_payableAddress; + static AddressType const m_address; + static std::array, 32> const m_intM; + static std::array, 32> const m_uintM; + static std::array, 32> const m_bytesM; + static std::array, 4> const m_magics; ///< MagicType's except MetaType + + std::map, std::unique_ptr> m_ufixedMxN{}; + std::map, std::unique_ptr> m_fixedMxN{}; + std::map> m_stringLiteralTypes{}; + std::vector> m_generalTypes{}; +}; + +} // namespace solidity +} // namespace dev diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 0c0339c1c..9904dec40 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -23,28 +23,30 @@ #include #include +#include -#include +#include #include +#include #include #include -#include +#include +#include #include -#include #include -#include +#include #include #include -#include #include #include -#include +#include #include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; namespace @@ -125,21 +127,47 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2) } /// Checks whether _value fits into IntegerType _type. -bool fitsIntegerType(bigint const& _value, IntegerType const& _type) +BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type) { - return (_type.minValue() <= _value) && (_value <= _type.maxValue()); + if (_value < 0 && !_type.isSigned()) + return BoolResult::err("Cannot implicitly convert signed literal to unsigned type."); + + if (_type.minValue() > _value || _value > _type.maxValue()) + return BoolResult::err("Literal is too large to fit in " + _type.toString(false) + "."); + + return true; } /// Checks whether _value fits into _bits bits when having 1 bit as the sign bit /// if _signed is true. bool fitsIntoBits(bigint const& _value, unsigned _bits, bool _signed) { - return fitsIntegerType(_value, IntegerType( + return fitsIntegerType(_value, *TypeProvider::integer( _bits, _signed ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned )); } +Result transformParametersToExternal(TypePointers const& _parameters, bool _inLibrary) +{ + TypePointers transformed; + + for (auto const& type: _parameters) + { + if (TypePointer ext = type->interfaceType(_inLibrary).get()) + transformed.push_back(ext); + else + return Result::err("Parameter should have external type."); + } + + return transformed; +} + +} + +void Type::clearCache() const +{ + m_members.clear(); } void StorageOffsets::computeOffsets(TypePointers const& _types) @@ -149,7 +177,7 @@ void StorageOffsets::computeOffsets(TypePointers const& _types) map> offsets; for (size_t i = 0; i < _types.size(); ++i) { - TypePointer const& type = _types[i]; + Type const* type = _types[i]; if (!type->canBeStored()) continue; if (byteOffset + type->storageBytes() > 32) @@ -230,7 +258,7 @@ string identifierList(Range const&& _list) return parenthesizeIdentifier(boost::algorithm::join(_list, ",")); } -string richIdentifier(TypePointer const& _type) +string richIdentifier(Type const* _type) { return _type ? _type->richIdentifier() : ""; } @@ -240,12 +268,12 @@ string identifierList(vector const& _list) return identifierList(_list | boost::adaptors::transformed(richIdentifier)); } -string identifierList(TypePointer const& _type) +string identifierList(Type const* _type) { return parenthesizeIdentifier(richIdentifier(_type)); } -string identifierList(TypePointer const& _type1, TypePointer const& _type2) +string identifierList(Type const* _type1, Type const* _type2) { TypePointers list; list.push_back(_type1); @@ -282,126 +310,16 @@ string Type::identifier() const return ret; } -TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type) -{ - solAssert(TokenTraits::isElementaryTypeName(_type.token()), - "Expected an elementary type name but got " + _type.toString() - ); - - Token token = _type.token(); - unsigned m = _type.firstNumber(); - unsigned n = _type.secondNumber(); - - switch (token) - { - case Token::IntM: - return make_shared(m, IntegerType::Modifier::Signed); - case Token::UIntM: - return make_shared(m, IntegerType::Modifier::Unsigned); - case Token::BytesM: - return make_shared(m); - case Token::FixedMxN: - return make_shared(m, n, FixedPointType::Modifier::Signed); - case Token::UFixedMxN: - return make_shared(m, n, FixedPointType::Modifier::Unsigned); - case Token::Int: - return make_shared(256, IntegerType::Modifier::Signed); - case Token::UInt: - return make_shared(256, IntegerType::Modifier::Unsigned); - case Token::SafeUint: - return make_shared(256 , IntegerType::Modifier::SafeUint); - case Token::Fixed: - return make_shared(128, 18, FixedPointType::Modifier::Signed); - case Token::UFixed: - return make_shared(128, 18, FixedPointType::Modifier::Unsigned); - case Token::Byte: - return make_shared(1); - case Token::Address: - return make_shared(StateMutability::NonPayable); - case Token::Bool: - return make_shared(); - case Token::Bytes: - return make_shared(DataLocation::Storage); - case Token::String: - return make_shared(DataLocation::Storage, true); - //no types found - default: - solAssert( - false, - "Unable to convert elementary typename " + _type.toString() + " to type." - ); - } -} - -TypePointer Type::fromElementaryTypeName(string const& _name) -{ - vector nameParts; - boost::split(nameParts, _name, boost::is_any_of(" ")); - solAssert(nameParts.size() == 1 || nameParts.size() == 2, "Cannot parse elementary type: " + _name); - Token token; - unsigned short firstNum, secondNum; - tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(nameParts[0]); - auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); - if (auto* ref = dynamic_cast(t.get())) - { - DataLocation location = DataLocation::Storage; - if (nameParts.size() == 2) - { - if (nameParts[1] == "storage") - location = DataLocation::Storage; - else if (nameParts[1] == "calldata") - location = DataLocation::CallData; - else if (nameParts[1] == "memory") - location = DataLocation::Memory; - else - solAssert(false, "Unknown data location: " + nameParts[1]); - } - return ref->copyForLocation(location, true); - } - else if (t->category() == Type::Category::Address) - { - if (nameParts.size() == 2) - { - if (nameParts[1] == "payable") - return make_shared(StateMutability::Payable); - else - solAssert(false, "Invalid state mutability for address type: " + nameParts[1]); - } - return make_shared(StateMutability::NonPayable); - } - else - { - solAssert(nameParts.size() == 1, "Storage location suffix only allowed for reference types"); - return t; - } -} - -TypePointer Type::forLiteral(Literal const& _literal) -{ - switch (_literal.token()) - { - case Token::TrueLiteral: - case Token::FalseLiteral: - return make_shared(); - case Token::Number: - return RationalNumberType::forLiteral(_literal); - case Token::StringLiteral: - return make_shared(_literal); - default: - return TypePointer(); - } -} - -TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) +TypePointer Type::commonType(Type const* _a, Type const* _b) { if (!_a || !_b) - return TypePointer(); + return nullptr; else if (_a->mobileType() && _b->isImplicitlyConvertibleTo(*_a->mobileType())) return _a->mobileType(); else if (_b->mobileType() && _a->isImplicitlyConvertibleTo(*_b->mobileType())) return _b->mobileType(); else - return TypePointer(); + return nullptr; } MemberList const& Type::members(ContractDefinition const* _currentScope) const @@ -416,7 +334,7 @@ MemberList const& Type::members(ContractDefinition const* _currentScope) const return *m_members[_currentScope]; } -TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _packed) const +TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool) const { TypePointer encodingType = mobileType(); if (encodingType) @@ -424,29 +342,29 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p if (encodingType) encodingType = encodingType->encodingType(); // Structs are fine in the following circumstances: - // - ABIv2 without packed encoding or, + // - ABIv2 or, // - storage struct for a library if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage)) return encodingType; TypePointer baseType = encodingType; - while (auto const* arrayType = dynamic_cast(baseType.get())) + while (auto const* arrayType = dynamic_cast(baseType)) baseType = arrayType->baseType(); - if (dynamic_cast(baseType.get())) - if (!_encoderV2 || _packed) - return TypePointer(); + if (dynamic_cast(baseType)) + if (!_encoderV2) + return nullptr; return encodingType; } MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition const& _scope) { // Normalise data location of type. - TypePointer type = ReferenceType::copyForLocationIfReference(DataLocation::Storage, _type.shared_from_this()); + TypePointer type = TypeProvider::withLocationIfReference(DataLocation::Storage, &_type); set seenFunctions; MemberList::MemberMap members; for (ContractDefinition const* contract: _scope.annotation().linearizedBaseContracts) for (UsingForDirective const* ufd: contract->usingForDirectives()) { - if (ufd->typeName() && *type != *ReferenceType::copyForLocationIfReference( + if (ufd->typeName() && *type != *TypeProvider::withLocationIfReference( DataLocation::Storage, ufd->typeName()->annotation().type )) @@ -459,10 +377,11 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function)) continue; seenFunctions.insert(function); - FunctionType funType(*function, false); - if (auto fun = funType.asMemberFunction(true, true)) - if (_type.isImplicitlyConvertibleTo(*fun->selfType())) - members.push_back(MemberList::Member(function->name(), fun, function)); + if (function->parameters().empty()) + continue; + FunctionTypePointer fun = FunctionType(*function, false).asCallableFunction(true, true); + if (_type.isImplicitlyConvertibleTo(*fun->selfType())) + members.emplace_back(function->name(), fun, function); } } return members; @@ -482,7 +401,7 @@ string AddressType::richIdentifier() const return "t_address"; } -bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const +BoolResult AddressType::isImplicitlyConvertibleTo(Type const& _other) const { if (_other.category() != category()) return false; @@ -491,13 +410,13 @@ bool AddressType::isImplicitlyConvertibleTo(Type const& _other) const return other.m_stateMutability <= m_stateMutability; } -bool AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult AddressType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (auto const* contractType = dynamic_cast(&_convertTo)) return (m_stateMutability >= StateMutability::Payable) || !contractType->isPayable(); return isImplicitlyConvertibleTo(_convertTo) || - _convertTo.category() == Category::Integer || - (_convertTo.category() == Category::FixedBytes && 160 == dynamic_cast(_convertTo).numBytes() * 8); + _convertTo.category() == Category::Integer || + (_convertTo.category() == Category::FixedBytes && 160 == dynamic_cast(_convertTo).numBytes() * 8); } string AddressType::toString(bool) const @@ -520,19 +439,18 @@ u256 AddressType::literalValue(Literal const* _literal) const return u256(_literal->valueWithoutUnderscores()); } -TypePointer AddressType::unaryOperatorResult(Token _operator) const +TypeResult AddressType::unaryOperatorResult(Token _operator) const { - return _operator == Token::Delete ? make_shared() : TypePointer(); + return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; } -TypePointer AddressType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult AddressType::binaryOperatorResult(Token _operator, Type const* _other) const { - // Addresses can only be compared. if (!TokenTraits::isCompareOp(_operator)) - return TypePointer(); + return TypeResult::err("Arithmetic operations on addresses are not supported. Convert to integer first before using them."); - return Type::commonType(shared_from_this(), _other); + return Type::commonType(this, _other); } bool AddressType::operator==(Type const& _other) const @@ -546,16 +464,16 @@ bool AddressType::operator==(Type const& _other) const MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) const { MemberList::MemberMap members = { - {"balance", make_shared(256)}, - {"call", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, FunctionType::SpecialModifier::Default, false, StateMutability::Payable)}, - {"callcode", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, FunctionType::SpecialModifier::Default, false, StateMutability::Payable)}, - {"delegatecall", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, FunctionType::SpecialModifier::Default, false)}, - {"staticcall", make_shared(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, FunctionType::SpecialModifier::Default, false, StateMutability::View)} + {"balance", TypeProvider::uint256()}, + {"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, + {"callcode", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)}, + {"delegatecall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareDelegateCall, false, StateMutability::NonPayable)}, + {"staticcall", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)} }; if (m_stateMutability == StateMutability::Payable) { - members.emplace_back(MemberList::Member{"send", make_shared(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)}); - members.emplace_back(MemberList::Member{"transfer", make_shared(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}); + members.emplace_back(MemberList::Member{"send", TypeProvider::function(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send, false, StateMutability::NonPayable)}); + members.emplace_back(MemberList::Member{"transfer", TypeProvider::function(strings{"uint"}, strings(), FunctionType::Kind::Transfer, false, StateMutability::NonPayable)}); } return members; } @@ -595,7 +513,7 @@ string IntegerType::richIdentifier() const return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits()); } -bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() == category()) { @@ -620,7 +538,7 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || _convertTo.category() == Category::Address || @@ -630,20 +548,19 @@ bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const _convertTo.category() == Category::FixedPoint; } -TypePointer IntegerType::unaryOperatorResult(Token _operator) const +TypeResult IntegerType::unaryOperatorResult(Token _operator) const { // "delete" is ok for all integer types if (_operator == Token::Delete) - return make_shared(); - // we allow +, -, ++ and -- - else if (_operator == Token::Add || _operator == Token::Sub || - _operator == Token::Inc || _operator == Token::Dec || - _operator == Token::BitNot) - return shared_from_this(); + return TypeResult{TypeProvider::emptyTuple()}; + // we allow -, ++ and -- + else if (_operator == Token::Sub || _operator == Token::Inc || + _operator == Token::Dec || _operator == Token::BitNot) + return TypeResult{this}; else if (_operator == Token::FactDelete && numBits() == 256 && isSigned() == false) return make_shared(); else - return TypePointer(); + return TypeResult::err(""); } bool IntegerType::operator==(Type const& _other) const @@ -678,41 +595,40 @@ bigint IntegerType::maxValue() const return (bigint(1) << m_bits) - 1; } -TypePointer IntegerType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult IntegerType::binaryOperatorResult(Token _operator, Type const* _other) const { if ( _other->category() != Category::RationalNumber && _other->category() != Category::FixedPoint && _other->category() != category() ) - return TypePointer(); + return nullptr; if (TokenTraits::isShiftOp(_operator)) { // Shifts are not symmetric with respect to the type if (isValidShiftAndAmountType(_operator, *_other)) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } - auto commonType = Type::commonType(shared_from_this(), _other); //might be an integer or fixed point + auto commonType = Type::commonType(this, _other); //might be an integer or fixed point if (!commonType) - return TypePointer(); + return nullptr; // All integer types can be compared if (TokenTraits::isCompareOp(_operator)) return commonType; if (TokenTraits::isBooleanOp(_operator)) - return TypePointer(); - if (auto intType = dynamic_pointer_cast(commonType)) + return nullptr; + if (auto intType = dynamic_cast(commonType)) { - // Signed EXP is not allowed if (Token::Exp == _operator && intType->isSigned()) - return TypePointer(); + return TypeResult::err("Exponentiation is not allowed for signed integer types."); } - else if (auto fixType = dynamic_pointer_cast(commonType)) + else if (dynamic_cast(commonType)) if (Token::Exp == _operator) - return TypePointer(); + return nullptr; return commonType; } @@ -731,12 +647,14 @@ string FixedPointType::richIdentifier() const return "t_" + string(isSigned() ? "" : "u") + "fixed" + to_string(m_totalBits) + "x" + to_string(m_fractionalDigits); } -bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() == category()) { FixedPointType const& convertTo = dynamic_cast(_convertTo); - if (convertTo.numBits() < m_totalBits || convertTo.fractionalDigits() < m_fractionalDigits) + if (convertTo.fractionalDigits() < m_fractionalDigits) + return BoolResult::err("Too many fractional digits."); + if (convertTo.numBits() < m_totalBits) return false; else return convertTo.maxIntegerValue() >= maxIntegerValue() && convertTo.minIntegerValue() <= minIntegerValue(); @@ -744,26 +662,26 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || _convertTo.category() == Category::Integer; } -TypePointer FixedPointType::unaryOperatorResult(Token _operator) const +TypeResult FixedPointType::unaryOperatorResult(Token _operator) const { switch(_operator) { case Token::Delete: // "delete" is ok for all fixed types - return make_shared(); + return TypeResult{TypeProvider::emptyTuple()}; case Token::Add: case Token::Sub: case Token::Inc: case Token::Dec: // for fixed, we allow +, -, ++ and -- - return shared_from_this(); + return this; default: - return TypePointer(); + return nullptr; } } @@ -798,24 +716,24 @@ bigint FixedPointType::minIntegerValue() const return bigint(0); } -TypePointer FixedPointType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedPointType::binaryOperatorResult(Token _operator, Type const* _other) const { - auto commonType = Type::commonType(shared_from_this(), _other); + auto commonType = Type::commonType(this, _other); if (!commonType) - return TypePointer(); + return nullptr; // All fixed types can be compared if (TokenTraits::isCompareOp(_operator)) return commonType; if (TokenTraits::isBitOp(_operator) || TokenTraits::isBooleanOp(_operator) || _operator == Token::Exp) - return TypePointer(); + return nullptr; return commonType; } -std::shared_ptr FixedPointType::asIntegerType() const +IntegerType const* FixedPointType::asIntegerType() const { - return make_shared(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); + return TypeProvider::integer(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned); } tuple RationalNumberType::parseRational(string const& _value) @@ -861,25 +779,6 @@ tuple RationalNumberType::parseRational(string const& _value) } } -TypePointer RationalNumberType::forLiteral(Literal const& _literal) -{ - solAssert(_literal.token() == Token::Number, ""); - tuple validLiteral = isValidLiteral(_literal); - if (get<0>(validLiteral)) - { - TypePointer compatibleBytesType; - if (_literal.isHexNumber()) - { - size_t const digitCount = _literal.valueWithoutUnderscores().length() - 2; - if (digitCount % 2 == 0 && (digitCount / 2) <= 32) - compatibleBytesType = make_shared(digitCount / 2); - } - - return make_shared(get<1>(validLiteral), compatibleBytesType); - } - return TypePointer(); -} - tuple RationalNumberType::isValidLiteral(Literal const& _literal) { rational value; @@ -983,7 +882,7 @@ tuple RationalNumberType::isValidLiteral(Literal const& _literal return make_tuple(true, value); } -bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const { switch (_convertTo.category()) { @@ -1015,7 +914,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const } } -bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const { switch (_convertTo.category()) { @@ -1049,14 +948,14 @@ bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const } } -TypePointer RationalNumberType::unaryOperatorResult(Token _operator) const +TypeResult RationalNumberType::unaryOperatorResult(Token _operator) const { rational value; switch (_operator) { case Token::BitNot: if (isFractional()) - return TypePointer(); + return nullptr; value = ~m_value.numerator(); break; case Token::Add: @@ -1066,24 +965,24 @@ TypePointer RationalNumberType::unaryOperatorResult(Token _operator) const value = -(m_value); break; case Token::After: - return shared_from_this(); + return this; default: - return TypePointer(); + return nullptr; } - return make_shared(value); + return TypeResult{TypeProvider::rationalNumber(value)}; } -TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult RationalNumberType::binaryOperatorResult(Token _operator, Type const* _other) const { if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint) { - auto commonType = Type::commonType(shared_from_this(), _other); + auto commonType = Type::commonType(this, _other); if (!commonType) - return TypePointer(); + return nullptr; return commonType->binaryOperatorResult(_operator, _other); } else if (_other->category() != category()) - return TypePointer(); + return nullptr; RationalNumberType const& other = dynamic_cast(*_other); if (TokenTraits::isCompareOp(_operator)) @@ -1094,7 +993,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe TypePointer thisMobile = mobileType(); TypePointer otherMobile = other.mobileType(); if (!thisMobile || !otherMobile) - return TypePointer(); + return nullptr; return thisMobile->binaryOperatorResult(_operator, otherMobile); } else @@ -1106,17 +1005,17 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe //bit operations will only be enabled for integers and fixed types that resemble integers case Token::BitOr: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() | other.m_value.numerator(); break; case Token::BitXor: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() ^ other.m_value.numerator(); break; case Token::BitAnd: if (fractional) - return TypePointer(); + return nullptr; value = m_value.numerator() & other.m_value.numerator(); break; case Token::Add: @@ -1130,13 +1029,13 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe break; case Token::Div: if (other.m_value == rational(0)) - return TypePointer(); + return nullptr; else value = m_value / other.m_value; break; case Token::Mod: if (other.m_value == rational(0)) - return TypePointer(); + return nullptr; else if (fractional) { rational tempValue = m_value / other.m_value; @@ -1148,7 +1047,7 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe case Token::Exp: { if (other.isFractional()) - return TypePointer(); + return nullptr; solAssert(other.m_value.denominator() == 1, ""); bigint const& exp = other.m_value.numerator(); @@ -1166,13 +1065,12 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe else { if (abs(exp) > numeric_limits::max()) - return TypePointer(); // This will need too much memory to represent. + return nullptr; // This will need too much memory to represent. uint32_t absExp = bigint(abs(exp)).convert_to(); - // Limit size to 4096 bits if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp)) - return TypePointer(); + return TypeResult::err("Precision of rational constants is limited to 4096 bits."); static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint { if (_base == 1) @@ -1197,18 +1095,18 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe case Token::SHL: { if (fractional) - return TypePointer(); + return nullptr; else if (other.m_value < 0) - return TypePointer(); + return nullptr; else if (other.m_value > numeric_limits::max()) - return TypePointer(); + return nullptr; if (m_value.numerator() == 0) value = 0; else { uint32_t exponent = other.m_value.numerator().convert_to(); if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent)) - return TypePointer(); + return nullptr; value = m_value.numerator() * boost::multiprecision::pow(bigint(2), exponent); } break; @@ -1218,11 +1116,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe case Token::SAR: { if (fractional) - return TypePointer(); + return nullptr; else if (other.m_value < 0) - return TypePointer(); + return nullptr; else if (other.m_value > numeric_limits::max()) - return TypePointer(); + return nullptr; if (m_value.numerator() == 0) value = 0; else @@ -1248,14 +1146,14 @@ TypePointer RationalNumberType::binaryOperatorResult(Token _operator, TypePointe break; } default: - return TypePointer(); + return nullptr; } // verify that numerator and denominator fit into 4096 bit after every operation if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096) - return TypePointer(); + return TypeResult::err("Precision of rational constants is limited to 4096 bits."); - return make_shared(value); + return TypeResult{TypeProvider::rationalNumber(value)}; } } @@ -1337,22 +1235,22 @@ TypePointer RationalNumberType::mobileType() const return fixedPointType(); } -shared_ptr RationalNumberType::integerType() const +IntegerType const* RationalNumberType::integerType() const { solAssert(!isFractional(), "integerType() called for fractional number."); bigint value = m_value.numerator(); if (isNegative()) // convert to positive number of same bit requirements value = ((0 - value) - 1) << 1; if (value > u256(-1)) - return shared_ptr(); + return nullptr; else - return make_shared( + return TypeProvider::integer( max(bytesRequired(value), 1u) * 8, isNegative() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned ); } -shared_ptr RationalNumberType::fixedPointType() const +FixedPointType const* RationalNumberType::fixedPointType() const { unsigned fractionalDigits = 0; unsigned emptyDigits = 0; @@ -1373,7 +1271,8 @@ shared_ptr RationalNumberType::fixedPointType() const } if (value > maxValue) - return shared_ptr(); + return nullptr; + // This means we round towards zero for positive and negative values. bigint totalPart = value.numerator(); @@ -1382,11 +1281,13 @@ shared_ptr RationalNumberType::fixedPointType() const // add one bit for sign and decrement because negative numbers can be larger totalPart = (totalPart << 1) - 1; - unsigned totalBits = max(bytesRequired(totalPart), 1u) * 8; - if (totalBits > 256) - return shared_ptr(); + if (v > u256(-1)) + return nullptr; + + unsigned totalBits = max(bytesRequired(v), 1u) * 8; + solAssert(totalBits <= 256, ""); - return make_shared( + return TypeProvider::fixedPoint( totalBits, fractionalDigits, isNegative() ? FixedPointType::Modifier::Signed : FixedPointType::Modifier::Unsigned ); @@ -1397,7 +1298,12 @@ StringLiteralType::StringLiteralType(Literal const& _literal): { } -bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const +StringLiteralType::StringLiteralType(string const& _value): + m_value{_value} +{ +} + +BoolResult StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (auto fixedBytes = dynamic_cast(&_convertTo)) return size_t(fixedBytes->numBytes()) >= m_value.size(); @@ -1417,7 +1323,7 @@ string StringLiteralType::richIdentifier() const return "t_stringliteral_" + toHex(keccak256(m_value).asBytes()); } -bool StringLiteralType::operator==(const Type& _other) const +bool StringLiteralType::operator==(Type const& _other) const { if (_other.category() != category()) return false; @@ -1436,7 +1342,7 @@ std::string StringLiteralType::toString(bool) const TypePointer StringLiteralType::mobileType() const { - return make_shared(DataLocation::Memory, true); + return TypeProvider::stringMemory(); } bool StringLiteralType::isValidUTF8() const @@ -1452,7 +1358,7 @@ FixedBytesType::FixedBytesType(unsigned _bytes): m_bytes(_bytes) ); } -bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -1460,7 +1366,7 @@ bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const return convertTo.m_bytes >= m_bytes; } -bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return (_convertTo.category() == Category::Integer && numBytes() * 8 == dynamic_cast(_convertTo).numBits()) || (_convertTo.category() == Category::Address && numBytes() == 20) || @@ -1468,41 +1374,41 @@ bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const _convertTo.category() == category(); } -TypePointer FixedBytesType::unaryOperatorResult(Token _operator) const +TypeResult FixedBytesType::unaryOperatorResult(Token _operator) const { // "delete" and "~" is okay for FixedBytesType if (_operator == Token::Delete) - return make_shared(); + return TypeResult{TypeProvider::emptyTuple()}; else if (_operator == Token::BitNot) - return shared_from_this(); + return this; - return TypePointer(); + return nullptr; } -TypePointer FixedBytesType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FixedBytesType::binaryOperatorResult(Token _operator, Type const* _other) const { if (TokenTraits::isShiftOp(_operator)) { if (isValidShiftAndAmountType(_operator, *_other)) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } - auto commonType = dynamic_pointer_cast(Type::commonType(shared_from_this(), _other)); + auto commonType = dynamic_cast(Type::commonType(this, _other)); if (!commonType) - return TypePointer(); + return nullptr; // FixedBytes can be compared and have bitwise operators applied to them if (TokenTraits::isCompareOp(_operator) || TokenTraits::isBitOp(_operator)) - return commonType; + return TypeResult(commonType); - return TypePointer(); + return nullptr; } -MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) const +MemberList::MemberMap FixedBytesType::nativeMembers(ContractDefinition const*) const { - return MemberList::MemberMap{MemberList::Member{"length", make_shared(8)}}; + return MemberList::MemberMap{MemberList::Member{"length", TypeProvider::uint(8)}}; } string FixedBytesType::richIdentifier() const @@ -1529,24 +1435,38 @@ u256 BoolType::literalValue(Literal const* _literal) const solAssert(false, "Bool type constructed from non-boolean literal."); } -TypePointer BoolType::unaryOperatorResult(Token _operator) const +TypeResult BoolType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return make_shared(); - return (_operator == Token::Not) ? shared_from_this() : TypePointer(); + return TypeProvider::emptyTuple(); + else if (_operator == Token::Not) + return this; + else + return nullptr; } -TypePointer BoolType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult BoolType::binaryOperatorResult(Token _operator, Type const* _other) const { if (category() != _other->category()) - return TypePointer(); + return nullptr; if (_operator == Token::Equal || _operator == Token::NotEqual || _operator == Token::And || _operator == Token::Or) return _other; else - return TypePointer(); + return nullptr; } -bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const +Type const* ContractType::encodingType() const +{ + if (isSuper()) + return nullptr; + + if (isPayable()) + return TypeProvider::payableAddress(); + else + return TypeProvider::address(); +} + +BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (*this == _convertTo) return true; @@ -1563,7 +1483,7 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; } -bool ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult ContractType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (auto const* addressType = dynamic_cast(&_convertTo)) return isPayable() || (addressType->stateMutability() < StateMutability::Payable); @@ -1576,31 +1496,26 @@ bool ContractType::isPayable() const return fallbackFunction && fallbackFunction->isPayable(); } -TypePointer ContractType::unaryOperatorResult(Token _operator) const +TypeResult ContractType::unaryOperatorResult(Token _operator) const { if (isSuper()) - return TypePointer{}; - return _operator == Token::Delete ? make_shared() : TypePointer(); + return nullptr; + else if (_operator == Token::Delete) + return TypeProvider::emptyTuple(); + else + return nullptr; } -TypePointer ReferenceType::unaryOperatorResult(Token _operator) const +Type const* ReferenceType::withLocation(DataLocation _location, bool _isPointer) const { - if (_operator == Token::Delete) - { - // delete can be used on everything except calldata references or storage pointers - // (storage references are ok) - switch (location()) - { - case DataLocation::CallData: - return TypePointer(); - case DataLocation::Memory: - return make_shared(); - case DataLocation::Storage: - return m_isPointer ? TypePointer() : make_shared(); - default: - solAssert(false, ""); - } - } + return TypeProvider::withLocation(this, _location, _isPointer); +} + +TypeResult ReferenceType::unaryOperatorResult(Token _operator) const +{ + // XXX: Check functionality after merge 0.5.12 + if (_operator != Token::Delete) + return nullptr; else if ( _operator == Token::FactInsert && dynamic_cast(this) && @@ -1612,19 +1527,23 @@ TypePointer ReferenceType::unaryOperatorResult(Token _operator) const dynamic_cast(this) ) return make_shared(); - return TypePointer(); -} - -TypePointer ReferenceType::copyForLocationIfReference(DataLocation _location, TypePointer const& _type) -{ - if (auto type = dynamic_cast(_type.get())) - return type->copyForLocation(_location, false); - return _type; + // delete can be used on everything except calldata references or storage pointers + // (storage references are ok) + switch (location()) + { + case DataLocation::CallData: + return nullptr; + case DataLocation::Memory: + return TypeProvider::emptyTuple(); + case DataLocation::Storage: + return m_isPointer ? nullptr : TypeProvider::emptyTuple(); + } + return nullptr; } -TypePointer ReferenceType::copyForLocationIfReference(TypePointer const& _type) const +TypePointer ReferenceType::copyForLocationIfReference(Type const* _type) const { - return copyForLocationIfReference(m_location, _type); + return TypeProvider::withLocationIfReference(m_location, _type); } string ReferenceType::stringForReferencePart() const @@ -1662,7 +1581,22 @@ string ReferenceType::identifierLocationSuffix() const return id; } -bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const +ArrayType::ArrayType(DataLocation _location, bool _isString): + ReferenceType(_location), + m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), + m_baseType{TypeProvider::byte()} +{ +} + +void ArrayType::clearCache() const +{ + Type::clearCache(); + + m_interfaceType.reset(); + m_interfaceType_library.reset(); +} + +BoolResult ArrayType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -1689,8 +1623,8 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const // require that the base type is the same, not only convertible. // This disallows assignment of nested dynamic arrays from storage to memory for now. if ( - *copyForLocationIfReference(location(), baseType()) != - *copyForLocationIfReference(location(), convertTo.baseType()) + *TypeProvider::withLocationIfReference(location(), baseType()) != + *TypeProvider::withLocationIfReference(location(), convertTo.baseType()) ) return false; if (isDynamicallySized() != convertTo.isDynamicallySized()) @@ -1702,7 +1636,7 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const } } -bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const +BoolResult ArrayType::isExplicitlyConvertibleTo(Type const& _convertTo) const { if (isImplicitlyConvertibleTo(_convertTo)) return true; @@ -1757,24 +1691,37 @@ bool ArrayType::operator==(Type const& _other) const bool ArrayType::validForCalldata() const { - if (auto arrayBaseType = dynamic_cast(baseType().get())) - if (!arrayBaseType->validForCalldata()) - return false; - return unlimitedCalldataEncodedSize(true) <= numeric_limits::max(); + if (auto arrayBaseType = dynamic_cast(baseType())) + if (!arrayBaseType->validForCalldata()) + return false; + return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits::max(); } -bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const +bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const { - if (isDynamicallySized()) - return 32; - bigint size = bigint(length()) * (isByteArray() ? 1 : baseType()->calldataEncodedSize(_padded)); - size = ((size + 31) / 32) * 32; + solAssert(!isDynamicallySized(), ""); + bigint size = bigint(length()) * calldataStride(); + if (_padded) + size = ((size + 31) / 32) * 32; return size; } unsigned ArrayType::calldataEncodedSize(bool _padded) const { - bigint size = unlimitedCalldataEncodedSize(_padded); + solAssert(!isDynamicallyEncoded(), ""); + bigint size = unlimitedStaticCalldataSize(_padded); + solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); + return unsigned(size); +} + +unsigned ArrayType::calldataEncodedTailSize() const +{ + solAssert(isDynamicallyEncoded(), ""); + if (isDynamicallySized()) + // We do not know the dynamic length itself, but at least the uint256 containing the + // length must still be present. + return 32; + bigint size = unlimitedStaticCalldataSize(false); solAssert(size <= numeric_limits::max(), "Array size does not fit unsigned."); return unsigned(size); } @@ -1872,23 +1819,23 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap members; if (!isString()) { - members.push_back({"length", make_shared(256)}); + members.emplace_back("length", TypeProvider::uint256()); if (isDynamicallySized() && location() == DataLocation::Storage) { - members.push_back({"push", make_shared( + members.emplace_back("push", TypeProvider::function( TypePointers{baseType()}, - TypePointers{make_shared(256)}, + TypePointers{TypeProvider::uint256()}, strings{string()}, strings{string()}, isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush - )}); - members.push_back({"pop", make_shared( + )); + members.emplace_back("pop", TypeProvider::function( TypePointers{}, TypePointers{}, - strings{string()}, - strings{string()}, + strings{}, + strings{}, FunctionType::Kind::ArrayPop - )}); + )); } } return members; @@ -1897,62 +1844,65 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const TypePointer ArrayType::encodingType() const { if (location() == DataLocation::Storage) - return make_shared(256); + return TypeProvider::uint256(); else - return this->copyForLocation(DataLocation::Memory, true); + return TypeProvider::withLocation(this, DataLocation::Memory, true); } TypePointer ArrayType::decodingType() const { if (location() == DataLocation::Storage) - return make_shared(256); + return TypeProvider::uint256(); else - return shared_from_this(); + return this; } -TypePointer ArrayType::interfaceType(bool _inLibrary) const +TypeResult ArrayType::interfaceType(bool _inLibrary) const { - // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) - if (_inLibrary && location() == DataLocation::Storage) - return shared_from_this(); + if (_inLibrary && m_interfaceType_library.is_initialized()) + return *m_interfaceType_library; - if (m_arrayKind != ArrayKind::Ordinary) - return this->copyForLocation(DataLocation::Memory, true); - TypePointer baseExt = m_baseType->interfaceType(_inLibrary); - if (!baseExt) - return TypePointer(); + if (!_inLibrary && m_interfaceType.is_initialized()) + return *m_interfaceType; - if (isDynamicallySized()) - return make_shared(DataLocation::Memory, baseExt); - else - return make_shared(DataLocation::Memory, baseExt, m_length); -} + TypeResult result{TypePointer{}}; + TypeResult baseInterfaceType = m_baseType->interfaceType(_inLibrary); -bool ArrayType::canBeUsedExternally(bool _inLibrary) const -{ - // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) - if (_inLibrary && location() == DataLocation::Storage) - return true; + if (!baseInterfaceType.get()) + { + solAssert(!baseInterfaceType.message().empty(), "Expected detailed error message!"); + result = baseInterfaceType; + } + else if (_inLibrary && location() == DataLocation::Storage) + result = this; else if (m_arrayKind != ArrayKind::Ordinary) - return true; - else if (!m_baseType->canBeUsedExternally(_inLibrary)) - return false; + result = TypeProvider::withLocation(this, DataLocation::Memory, true); + else if (isDynamicallySized()) + result = TypeProvider::array(DataLocation::Memory, baseInterfaceType); else - return true; + result = TypeProvider::array(DataLocation::Memory, baseInterfaceType, m_length); + + if (_inLibrary) + m_interfaceType_library = result; + else + m_interfaceType = result; + + return result; } -u256 ArrayType::memorySize() const +u256 ArrayType::memoryDataSize() const { solAssert(!isDynamicallySized(), ""); solAssert(m_location == DataLocation::Memory, ""); + solAssert(!isByteArray(), ""); bigint size = bigint(m_length) * m_baseType->memoryHeadSize(); solAssert(size <= numeric_limits::max(), "Array size does not fit u256."); return u256(size); } -TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const +std::unique_ptr ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const { - auto copy = make_shared(_location); + auto copy = make_unique(_location); copy->m_isPointer = _isPointer; copy->m_arrayKind = m_arrayKind; copy->m_baseType = copy->copyForLocationIfReference(m_baseType); @@ -2000,15 +1950,16 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con for (ContractDefinition const* base: bases | boost::adaptors::sliced(1, bases.size())) for (FunctionDefinition const* function: base->definedFunctions()) { - if (!function->isVisibleInDerivedContracts()) + if (!function->isVisibleInDerivedContracts() || !function->isImplemented()) continue; - auto functionType = make_shared(*function, true); + + auto functionType = TypeProvider::function(*function, true); bool functionWithEqualArgumentsFound = false; for (auto const& member: members) { if (member.name != function->name()) continue; - auto memberType = dynamic_cast(member.type.get()); + auto memberType = dynamic_cast(member.type); solAssert(!!memberType, "Override changes type."); if (!memberType->hasEqualParameterTypes(*functionType)) continue; @@ -2016,26 +1967,22 @@ MemberList::MemberMap ContractType::nativeMembers(ContractDefinition const* _con break; } if (!functionWithEqualArgumentsFound) - members.push_back(MemberList::Member( - function->name(), - functionType, - function - )); + members.emplace_back(function->name(), functionType, function); } } else if (!m_contract.isLibrary()) { for (auto const& it: m_contract.interfaceFunctions()) - members.push_back(MemberList::Member( + members.emplace_back( it.second->declaration().name(), - it.second->asMemberFunction(m_contract.isLibrary()), + it.second->asCallableFunction(m_contract.isLibrary()), &it.second->declaration() - )); + ); } return members; } -shared_ptr const& ContractType::newExpressionType() const +FunctionType const* ContractType::newExpressionType() const { if (!m_constructorType) m_constructorType = FunctionType::newExpressionType(m_contract); @@ -2058,11 +2005,27 @@ vector> ContractType::stateVar vector> variablesAndOffsets; for (size_t index = 0; index < variables.size(); ++index) if (auto const* offset = offsets.offset(index)) - variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second)); + variablesAndOffsets.emplace_back(variables[index], offset->first, offset->second); return variablesAndOffsets; } -bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const +void StructType::clearCache() const +{ + Type::clearCache(); + + m_interfaceType.reset(); + m_interfaceType_library.reset(); +} + +Type const* StructType::encodingType() const +{ + if (location() != DataLocation::Storage) + return this; + + return TypeProvider::uint256(); +} + +BoolResult StructType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -2088,25 +2051,55 @@ bool StructType::operator==(Type const& _other) const return ReferenceType::operator==(other) && other.m_struct == m_struct; } -unsigned StructType::calldataEncodedSize(bool _padded) const + +unsigned StructType::calldataEncodedSize(bool) const { + solAssert(!isDynamicallyEncoded(), ""); + unsigned size = 0; for (auto const& member: members(nullptr)) - if (!member.type->canLiveOutsideStorage()) - return 0; - else - { - unsigned memberSize = member.type->calldataEncodedSize(_padded); - if (memberSize == 0) - return 0; - size += memberSize; - } + { + solAssert(member.type->canLiveOutsideStorage(), ""); + // Struct members are always padded. + size += member.type->calldataEncodedSize(); + } return size; } + +unsigned StructType::calldataEncodedTailSize() const +{ + solAssert(isDynamicallyEncoded(), ""); + + unsigned size = 0; + for (auto const& member: members(nullptr)) + { + solAssert(member.type->canLiveOutsideStorage(), ""); + // Struct members are always padded. + size += member.type->calldataHeadSize(); + } + return size; +} + +unsigned StructType::calldataOffsetOfMember(std::string const& _member) const +{ + unsigned offset = 0; + for (auto const& member: members(nullptr)) + { + solAssert(member.type->canLiveOutsideStorage(), ""); + if (member.name == _member) + return offset; + // Struct members are always padded. + offset += member.type->calldataHeadSize(); + } + solAssert(false, "Struct member not found."); +} + bool StructType::isDynamicallyEncoded() const { - solAssert(!recursive(), ""); + if (recursive()) + return true; + solAssert(interfaceType(false).get(), ""); for (auto t: memoryMemberTypes()) { solAssert(t, "Parameter should have external type."); @@ -2117,7 +2110,7 @@ bool StructType::isDynamicallyEncoded() const return false; } -u256 StructType::memorySize() const +u256 StructType::memoryDataSize() const { u256 size; for (auto const& t: memoryMemberTypes()) @@ -2145,55 +2138,113 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const { TypePointer type = variable->annotation().type; solAssert(type, ""); - // Skip all mapping members if we are not in storage. + // If we are not in storage, skip all members that cannot live outside of storage, + // ex. mappings and array of mappings if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) continue; - members.push_back(MemberList::Member( + members.emplace_back( variable->name(), copyForLocationIfReference(type), - variable.get()) + variable.get() ); } return members; } -TypePointer StructType::interfaceType(bool _inLibrary) const +TypeResult StructType::interfaceType(bool _inLibrary) const { - if (!canBeUsedExternally(_inLibrary)) - return TypePointer(); + if (_inLibrary && m_interfaceType_library.is_initialized()) + return *m_interfaceType_library; - // Has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) - if (_inLibrary && location() == DataLocation::Storage) - return shared_from_this(); - else - return copyForLocation(DataLocation::Memory, true); -} + if (!_inLibrary && m_interfaceType.is_initialized()) + return *m_interfaceType; -bool StructType::canBeUsedExternally(bool _inLibrary) const -{ - if (_inLibrary && location() == DataLocation::Storage) - return true; - else if (recursive()) - return false; - else + TypeResult result{TypePointer{}}; + + m_recursive = false; + + auto visitor = [&]( + StructDefinition const& _struct, + CycleDetector& _cycleDetector, + size_t /*_depth*/ + ) { // Check that all members have interface types. - // We pass "false" to canBeUsedExternally (_inLibrary), because this struct will be - // passed by value and thus the encoding does not differ, but it will disallow - // mappings. - for (auto const& var: m_struct.members()) + // Return an error if at least one struct member does not have a type. + // This might happen, for example, if the type of the member does not exist. + for (ASTPointer const& variable: _struct.members()) { - solAssert(var->annotation().type, ""); - if (!var->annotation().type->canBeUsedExternally(false)) - return false; + // If the struct member does not have a type return false. + // A TypeError is expected in this case. + if (!variable->annotation().type) + { + result = TypeResult::err("Invalid type!"); + return; + } + + Type const* memberType = variable->annotation().type; + + while (dynamic_cast(memberType)) + memberType = dynamic_cast(memberType)->baseType(); + + if (StructType const* innerStruct = dynamic_cast(memberType)) + if ( + innerStruct->m_recursive == true || + _cycleDetector.run(innerStruct->structDefinition()) + ) + { + m_recursive = true; + if (_inLibrary && location() == DataLocation::Storage) + continue; + else + { + result = TypeResult::err("Recursive structs can only be passed as storage pointers to libraries, not as memory objects to contract functions."); + return; + } + } + + auto iType = memberType->interfaceType(_inLibrary); + if (!iType.get()) + { + solAssert(!iType.message().empty(), "Expected detailed error message!"); + result = iType; + return; + } } + }; + + m_recursive = m_recursive.get() || (CycleDetector(visitor).run(structDefinition()) != nullptr); + + std::string const recursiveErrMsg = "Recursive type not allowed for public or external contract functions."; + + if (_inLibrary) + { + if (!result.message().empty()) + m_interfaceType_library = result; + else if (location() == DataLocation::Storage) + m_interfaceType_library = this; + else + m_interfaceType_library = TypeProvider::withLocation(this, DataLocation::Memory, true); + + if (m_recursive.get()) + m_interfaceType = TypeResult::err(recursiveErrMsg); + + return *m_interfaceType_library; } - return true; + + if (m_recursive.get()) + m_interfaceType = TypeResult::err(recursiveErrMsg); + else if (!result.message().empty()) + m_interfaceType = result; + else + m_interfaceType = TypeProvider::withLocation(this, DataLocation::Memory, true); + + return *m_interfaceType; } -TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const +std::unique_ptr StructType::copyForLocation(DataLocation _location, bool _isPointer) const { - auto copy = make_shared(m_struct, _location); + auto copy = make_unique(m_struct, _location); copy->m_isPointer = _isPointer; return copy; } @@ -2209,8 +2260,8 @@ string StructType::signatureInExternalFunction(bool _structsByName) const { solAssert(_t, "Parameter should have external type."); auto t = _t->interfaceType(_structsByName); - solAssert(t, ""); - return t->signatureInExternalFunction(_structsByName); + solAssert(t.get(), ""); + return t.get()->signatureInExternalFunction(_structsByName); }); return "(" + boost::algorithm::join(memberTypeStrings, ",") + ")"; } @@ -2230,13 +2281,13 @@ FunctionTypePointer StructType::constructorType() const if (!member.type->canLiveOutsideStorage()) continue; paramNames.push_back(member.name); - paramTypes.push_back(copyForLocationIfReference(DataLocation::Memory, member.type)); + paramTypes.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, member.type)); } - return make_shared( + return TypeProvider::function( paramTypes, - TypePointers{copyForLocation(DataLocation::Memory, false)}, + TypePointers{TypeProvider::withLocation(this, DataLocation::Memory, false)}, paramNames, - strings(), + strings(1, ""), FunctionType::Kind::Internal ); } @@ -2278,30 +2329,14 @@ set StructType::membersMissingInMemory() const return missing; } -bool StructType::recursive() const +TypePointer EnumType::encodingType() const { - if (!m_recursive.is_initialized()) - { - auto visitor = [&](StructDefinition const& _struct, CycleDetector& _cycleDetector, size_t /*_depth*/) - { - for (ASTPointer const& variable: _struct.members()) - { - Type const* memberType = variable->annotation().type.get(); - while (dynamic_cast(memberType)) - memberType = dynamic_cast(memberType)->baseType().get(); - if (StructType const* innerStruct = dynamic_cast(memberType)) - if (_cycleDetector.run(innerStruct->structDefinition())) - return; - } - }; - m_recursive = (CycleDetector(visitor).run(structDefinition()) != nullptr); - } - return *m_recursive; + return TypeProvider::uint(8 * storageBytes()); } -TypePointer EnumType::unaryOperatorResult(Token _operator) const +TypeResult EnumType::unaryOperatorResult(Token _operator) const { - return _operator == Token::Delete ? make_shared() : TypePointer(); + return _operator == Token::Delete ? TypeProvider::emptyTuple() : nullptr; } string EnumType::richIdentifier() const @@ -2341,7 +2376,7 @@ size_t EnumType::numberOfMembers() const return m_enum.members().size(); }; -bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo == *this || _convertTo.category() == Category::Integer; } @@ -2358,7 +2393,7 @@ unsigned EnumType::memberValue(ASTString const& _member) const solAssert(false, "Requested unknown enum value " + _member); } -bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +BoolResult TupleType::isImplicitlyConvertibleTo(Type const& _other) const { if (auto tupleType = dynamic_cast(&_other)) { @@ -2424,16 +2459,16 @@ TypePointer TupleType::mobileType() const { auto mt = c->mobileType(); if (!mt) - return TypePointer(); + return nullptr; mobiles.push_back(mt); } else - mobiles.push_back(TypePointer()); + mobiles.push_back(nullptr); } - return make_shared(mobiles); + return TypeProvider::tuple(move(mobiles)); } -TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) const +TypePointer TupleType::closestTemporaryType(Type const* _targetType) const { solAssert(!!_targetType, ""); TypePointers const& targetComponents = dynamic_cast(*_targetType).components(); @@ -2447,7 +2482,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons solAssert(tempComponents[i], ""); } } - return make_shared(tempComponents); + return TypeProvider::tuple(move(tempComponents)); } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal, bool _isFreeGas): @@ -2469,6 +2504,16 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal m_returnParameterNames.push_back(var->name()); m_returnParameterTypes.push_back(var->annotation().type); } + + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + + solAssert( + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); } FunctionType::FunctionType(VariableDeclaration const& _varDecl): @@ -2481,36 +2526,36 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): while (true) { - if (auto mappingType = dynamic_cast(returnType.get())) + if (auto mappingType = dynamic_cast(returnType)) { m_parameterTypes.push_back(mappingType->keyType()); - m_parameterNames.push_back(""); + m_parameterNames.emplace_back(""); returnType = mappingType->valueType(); } - else if (auto arrayType = dynamic_cast(returnType.get())) + else if (auto arrayType = dynamic_cast(returnType)) { if (arrayType->isByteArray()) // Return byte arrays as whole. break; returnType = arrayType->baseType(); - m_parameterNames.push_back(""); - m_parameterTypes.push_back(make_shared(256)); + m_parameterNames.emplace_back(""); + m_parameterTypes.push_back(TypeProvider::uint256()); } else break; } - if (auto structType = dynamic_cast(returnType.get())) + if (auto structType = dynamic_cast(returnType)) { for (auto const& member: structType->members(nullptr)) { solAssert(member.type, ""); if (member.type->category() != Category::Mapping) { - if (auto arrayType = dynamic_cast(member.type.get())) + if (auto arrayType = dynamic_cast(member.type)) if (!arrayType->isByteArray()) continue; - m_returnParameterTypes.push_back(ReferenceType::copyForLocationIfReference( + m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( DataLocation::Memory, member.type )); @@ -2520,12 +2565,21 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl): } else { - m_returnParameterTypes.push_back(ReferenceType::copyForLocationIfReference( + m_returnParameterTypes.push_back(TypeProvider::withLocationIfReference( DataLocation::Memory, returnType )); - m_returnParameterNames.push_back(""); + m_returnParameterNames.emplace_back(""); } + + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + solAssert( + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); } FunctionType::FunctionType(EventDefinition const& _event): @@ -2539,10 +2593,21 @@ FunctionType::FunctionType(EventDefinition const& _event): m_parameterNames.push_back(var->name()); m_parameterTypes.push_back(var->annotation().type); } + + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + solAssert( + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); } //XXX: UNKNOWN FunctionType::FunctionType(FunctionTypeName const& _typeName): + m_parameterNames(_typeName.parameterTypes().size(), ""), + m_returnParameterNames(_typeName.returnParameterTypes().size(), ""), m_kind(_typeName.visibility() == VariableDeclaration::Visibility::External ? Kind::External : Kind::Internal), m_specialModifier(SpecialModifier::Default), m_stateMutability(_typeName.stateMutability()) @@ -2554,7 +2619,7 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): solAssert(t->annotation().type, "Type not set for parameter."); if (m_kind == Kind::External) solAssert( - t->annotation().type->canBeUsedExternally(false), + t->annotation().type->interfaceType(false).get(), "Internal type used as parameter for external function." ); m_parameterTypes.push_back(t->annotation().type); @@ -2564,11 +2629,20 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): solAssert(t->annotation().type, "Type not set for return parameter."); if (m_kind == Kind::External) solAssert( - t->annotation().type->canBeUsedExternally(false), + t->annotation().type->interfaceType(false).get(), "Internal type used as return parameter for external function." ); m_returnParameterTypes.push_back(t->annotation().type); } + + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + solAssert( + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); } FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _contract) @@ -2578,7 +2652,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c strings parameterNames; StateMutability stateMutability = StateMutability::NonPayable; - solAssert(_contract.contractKind() != ContractDefinition::ContractKind::Interface, ""); + solAssert(!_contract.isInterface(), ""); if (constructor) { @@ -2591,9 +2665,9 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c stateMutability = StateMutability::Payable; } - return make_shared( + return TypeProvider::function( parameters, - TypePointers{make_shared(_contract)}, + TypePointers{TypeProvider::contract(_contract)}, parameterNames, strings{""}, Kind::Creation, @@ -2624,7 +2698,7 @@ TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const ) for (auto& param: returnParameterTypes) if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage)) - param = make_shared(); + param = TypeProvider::inaccessibleDynamic(); return returnParameterTypes; } @@ -2683,6 +2757,7 @@ string FunctionType::richIdentifier() const case Kind::IsValidator: id += "isvalidator"; break; case Kind::Rand: id += "rand"; break; case Kind::ABIDecode: id += "abidecode"; break; + case Kind::MetaType: id += "metatype"; break; default: solAssert(false, "Unknown function location."); break; } switch (m_specialModifier) @@ -2721,14 +2796,14 @@ bool FunctionType::operator==(Type const& _other) const return true; } -bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (m_kind == Kind::External && _convertTo == AddressType::address()) + if (m_kind == Kind::External && _convertTo == *TypeProvider::address()) return true; return _convertTo.category() == category(); } -bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const +BoolResult FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.category() != category()) return false; @@ -2753,21 +2828,21 @@ bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const return true; } -TypePointer FunctionType::unaryOperatorResult(Token _operator) const +TypeResult FunctionType::unaryOperatorResult(Token _operator) const { if (_operator == Token::Delete) - return make_shared(); - return TypePointer(); + return TypeResult(TypeProvider::emptyTuple()); + return nullptr; } -TypePointer FunctionType::binaryOperatorResult(Token _operator, TypePointer const& _other) const +TypeResult FunctionType::binaryOperatorResult(Token _operator, Type const* _other) const { if (_other->category() != category() || !(_operator == Token::Equal || _operator == Token::NotEqual)) - return TypePointer(); + return nullptr; FunctionType const& other = dynamic_cast(*_other); if (kind() == Kind::Internal && other.kind() == Kind::Internal && sizeOnStack() == 1 && other.sizeOnStack() == 1) - return commonType(shared_from_this(), _other); - return TypePointer(); + return commonType(this, _other); + return nullptr; } string FunctionType::canonicalName() const @@ -2814,6 +2889,14 @@ u256 FunctionType::storageSize() const solAssert(false, "Storage size of non-storable function type requested."); } +bool FunctionType::leftAligned() const +{ + if (m_kind == Kind::External) + return true; + else + solAssert(false, "Alignment property of non-exportable function type requested."); +} + unsigned FunctionType::storageBytes() const { if (m_kind == Kind::External) @@ -2849,6 +2932,8 @@ unsigned FunctionType::sizeOnStack() const case Kind::ArrayPush: case Kind::ArrayPop: case Kind::ByteArrayPush: + case Kind::Transfer: + case Kind::Send: size = 1; break; default: @@ -2868,30 +2953,25 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const { // Note that m_declaration might also be a state variable! solAssert(m_declaration, "Declaration needed to determine interface function type."); - bool isLibraryFunction = dynamic_cast(*m_declaration->scope()).isLibrary(); + bool isLibraryFunction = kind() != Kind::Event && dynamic_cast(*m_declaration->scope()).isLibrary(); - TypePointers paramTypes; - TypePointers retParamTypes; + Result paramTypes = + transformParametersToExternal(m_parameterTypes, isLibraryFunction); + + if (!paramTypes.message().empty()) + return FunctionTypePointer(); + + Result retParamTypes = + transformParametersToExternal(m_returnParameterTypes, isLibraryFunction); + + if (!retParamTypes.message().empty()) + return FunctionTypePointer(); - for (auto type: m_parameterTypes) - { - if (auto ext = type->interfaceType(isLibraryFunction)) - paramTypes.push_back(ext); - else - return FunctionTypePointer(); - } - for (auto type: m_returnParameterTypes) - { - if (auto ext = type->interfaceType(isLibraryFunction)) - retParamTypes.push_back(ext); - else - return FunctionTypePointer(); - } auto variable = dynamic_cast(m_declaration); - if (variable && retParamTypes.empty()) + if (variable && retParamTypes.get().empty()) return FunctionTypePointer(); - return make_shared( + return TypeProvider::function( paramTypes, retParamTypes, m_parameterNames, @@ -2917,47 +2997,44 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con { MemberList::MemberMap members; if (m_kind == Kind::External) - members.push_back(MemberList::Member( - "selector", - make_shared(4) - )); + members.emplace_back("selector", TypeProvider::fixedBytes(4)); if (m_kind != Kind::BareDelegateCall) { if (isPayable()) - members.push_back(MemberList::Member( + members.emplace_back( "value", - make_shared( + TypeProvider::function( parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(false, true)}, - strings(), - strings(), + strings(1, ""), + strings(1, ""), Kind::SetValue, SpecialModifier::Default, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet ) - )); + ); } if (m_kind != Kind::Creation) - members.push_back(MemberList::Member( + members.emplace_back( "gas", - make_shared( + TypeProvider::function( parseElementaryTypeVector({"uint"}), TypePointers{copyAndSetGasOrValue(true, false)}, - strings(), - strings(), + strings(1, ""), + strings(1, ""), Kind::SetGas, SpecialModifier::Default, false, - StateMutability::NonPayable, + StateMutability::Pure, nullptr, m_gasSet, m_valueSet ) - )); + ); return members; } default: @@ -2969,39 +3046,66 @@ TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. if (m_kind == Kind::External) - return shared_from_this(); + return this; else - return TypePointer(); + return nullptr; } -TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const +TypeResult FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_kind == Kind::External) - return shared_from_this(); + return this; else - return TypePointer(); + return TypeResult::err("Internal type is not allowed for public or external functions."); } -bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const +bool FunctionType::canTakeArguments( + FuncCallArguments const& _arguments, + Type const* _selfType +) const { solAssert(!bound() || _selfType, ""); if (bound() && !_selfType->isImplicitlyConvertibleTo(*selfType())) return false; TypePointers paramTypes = parameterTypes(); + std::vector const paramNames = parameterNames(); + if (takesArbitraryParameters()) return true; - else if (_argumentTypes.size() != paramTypes.size()) + else if (_arguments.numArguments() != paramTypes.size()) return false; - else + else if (!_arguments.hasNamedArguments()) return equal( - _argumentTypes.cbegin(), - _argumentTypes.cend(), + _arguments.types.cbegin(), + _arguments.types.cend(), paramTypes.cbegin(), - [](TypePointer const& argumentType, TypePointer const& parameterType) + [](Type const* argumentType, Type const* parameterType) { return argumentType->isImplicitlyConvertibleTo(*parameterType); } ); + else if (paramNames.size() != _arguments.numNames()) + return false; + else + { + solAssert(_arguments.numArguments() == _arguments.numNames(), "Expected equal sized type & name vectors"); + + size_t matchedNames = 0; + + for (auto const& argName: _arguments.names) + for (size_t i = 0; i < paramNames.size(); i++) + if (*argName == paramNames[i]) + { + matchedNames++; + if (!_arguments.types[i]->isImplicitlyConvertibleTo(*paramTypes[i])) + return false; + } + + if (matchedNames == _arguments.numNames()) + return true; + + return false; + } } bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const @@ -3012,7 +3116,7 @@ bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const m_parameterTypes.cbegin(), m_parameterTypes.cend(), _other.m_parameterTypes.cbegin(), - [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; } + [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; } ); } @@ -3024,7 +3128,7 @@ bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), _other.m_returnParameterTypes.cbegin(), - [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; } + [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; } ); } @@ -3080,14 +3184,17 @@ string FunctionType::externalSignature() const solAssert(false, "Invalid function type for requesting external signature."); } - bool const inLibrary = dynamic_cast(*m_declaration->scope()).isLibrary(); - FunctionTypePointer external = interfaceFunctionType(); - solAssert(!!external, "External function type requested."); - auto parameterTypes = external->parameterTypes(); - auto typeStrings = parameterTypes | boost::adaptors::transformed([&](TypePointer _t) -> string + // "inLibrary" is only relevant if this is not an event. + bool const inLibrary = kind() != Kind::Event && dynamic_cast(*m_declaration->scope()).isLibrary(); + + auto extParams = transformParametersToExternal(m_parameterTypes, inLibrary); + + solAssert(extParams.message().empty(), extParams.message()); + + auto typeStrings = extParams.get() | boost::adaptors::transformed([&](TypePointer _t) -> string { - solAssert(_t, "Parameter should have external type."); string typeName = _t->signatureInExternalFunction(inLibrary); + if (inLibrary && _t->dataStoredIn(DataLocation::Storage)) typeName += " storage"; return typeName; @@ -3102,8 +3209,8 @@ u256 FunctionType::externalIdentifier() const bool FunctionType::isPure() const { - // FIXME: replace this with m_stateMutability == StateMutability::Pure once - // the callgraph analyzer is in place + // TODO: replace this with m_stateMutability == StateMutability::Pure once + // the callgraph analyzer is in place return m_kind == Kind::KECCAK256 || m_kind == Kind::ECRecover || @@ -3118,7 +3225,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIEncodeWithSignature || m_kind == Kind::IsValidator || m_kind == Kind::ENI || - m_kind == Kind::ABIDecode; + m_kind == Kind::ABIDecode || + m_kind == Kind::MetaType; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -3126,13 +3234,13 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) TypePointers pointers; pointers.reserve(_types.size()); for (string const& type: _types) - pointers.push_back(Type::fromElementaryTypeName(type)); + pointers.push_back(TypeProvider::fromElementaryTypeName(type)); return pointers; } TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const { - return make_shared( + return TypeProvider::function( m_parameterTypes, m_returnParameterTypes, m_parameterNames, @@ -3148,17 +3256,17 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con ); } -FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) const +FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _bound) const { - if (_bound && m_parameterTypes.empty()) - return FunctionTypePointer(); + if (_bound) + solAssert(!m_parameterTypes.empty(), ""); TypePointers parameterTypes; for (auto const& t: m_parameterTypes) { - auto refType = dynamic_cast(t.get()); + auto refType = dynamic_cast(t); if (refType && refType->location() == DataLocation::CallData) - parameterTypes.push_back(refType->copyForLocation(DataLocation::Memory, true)); + parameterTypes.push_back(TypeProvider::withLocation(refType, DataLocation::Memory, true)); else parameterTypes.push_back(t); } @@ -3173,7 +3281,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) kind = Kind::DelegateCall; } - return make_shared( + return TypeProvider::function( parameterTypes, m_returnParameterTypes, m_parameterNames, @@ -3189,7 +3297,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound) ); } -TypePointer const& FunctionType::selfType() const +Type const* FunctionType::selfType() const { solAssert(bound(), "Function is not bound."); solAssert(m_parameterTypes.size() > 0, "Function has no self type."); @@ -3225,6 +3333,11 @@ bool FunctionType::padArguments() const return true; } +Type const* MappingType::encodingType() const +{ + return TypeProvider::integer(256, IntegerType::Modifier::Unsigned); +} + string MappingType::richIdentifier() const { return "t_mapping" + identifierList(m_keyType, m_valueType); @@ -3248,6 +3361,26 @@ string MappingType::canonicalName() const return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")"; } +TypeResult MappingType::interfaceType(bool _inLibrary) const +{ + solAssert(keyType()->interfaceType(_inLibrary).get(), "Must be an elementary type!"); + + if (_inLibrary) + { + auto iType = valueType()->interfaceType(_inLibrary); + + if (!iType.get()) + { + solAssert(!iType.message().empty(), "Expected detailed error message!"); + return iType; + } + } + else + return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions."); + + return this; +} + string TypeType::richIdentifier() const { return "t_type" + identifierList(actualType()); @@ -3268,7 +3401,7 @@ u256 TypeType::storageSize() const unsigned TypeType::sizeOnStack() const { - if (auto contractType = dynamic_cast(m_actualType.get())) + if (auto contractType = dynamic_cast(m_actualType)) if (contractType->contractDefinition().isLibrary()) return 1; return 0; @@ -3289,37 +3422,37 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current if (contract.isLibrary()) for (FunctionDefinition const* function: contract.definedFunctions()) if (function->isVisibleAsLibraryMember()) - members.push_back(MemberList::Member( + members.emplace_back( function->name(), - FunctionType(*function).asMemberFunction(true), + FunctionType(*function).asCallableFunction(true), function - )); + ); if (isBase) { // We are accessing the type of a base contract, so add all public and protected // members. Note that this does not add inherited functions on purpose. for (Declaration const* decl: contract.inheritableMembers()) - members.push_back(MemberList::Member(decl->name(), decl->type(), decl)); + members.emplace_back(decl->name(), decl->type(), decl); } else { for (auto const& stru: contract.definedStructs()) - members.push_back(MemberList::Member(stru->name(), stru->type(), stru)); + members.emplace_back(stru->name(), stru->type(), stru); for (auto const& enu: contract.definedEnums()) - members.push_back(MemberList::Member(enu->name(), enu->type(), enu)); + members.emplace_back(enu->name(), enu->type(), enu); } } else if (m_actualType->category() == Category::Enum) { EnumDefinition const& enumDef = dynamic_cast(*m_actualType).enumDefinition(); - auto enumType = make_shared(enumDef); + auto enumType = TypeProvider::enumType(enumDef); for (ASTPointer const& enumValue: enumDef.members()) - members.push_back(MemberList::Member(enumValue->name(), enumType)); + members.emplace_back(enumValue->name(), enumType); } return members; } -ModifierType::ModifierType(const ModifierDefinition& _modifier) +ModifierType::ModifierType(ModifierDefinition const& _modifier) { TypePointers params; params.reserve(_modifier.parameters().size()); @@ -3346,10 +3479,14 @@ bool ModifierType::operator==(Type const& _other) const if (m_parameterTypes.size() != other.m_parameterTypes.size()) return false; - auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; + auto typeCompare = [](Type const* _a, Type const* _b) -> bool { return *_a == *_b; }; - if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), - other.m_parameterTypes.cbegin(), typeCompare)) + if (!equal( + m_parameterTypes.cbegin(), + m_parameterTypes.cend(), + other.m_parameterTypes.cbegin(), + typeCompare + )) return false; return true; } @@ -3379,7 +3516,7 @@ MemberList::MemberMap ModuleType::nativeMembers(ContractDefinition const*) const MemberList::MemberMap symbols; for (auto const& symbolName: m_sourceUnit.annotation().exportedSymbols) for (Declaration const* symbol: symbolName.second) - symbols.push_back(MemberList::Member(symbolName.first, symbol->type(), symbol)); + symbols.emplace_back(symbolName.first, symbol->type(), symbol); return symbols; } @@ -3400,6 +3537,9 @@ string MagicType::richIdentifier() const return "t_magic_transaction"; case Kind::ABI: return "t_magic_abi"; + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "t_magic_meta_type_" + m_typeArgument->richIdentifier(); } return ""; } @@ -3418,69 +3558,69 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const { case Kind::Block: return MemberList::MemberMap({ - {"coinbase", make_shared(StateMutability::Payable)}, - {"timestamp", make_shared(256)}, - {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, FunctionType::SpecialModifier::Default, false, StateMutability::View)}, - {"difficulty", make_shared(256)}, - {"number", make_shared(256)}, - {"gaslimit", make_shared(256)} + {"coinbase", TypeProvider::payableAddress()}, + {"timestamp", TypeProvider::uint256()}, + {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)}, + {"difficulty", TypeProvider::uint256()}, + {"number", TypeProvider::uint256()}, + {"gaslimit", TypeProvider::uint256()} }); case Kind::Message: return MemberList::MemberMap({ - {"sender", make_shared(StateMutability::Payable)}, - {"gas", make_shared(256)}, - {"value", make_shared(256)}, - {"data", make_shared(DataLocation::CallData)}, - {"sig", make_shared(4)} + {"sender", TypeProvider::payableAddress()}, + {"gas", TypeProvider::uint256()}, + {"value", TypeProvider::uint256()}, + {"data", TypeProvider::array(DataLocation::CallData)}, + {"sig", TypeProvider::fixedBytes(4)} }); case Kind::Transaction: return MemberList::MemberMap({ - {"origin", make_shared(StateMutability::Payable)}, - {"gasprice", make_shared(256)} + {"origin", TypeProvider::payableAddress()}, + {"gasprice", TypeProvider::uint256()} }); case Kind::ABI: return MemberList::MemberMap({ - {"encode", make_shared( - TypePointers(), - TypePointers{make_shared(DataLocation::Memory)}, - strings{}, + {"encode", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{}, + strings{1, ""}, FunctionType::Kind::ABIEncode, FunctionType::SpecialModifier::Default, true, StateMutability::Pure )}, - {"encodePacked", make_shared( - TypePointers(), - TypePointers{make_shared(DataLocation::Memory)}, - strings{}, + {"encodePacked", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, strings{}, + strings{1, ""}, FunctionType::Kind::ABIEncodePacked, FunctionType::SpecialModifier::Default, true, StateMutability::Pure )}, - {"encodeWithSelector", make_shared( - TypePointers{make_shared(4)}, - TypePointers{make_shared(DataLocation::Memory)}, - strings{}, - strings{}, + {"encodeWithSelector", TypeProvider::function( + TypePointers{TypeProvider::fixedBytes(4)}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, + strings{1, ""}, + strings{1, ""}, FunctionType::Kind::ABIEncodeWithSelector, FunctionType::SpecialModifier::Default, true, StateMutability::Pure )}, - {"encodeWithSignature", make_shared( - TypePointers{make_shared(DataLocation::Memory, true)}, - TypePointers{make_shared(DataLocation::Memory)}, - strings{}, - strings{}, + {"encodeWithSignature", TypeProvider::function( + TypePointers{TypeProvider::array(DataLocation::Memory, true)}, + TypePointers{TypeProvider::array(DataLocation::Memory)}, + strings{1, ""}, + strings{1, ""}, FunctionType::Kind::ABIEncodeWithSignature, FunctionType::SpecialModifier::Default, true, StateMutability::Pure )}, - {"decode", make_shared( + {"decode", TypeProvider::function( TypePointers(), TypePointers(), strings{}, @@ -3491,12 +3631,28 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const StateMutability::Pure )} }); - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + { + solAssert( + m_typeArgument && m_typeArgument->category() == Type::Category::Contract, + "Only contracts supported for now" + ); + ContractDefinition const& contract = dynamic_cast(*m_typeArgument).contractDefinition(); + if (contract.canBeDeployed()) + return MemberList::MemberMap({ + {"creationCode", TypeProvider::array(DataLocation::Memory)}, + {"runtimeCode", TypeProvider::array(DataLocation::Memory)}, + {"name", TypeProvider::stringMemory()}, + }); + else + return {}; } + } + solAssert(false, "Unknown kind of magic."); + return {}; } -string MagicType::toString(bool) const +string MagicType::toString(bool _short) const { switch (m_kind) { @@ -3508,7 +3664,22 @@ string MagicType::toString(bool) const return "tx"; case Kind::ABI: return "abi"; - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "type(" + m_typeArgument->toString(_short) + ")"; } + solAssert(false, "Unknown kind of magic."); + return {}; +} + +TypePointer MagicType::typeArgument() const +{ + solAssert(m_kind == Kind::MetaType, ""); + solAssert(m_typeArgument, ""); + return m_typeArgument; +} + +TypePointer InaccessibleDynamicType::decodingType() const +{ + return TypeProvider::integer(256, IntegerType::Modifier::Unsigned); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 78ee5c1d6..b35c6216b 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -22,34 +22,37 @@ #pragma once -#include -#include #include +#include #include +#include #include #include +#include -#include -#include #include +#include -#include -#include #include +#include #include +#include namespace dev { namespace solidity { +class TypeProvider; class Type; // forward class FunctionType; // forward -using TypePointer = std::shared_ptr; -using FunctionTypePointer = std::shared_ptr; +using TypePointer = Type const*; +using FunctionTypePointer = FunctionType const*; using TypePointers = std::vector; using rational = boost::rational; +using TypeResult = Result; +using BoolResult = Result; inline rational makeRational(bigint const& _numerator, bigint const& _denominator) { @@ -63,6 +66,7 @@ inline rational makeRational(bigint const& _numerator, bigint const& _denominato enum class DataLocation { Storage, CallData, Memory }; + /** * Helper class to compute storage offsets of members of structs and contracts. */ @@ -90,7 +94,7 @@ class MemberList public: struct Member { - Member(std::string const& _name, TypePointer const& _type, Declaration const* _declaration = nullptr): + Member(std::string const& _name, Type const* _type, Declaration const* _declaration = nullptr): name(_name), type(_type), declaration(_declaration) @@ -98,17 +102,18 @@ class MemberList } std::string name; - TypePointer type; + Type const* type; Declaration const* declaration = nullptr; }; using MemberMap = std::vector; explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + void combine(MemberList const& _other); TypePointer memberType(std::string const& _name) const { - TypePointer type; + TypePointer type = nullptr; for (auto const& it: m_memberTypes) if (it.name == _name) { @@ -144,10 +149,16 @@ static_assert(std::is_nothrow_move_constructible::value, "MemberList /** * Abstract base class that forms the root of the type hierarchy. */ -class Type: private boost::noncopyable, public std::enable_shared_from_this +class Type { public: + Type() = default; + Type(Type const&) = delete; + Type(Type&&) = delete; + Type& operator=(Type const&) = delete; + Type& operator=(Type&&) = delete; virtual ~Type() = default; + enum class Category { Address, Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array, @@ -156,20 +167,8 @@ class Type: private boost::noncopyable, public std::enable_shared_from_thisoperator ==(_other); } - /// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type, - /// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded - /// in calldata. - /// @note: This should actually not be called on types, where isDynamicallyEncoded returns true. + /// @returns number of bytes used by this type when encoded for CALL. Cannot be used for + /// dynamically encoded types. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata + /// (or is dynamically encoded). /// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes. - virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; } + virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; solAssert(false, ""); } + /// Convenience version of @see calldataEncodedSize(bool) + unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } + /// @returns the distance between two elements of this type in a calldata array, tuple or struct. + /// For statically encoded types this is the same as calldataEncodedSize(true). + /// For dynamically encoded types this is the distance between two tail pointers, i.e. 32. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata. + unsigned calldataHeadSize() const { return isDynamicallyEncoded() ? 32 : calldataEncodedSize(true); } + /// @returns the (minimal) size of the calldata tail for this type. Can only be used for + /// dynamically encoded types. For dynamically-sized arrays this is 32 (the size of the length), + /// for statically-sized, but dynamically encoded arrays this is 32*length(), for structs + /// this is the sum of the calldataHeadSize's of its members. + /// Always returns a value greater than zero and throws if the type cannot be encoded in calldata + /// (or is not dynamically encoded). + virtual unsigned calldataEncodedTailSize() const { solAssert(false, ""); } /// @returns the size of this data type in bytes when stored in memory. For memory-reference /// types, this is the size of the memory pointer. virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); } - /// Convenience version of @see calldataEncodedSize(bool) - unsigned calldataEncodedSize() const { return calldataEncodedSize(true); } + /// @returns the size of this data type in bytes when stored in memory. For memory-reference + /// types, this is the size of the actual data area, if it is statically-sized. + virtual u256 memoryDataSize() const { return calldataEncodedSize(); } /// @returns true if the type is a dynamic array virtual bool isDynamicallySized() const { return false; } /// @returns true if the type is dynamically encoded in the ABI @@ -233,6 +247,13 @@ class Type: private boost::noncopyable, public std::enable_shared_from_thisdataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; } @@ -288,7 +309,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this addr(std::make_shared(StateMutability::NonPayable)); return *addr; } - static AddressType& addressPayable() { static std::shared_ptr addr(std::make_shared(StateMutability::Payable)); return *addr; } - - virtual Category category() const override { return Category::Address; } - explicit AddressType(StateMutability _stateMutability); - virtual std::string richIdentifier() const override; - virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + Category category() const override { return Category::Address; } + + std::string richIdentifier() const override; + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; - virtual bool operator==(Type const& _other) const override; + bool operator==(Type const& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; } - virtual unsigned storageBytes() const override { return 160 / 8; } - virtual bool isValueType() const override { return true; } + unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : 160 / 8; } + unsigned storageBytes() const override { return 160 / 8; } + bool leftAligned() const override { return false; } + bool isValueType() const override { return true; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; - virtual std::string toString(bool _short) const override; - virtual std::string canonicalName() const override; + std::string toString(bool _short) const override; + std::string canonicalName() const override; - virtual u256 literalValue(Literal const* _literal) const override; + u256 literalValue(Literal const* _literal) const override; - virtual TypePointer encodingType() const override { return shared_from_this(); } - virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } StateMutability stateMutability(void) const { return m_stateMutability; } @@ -374,26 +393,27 @@ class IntegerType: public Type Unsigned, Signed, SafeUint }; - virtual Category category() const override { return Category::Integer; } - explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned); - virtual std::string richIdentifier() const override; - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + Category category() const override { return Category::Integer; } - virtual bool operator==(Type const& _other) const override; + std::string richIdentifier() const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; - virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } - virtual unsigned storageBytes() const override { return m_bits / 8; } - virtual bool isValueType() const override { return true; } + bool operator==(Type const& _other) const override; - virtual std::string toString(bool _short) const override; + unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } + unsigned storageBytes() const override { return m_bits / 8; } + bool leftAligned() const override { return false; } + bool isValueType() const override { return true; } - virtual TypePointer encodingType() const override { return shared_from_this(); } - virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } + std::string toString(bool _short) const override; + + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } unsigned numBits() const { return m_bits; } bool isSigned() const { return m_modifier == Modifier::Signed; } @@ -403,8 +423,8 @@ class IntegerType: public Type bigint maxValue() const; private: - unsigned m_bits; - Modifier m_modifier; + unsigned const m_bits; + Modifier const m_modifier; }; /** @@ -417,26 +437,27 @@ class FixedPointType: public Type { Unsigned, Signed }; - virtual Category category() const override { return Category::FixedPoint; } explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned); + Category category() const override { return Category::FixedPoint; } - virtual std::string richIdentifier() const override; - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + std::string richIdentifier() const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; - virtual bool operator==(Type const& _other) const override; + bool operator==(Type const& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_totalBits / 8; } - virtual unsigned storageBytes() const override { return m_totalBits / 8; } - virtual bool isValueType() const override { return true; } + unsigned calldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_totalBits / 8; } + unsigned storageBytes() const override { return m_totalBits / 8; } + bool leftAligned() const override { return false; } + bool isValueType() const override { return true; } - virtual std::string toString(bool _short) const override; + std::string toString(bool _short) const override; - virtual TypePointer encodingType() const override { return shared_from_this(); } - virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } /// Number of bits used for this type in total. unsigned numBits() const { return m_totalBits; } @@ -451,7 +472,7 @@ class FixedPointType: public Type bigint minIntegerValue() const; /// @returns the smallest integer type that can hold this type with fractional parts shifted to integers. - std::shared_ptr asIntegerType() const; + IntegerType const* asIntegerType() const; private: unsigned m_totalBits; @@ -467,36 +488,33 @@ class FixedPointType: public Type class RationalNumberType: public Type { public: - - virtual Category category() const override { return Category::RationalNumber; } - - static TypePointer forLiteral(Literal const& _literal); - - explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()): + explicit RationalNumberType(rational const& _value, Type const* _compatibleBytesType = nullptr): m_value(_value), m_compatibleBytesType(_compatibleBytesType) {} - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + Category category() const override { return Category::RationalNumber; } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; - virtual bool canBeStored() const override { return false; } - virtual bool canLiveOutsideStorage() const override { return false; } + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; - virtual std::string toString(bool _short) const override; - virtual u256 literalValue(Literal const* _literal) const override; - virtual TypePointer mobileType() const override; + bool canBeStored() const override { return false; } + bool canLiveOutsideStorage() const override { return false; } + + std::string toString(bool _short) const override; + u256 literalValue(Literal const* _literal) const override; + TypePointer mobileType() const override; /// @returns the smallest integer type that can hold the value or an empty pointer if not possible. - std::shared_ptr integerType() const; + IntegerType const* integerType() const; /// @returns the smallest fixed type that can hold the value or incurs the least precision loss, /// unless the value was truncated, then a suitable type will be chosen to indicate such event. /// If the integer part does not fit, returns an empty pointer. - std::shared_ptr fixedPointType() const; + FixedPointType const* fixedPointType() const; /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } @@ -507,6 +525,9 @@ class RationalNumberType: public Type /// @returns true if the value is zero. bool isZero() const { return m_value == 0; } + /// @returns true if the literal is a valid integer. + static std::tuple isValidLiteral(Literal const& _literal); + private: rational m_value; @@ -514,9 +535,6 @@ class RationalNumberType: public Type /// Empty for all rationals that are not directly parsed from hex literals. TypePointer m_compatibleBytesType; - /// @returns true if the literal is a valid integer. - static std::tuple isValidLiteral(Literal const& _literal); - /// @returns true if the literal is a valid rational number. static std::tuple parseRational(std::string const& _value); @@ -531,25 +549,26 @@ class RationalNumberType: public Type class StringLiteralType: public Type { public: - virtual Category category() const override { return Category::StringLiteral; } - explicit StringLiteralType(Literal const& _literal); + explicit StringLiteralType(std::string const& _value); + + Category category() const override { return Category::StringLiteral; } - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; - virtual bool canBeStored() const override { return false; } - virtual bool canLiveOutsideStorage() const override { return false; } - virtual unsigned sizeOnStack() const override { return 0; } + bool canBeStored() const override { return false; } + bool canLiveOutsideStorage() const override { return false; } + unsigned sizeOnStack() const override { return 0; } - virtual std::string toString(bool) const override; - virtual TypePointer mobileType() const override; + std::string toString(bool) const override; + TypePointer mobileType() const override; bool isValidUTF8() const; @@ -565,25 +584,26 @@ class StringLiteralType: public Type class FixedBytesType: public Type { public: - virtual Category category() const override { return Category::FixedBytes; } - explicit FixedBytesType(unsigned _bytes); - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; + Category category() const override { return Category::FixedBytes; } + + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; - virtual unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } - virtual unsigned storageBytes() const override { return m_bytes; } - virtual bool isValueType() const override { return true; } + unsigned calldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } + unsigned storageBytes() const override { return m_bytes; } + bool leftAligned() const override { return true; } + bool isValueType() const override { return true; } - virtual std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; - virtual TypePointer encodingType() const override { return shared_from_this(); } - virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } + std::string toString(bool) const override { return "bytes" + dev::toString(m_bytes); } + MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } unsigned numBytes() const { return m_bytes; } @@ -597,20 +617,20 @@ class FixedBytesType: public Type class BoolType: public Type { public: - BoolType() {} - virtual Category category() const override { return Category::Bool; } - virtual std::string richIdentifier() const override { return "t_bool"; } - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token _operator, TypePointer const& _other) const override; - - virtual unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } - virtual unsigned storageBytes() const override { return 1; } - virtual bool isValueType() const override { return true; } - - virtual std::string toString(bool) const override { return "bool"; } - virtual u256 literalValue(Literal const* _literal) const override; - virtual TypePointer encodingType() const override { return shared_from_this(); } - virtual TypePointer interfaceType(bool) const override { return shared_from_this(); } + Category category() const override { return Category::Bool; } + std::string richIdentifier() const override { return "t_bool"; } + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token _operator, Type const* _other) const override; + + unsigned calldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } + unsigned storageBytes() const override { return 1; } + bool leftAligned() const override { return false; } + bool isValueType() const override { return true; } + + std::string toString(bool) const override { return "bool"; } + u256 literalValue(Literal const* _literal) const override; + TypePointer encodingType() const override { return this; } + TypeResult interfaceType(bool) const override { return this; } }; /** @@ -619,24 +639,30 @@ class BoolType: public Type */ class ReferenceType: public Type { -public: +protected: explicit ReferenceType(DataLocation _location): m_location(_location) {} + +public: DataLocation location() const { return m_location; } - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } - virtual unsigned memoryHeadSize() const override { return 32; } + unsigned memoryHeadSize() const override { return 32; } + u256 memoryDataSize() const override = 0; + + unsigned calldataEncodedSize(bool) const override = 0; + unsigned calldataEncodedTailSize() const override = 0; /// @returns a copy of this type with location (recursively) changed to @a _location, /// whereas isPointer is only shallowly changed - the deep copy is always a bound reference. - virtual TypePointer copyForLocation(DataLocation _location, bool _isPointer) const = 0; + virtual std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const = 0; - virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } - virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } - virtual bool hasSimpleZeroValueInMemory() const override { return false; } + TypePointer mobileType() const override { return withLocation(m_location, true); } + bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } + bool hasSimpleZeroValueInMemory() const override { return false; } /// Storage references can be pointers or bound references. In general, local variables are of /// pointer type, state variables are bound references. Assignments to pointers or deleting @@ -649,13 +675,10 @@ class ReferenceType: public Type return location() == _other.location() && isPointer() == _other.isPointer(); } - /// @returns a copy of @a _type having the same location as this (and is not a pointer type) - /// if _type is a reference type and an unmodified copy of _type otherwise. - /// This function is mostly useful to modify inner types appropriately. - static TypePointer copyForLocationIfReference(DataLocation _location, TypePointer const& _type); + Type const* withLocation(DataLocation _location, bool _isPointer) const; protected: - TypePointer copyForLocationIfReference(TypePointer const& _type) const; + Type const* copyForLocationIfReference(Type const* _type) const; /// @returns a human-readable description of the reference part of the type. std::string stringForReferencePart() const; /// @returns the suffix computed from the reference part to be used by identifier(); @@ -675,50 +698,44 @@ class ReferenceType: public Type class ArrayType: public ReferenceType { public: - static ArrayType& bytesMemory() { static std::shared_ptr addr(std::make_shared(DataLocation::Memory)); return *addr; } - static ArrayType& stringMemory() { static std::shared_ptr addr(std::make_shared(DataLocation::Memory, true)); return *addr; } - - virtual Category category() const override { return Category::Array; } - /// Constructor for a byte array ("bytes") and string. - explicit ArrayType(DataLocation _location, bool _isString = false): - ReferenceType(_location), - m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), - m_baseType(std::make_shared(1)) - { - } + explicit ArrayType(DataLocation _location, bool _isString = false); + /// Constructor for a dynamically sized array type ("type[]") - ArrayType(DataLocation _location, TypePointer const& _baseType): + ArrayType(DataLocation _location, Type const* _baseType): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)) { } + /// Constructor for a fixed-size array type ("type[20]") - ArrayType(DataLocation _location, TypePointer const& _baseType, u256 const& _length): + ArrayType(DataLocation _location, Type const* _baseType, u256 const& _length): ReferenceType(_location), m_baseType(copyForLocationIfReference(_baseType)), m_hasDynamicLength(false), m_length(_length) {} - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(const Type& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded) const override; - virtual bool isDynamicallySized() const override { return m_hasDynamicLength; } - virtual bool isDynamicallyEncoded() const override; - virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } - virtual unsigned sizeOnStack() const override; - virtual std::string toString(bool _short) const override; - virtual std::string canonicalName() const override; - virtual std::string signatureInExternalFunction(bool _structsByName) const override; - virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - virtual TypePointer encodingType() const override; - virtual TypePointer decodingType() const override; - virtual TypePointer interfaceType(bool _inLibrary) const override; - virtual bool canBeUsedExternally(bool _inLibrary) const override; + Category category() const override { return Category::Array; } + + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool) const override; + unsigned calldataEncodedTailSize() const override; + bool isDynamicallySized() const override { return m_hasDynamicLength; } + bool isDynamicallyEncoded() const override; + u256 storageSize() const override; + bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } + unsigned sizeOnStack() const override; + std::string toString(bool _short) const override; + std::string canonicalName() const override; + std::string signatureInExternalFunction(bool _structsByName) const override; + MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + TypePointer encodingType() const override; + TypePointer decodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override; /// @returns true if this is valid to be stored in calldata bool validForCalldata() const; @@ -727,23 +744,34 @@ class ArrayType: public ReferenceType bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } /// @returns true if this is a string bool isString() const { return m_arrayKind == ArrayKind::String; } - TypePointer const& baseType() const { solAssert(!!m_baseType, ""); return m_baseType;} + Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; } u256 const& length() const { return m_length; } - u256 memorySize() const; + u256 memoryDataSize() const override; - TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; + std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const override; + + /// The offset to advance in calldata to move from one array element to the next. + unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataHeadSize(); } + /// The offset to advance in memory to move from one array element to the next. + unsigned memoryStride() const { return isByteArray() ? 1 : m_baseType->memoryHeadSize(); } + /// The offset to advance in storage to move from one array element to the next. + unsigned storageStride() const { return isByteArray() ? 1 : m_baseType->storageBytes(); } + + void clearCache() const override; private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; - bigint unlimitedCalldataEncodedSize(bool _padded) const; + bigint unlimitedStaticCalldataSize(bool _padded) const; ///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays. ArrayKind m_arrayKind = ArrayKind::Ordinary; - TypePointer m_baseType; + Type const* m_baseType; bool m_hasDynamicLength = true; u256 m_length; + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; }; /** @@ -752,42 +780,42 @@ class ArrayType: public ReferenceType class ContractType: public Type { public: - virtual Category category() const override { return Category::Contract; } explicit ContractType(ContractDefinition const& _contract, bool _super = false): m_contract(_contract), m_super(_super) {} + + Category category() const override { return Category::Contract; } /// Contracts can be implicitly converted only to base contracts. - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; /// Contracts can only be explicitly converted to address types and base contracts. - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded ) const override + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool _padded ) const override { solAssert(!isSuper(), ""); return encodingType()->calldataEncodedSize(_padded); } - virtual unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } - virtual bool canLiveOutsideStorage() const override { return !isSuper(); } - virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; } - virtual bool isValueType() const override { return !isSuper(); } - virtual std::string toString(bool _short) const override; - virtual std::string canonicalName() const override; - - virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - virtual TypePointer encodingType() const override - { - if (isSuper()) - return TypePointer{}; - return std::make_shared(isPayable() ? StateMutability::Payable : StateMutability::NonPayable); - } - virtual TypePointer interfaceType(bool _inLibrary) const override + unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } + bool leftAligned() const override { solAssert(!isSuper(), ""); return false; } + bool canLiveOutsideStorage() const override { return !isSuper(); } + unsigned sizeOnStack() const override { return m_super ? 0 : 1; } + bool isValueType() const override { return !isSuper(); } + std::string toString(bool _short) const override; + std::string canonicalName() const override; + + MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + + Type const* encodingType() const override; + + TypeResult interfaceType(bool _inLibrary) const override { if (isSuper()) - return TypePointer{}; - return _inLibrary ? shared_from_this() : encodingType(); + return nullptr; + return _inLibrary ? this : encodingType(); } + /// See documentation of m_super bool isSuper() const { return m_super; } // @returns true if and only if the contract has a payable fallback function @@ -796,7 +824,7 @@ class ContractType: public Type ContractDefinition const& contractDefinition() const { return m_contract; } /// Returns the function type of the constructor modified to return an object of the contract's type. - FunctionTypePointer const& newExpressionType() const; + FunctionType const* newExpressionType() const; /// @returns a list of all state variables (including inherited) of the contract and their /// offsets in storage. @@ -804,11 +832,10 @@ class ContractType: public Type private: ContractDefinition const& m_contract; - /// If true, it is the "super" type of the current contract, i.e. it contains only inherited - /// members. + /// If true, this is a special "super" type of m_contract containing only members that m_contract inherited bool m_super = false; /// Type of the constructor, @see constructorType. Lazily initialized. - mutable FunctionTypePointer m_constructorType; + mutable FunctionType const* m_constructorType = nullptr; }; /** @@ -817,38 +844,48 @@ class ContractType: public Type class StructType: public ReferenceType { public: - virtual Category category() const override { return Category::Struct; } explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage): ReferenceType(_location), m_struct(_struct) {} - virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded) const override; - virtual bool isDynamicallyEncoded() const override; - u256 memorySize() const; - virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return true; } - virtual std::string toString(bool _short) const override; - - virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - virtual TypePointer encodingType() const override + + Category category() const override { return Category::Struct; } + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool) const override; + unsigned calldataEncodedTailSize() const override; + bool isDynamicallyEncoded() const override; + u256 memoryDataSize() const override; + u256 storageSize() const override; + bool canLiveOutsideStorage() const override { return true; } + std::string toString(bool _short) const override; + + MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + + Type const* encodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override; + + bool recursive() const { - return location() == DataLocation::Storage ? std::make_shared(256) : shared_from_this(); + if (m_recursive.is_initialized()) + return m_recursive.get(); + + interfaceType(false); + + return m_recursive.get(); } - virtual TypePointer interfaceType(bool _inLibrary) const override; - virtual bool canBeUsedExternally(bool _inLibrary) const override; - TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override; + std::unique_ptr copyForLocation(DataLocation _location, bool _isPointer) const override; - virtual std::string canonicalName() const override; - virtual std::string signatureInExternalFunction(bool _structsByName) const override; + std::string canonicalName() const override; + std::string signatureInExternalFunction(bool _structsByName) const override; /// @returns a function that performs the type conversion between a list of struct members /// and a memory struct of this type. - FunctionTypePointer constructorType() const; + FunctionType const* constructorType() const; std::pair const& storageOffsetsOfMember(std::string const& _name) const; u256 memoryOffsetOfMember(std::string const& _name) const; + unsigned calldataOffsetOfMember(std::string const& _name) const; StructDefinition const& structDefinition() const { return m_struct; } @@ -857,13 +894,13 @@ class StructType: public ReferenceType /// @returns the set of all members that are removed in the memory version (typically mappings). std::set membersMissingInMemory() const; - /// @returns true if the same struct is used recursively in one of its members. Only - /// analyses the "memory" representation, i.e. mappings are ignored in all structs. - bool recursive() const; + void clearCache() const override; private: StructDefinition const& m_struct; - /// Cache for the recursive() function. + // Caches for interfaceType(bool) + mutable boost::optional m_interfaceType; + mutable boost::optional m_interfaceType_library; mutable boost::optional m_recursive; }; @@ -873,29 +910,28 @@ class StructType: public ReferenceType class EnumType: public Type { public: - virtual Category category() const override { return Category::Enum; } explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {} - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual unsigned calldataEncodedSize(bool _padded) const override + + Category category() const override { return Category::Enum; } + TypeResult unaryOperatorResult(Token _operator) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + unsigned calldataEncodedSize(bool _padded) const override { return encodingType()->calldataEncodedSize(_padded); } - virtual unsigned storageBytes() const override; - virtual bool canLiveOutsideStorage() const override { return true; } - virtual std::string toString(bool _short) const override; - virtual std::string canonicalName() const override; - virtual bool isValueType() const override { return true; } - - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer encodingType() const override - { - return std::make_shared(8 * int(storageBytes())); - } - virtual TypePointer interfaceType(bool _inLibrary) const override + unsigned storageBytes() const override; + bool leftAligned() const override { return false; } + bool canLiveOutsideStorage() const override { return true; } + std::string toString(bool _short) const override; + std::string canonicalName() const override; + bool isValueType() const override { return true; } + + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypePointer encodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override { - return _inLibrary ? shared_from_this() : encodingType(); + return _inLibrary ? this : encodingType(); } EnumDefinition const& enumDefinition() const { return m_enum; } @@ -914,21 +950,23 @@ class EnumType: public Type class TupleType: public Type { public: - virtual Category category() const override { return Category::Tuple; } - explicit TupleType(std::vector const& _types = std::vector()): m_components(_types) {} - virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual std::string toString(bool) const override; - virtual bool canBeStored() const override { return false; } - virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } - virtual unsigned sizeOnStack() const override; - virtual bool hasSimpleZeroValueInMemory() const override { return false; } - virtual TypePointer mobileType() const override; + explicit TupleType(std::vector _types = {}): m_components(std::move(_types)) {} + + Category category() const override { return Category::Tuple; } + + BoolResult isImplicitlyConvertibleTo(Type const& _other) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + std::string toString(bool) const override; + bool canBeStored() const override { return false; } + u256 storageSize() const override; + bool canLiveOutsideStorage() const override { return false; } + unsigned sizeOnStack() const override; + bool hasSimpleZeroValueInMemory() const override { return false; } + TypePointer mobileType() const override; /// Converts components to their temporary types and performs some wildcard matching. - virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; + TypePointer closestTemporaryType(Type const* _targetType) const override; std::vector const& components() const { return m_components; } @@ -995,10 +1033,9 @@ class FunctionType: public Type IsValidator, ///< isValidator() Rand, ///< rand() ABIDecode, + MetaType ///< type(...) }; - virtual Category category() const override { return Category::Function; } - /// Creates the type of a function. explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true, bool _isFreeGas = false); /// Creates the accessor function type of a state variable. @@ -1018,8 +1055,8 @@ class FunctionType: public Type ): FunctionType( parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), - strings(), - strings(), + strings(_parameterTypes.size(), ""), + strings(_returnParameterTypes.size(), ""), _kind, _specialModifier, _arbitraryParameters, @@ -1032,9 +1069,6 @@ class FunctionType: public Type { } - /// @returns the type of the "new Contract" function, i.e. basically the constructor. - static FunctionTypePointer newExpressionType(ContractDefinition const& _contract); - /// Detailed constructor, use with care. FunctionType( TypePointers const& _parameterTypes, @@ -1063,12 +1097,25 @@ class FunctionType: public Type m_bound(_bound), m_declaration(_declaration) { + solAssert( + m_parameterNames.size() == m_parameterTypes.size(), + "Parameter names list must match parameter types list!" + ); + solAssert( + m_returnParameterNames.size() == m_returnParameterTypes.size(), + "Return parameter names list must match return parameter types list!" + ); solAssert( !m_bound || !m_parameterTypes.empty(), "Attempted construction of bound function without self type" ); } + Category category() const override { return Category::Function; } + + /// @returns the type of the "new Contract" function, i.e. basically the constructor. + static FunctionTypePointer newExpressionType(ContractDefinition const& _contract); + TypePointers parameterTypes() const; std::vector parameterNames() const; TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } @@ -1077,27 +1124,28 @@ class FunctionType: public Type TypePointers returnParameterTypesWithoutDynamicTypes() const; std::vector const& returnParameterNames() const { return m_returnParameterNames; } /// @returns the "self" parameter type for a bound function - TypePointer const& selfType() const; - - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; - virtual TypePointer unaryOperatorResult(Token _operator) const override; - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override; - virtual std::string canonicalName() const override; - virtual std::string toString(bool _short) const override; - virtual unsigned calldataEncodedSize(bool _padded) const override; - virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } - virtual u256 storageSize() const override; - virtual unsigned storageBytes() const override; - virtual bool isValueType() const override { return true; } - virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } - virtual unsigned sizeOnStack() const override; - virtual bool hasSimpleZeroValueInMemory() const override { return false; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; - virtual TypePointer encodingType() const override; - virtual TypePointer interfaceType(bool _inLibrary) const override; + Type const* selfType() const; + + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override; + BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; + TypeResult unaryOperatorResult(Token _operator) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override; + std::string canonicalName() const override; + std::string toString(bool _short) const override; + unsigned calldataEncodedSize(bool _padded) const override; + bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } + u256 storageSize() const override; + bool leftAligned() const override; + unsigned storageBytes() const override; + bool isValueType() const override { return true; } + bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } + unsigned sizeOnStack() const override; + bool hasSimpleZeroValueInMemory() const override { return false; } + MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + TypePointer encodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an /// appropriate external types (i.e. the interfaceType()s) of input/return parameters of @@ -1106,11 +1154,15 @@ class FunctionType: public Type /// external type. FunctionTypePointer interfaceFunctionType() const; - /// @returns true if this function can take the given argument types (possibly + /// @returns true if this function can take the given arguments (possibly /// after implicit conversion). /// @param _selfType if the function is bound, this has to be supplied and is the type of the /// expression the function is called on. - bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const; + bool canTakeArguments( + FuncCallArguments const& _arguments, + Type const* _selfType = nullptr + ) const; + /// @returns true if the types of parameters are equal (does not check return parameter types) bool hasEqualParameterTypes(FunctionType const& _other) const; /// @returns true iff the return types are equal (does not check parameter types) @@ -1174,14 +1226,13 @@ class FunctionType: public Type /// of the parameters to false. TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; - /// @returns a copy of this function type where all return parameters of dynamic size are - /// removed and the location of reference types is changed from CallData to Memory. - /// This is needed if external functions are called on other contracts, as they cannot return - /// dynamic values. - /// Returns empty shared pointer on a failure. Namely, if a bound function has no parameters. + /// @returns a copy of this function type where the location of reference types is changed + /// from CallData to Memory. This is the type that would be used when the function is + /// called, as opposed to the parameter types that are available inside the function body. + /// Also supports variants to be used for library or bound calls. /// @param _inLibrary if true, uses DelegateCall as location. - /// @param _bound if true, the arguments are placed as `arg1.functionName(arg2, ..., argn)`. - FunctionTypePointer asMemberFunction(bool _inLibrary, bool _bound = false) const; + /// @param _bound if true, the function type is set to be bound. + FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const; private: static TypePointers parseElementaryTypeVector(strings const& _types); @@ -1208,30 +1259,25 @@ class FunctionType: public Type class MappingType: public Type { public: - virtual Category category() const override { return Category::Mapping; } - MappingType(TypePointer const& _keyType, TypePointer const& _valueType): + MappingType(Type const* _keyType, Type const* _valueType): m_keyType(_keyType), m_valueType(_valueType) {} - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual std::string toString(bool _short) const override; - virtual std::string canonicalName() const override; - virtual bool canLiveOutsideStorage() const override { return false; } - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual TypePointer encodingType() const override - { - return std::make_shared(256); - } - virtual TypePointer interfaceType(bool _inLibrary) const override - { - return _inLibrary ? shared_from_this() : TypePointer(); - } - virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } + Category category() const override { return Category::Mapping; } + + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + std::string toString(bool _short) const override; + std::string canonicalName() const override; + bool canLiveOutsideStorage() const override { return false; } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + Type const* encodingType() const override; + TypeResult interfaceType(bool _inLibrary) const override; + bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } /// Cannot be stored in memory, but just in case. - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - TypePointer const& keyType() const { return m_keyType; } - TypePointer const& valueType() const { return m_valueType; } + Type const* keyType() const { return m_keyType; } + Type const* valueType() const { return m_valueType; } private: TypePointer m_keyType; @@ -1246,20 +1292,21 @@ class MappingType: public Type class TypeType: public Type { public: - virtual Category category() const override { return Category::TypeType; } - explicit TypeType(TypePointer const& _actualType): m_actualType(_actualType) {} - TypePointer const& actualType() const { return m_actualType; } - - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual bool canBeStored() const override { return false; } - virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } - virtual unsigned sizeOnStack() const override; - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + explicit TypeType(Type const* _actualType): m_actualType(_actualType) {} + + Category category() const override { return Category::TypeType; } + Type const* actualType() const { return m_actualType; } + + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + bool canBeStored() const override { return false; } + u256 storageSize() const override; + bool canLiveOutsideStorage() const override { return false; } + unsigned sizeOnStack() const override; + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } + MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; private: TypePointer m_actualType; @@ -1272,18 +1319,19 @@ class TypeType: public Type class ModifierType: public Type { public: - virtual Category category() const override { return Category::Modifier; } explicit ModifierType(ModifierDefinition const& _modifier); - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual bool canBeStored() const override { return false; } - virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } - virtual unsigned sizeOnStack() const override { return 0; } - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual std::string toString(bool _short) const override; + Category category() const override { return Category::Modifier; } + + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + bool canBeStored() const override { return false; } + u256 storageSize() const override; + bool canLiveOutsideStorage() const override { return false; } + unsigned sizeOnStack() const override { return 0; } + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + std::string toString(bool _short) const override; private: TypePointers m_parameterTypes; @@ -1297,56 +1345,68 @@ class ModifierType: public Type class ModuleType: public Type { public: - virtual Category category() const override { return Category::Module; } - explicit ModuleType(SourceUnit const& _source): m_sourceUnit(_source) {} - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual bool canBeStored() const override { return false; } - virtual bool canLiveOutsideStorage() const override { return true; } - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - virtual unsigned sizeOnStack() const override { return 0; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + Category category() const override { return Category::Module; } - virtual std::string toString(bool _short) const override; + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + bool canBeStored() const override { return false; } + bool canLiveOutsideStorage() const override { return true; } + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + unsigned sizeOnStack() const override { return 0; } + MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + + std::string toString(bool _short) const override; private: SourceUnit const& m_sourceUnit; }; /** - * Special type for magic variables (block, msg, tx), similar to a struct but without any reference - * (it always references a global singleton by name). + * Special type for magic variables (block, msg, tx, type(...)), similar to a struct but without any reference. */ class MagicType: public Type { public: - enum class Kind { Block, Message, Transaction, ABI }; - virtual Category category() const override { return Category::Magic; } + enum class Kind { + Block, ///< "block" + Message, ///< "msg" + Transaction, ///< "tx" + ABI, ///< "abi" + MetaType ///< "type(...)" + }; +public: explicit MagicType(Kind _kind): m_kind(_kind) {} + explicit MagicType(Type const* _metaTypeArg): m_kind{Kind::MetaType}, m_typeArgument{_metaTypeArg} {} - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override + Category category() const override { return Category::Magic; } + + TypeResult binaryOperatorResult(Token, Type const*) const override { - return TypePointer(); + return nullptr; } - virtual std::string richIdentifier() const override; - virtual bool operator==(Type const& _other) const override; - virtual bool canBeStored() const override { return false; } - virtual bool canLiveOutsideStorage() const override { return true; } - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - virtual unsigned sizeOnStack() const override { return 0; } - virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; + std::string richIdentifier() const override; + bool operator==(Type const& _other) const override; + bool canBeStored() const override { return false; } + bool canLiveOutsideStorage() const override { return true; } + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + unsigned sizeOnStack() const override { return 0; } + MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; - virtual std::string toString(bool _short) const override; + std::string toString(bool _short) const override; Kind kind() const { return m_kind; } + TypePointer typeArgument() const; + private: Kind m_kind; + /// Contract type used for contract metadata magic. + TypePointer m_typeArgument; }; /** @@ -1356,20 +1416,20 @@ class MagicType: public Type class InaccessibleDynamicType: public Type { public: - virtual Category category() const override { return Category::InaccessibleDynamic; } - - virtual std::string richIdentifier() const override { return "t_inaccessible"; } - virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; } - virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; } - virtual TypePointer binaryOperatorResult(Token, TypePointer const&) const override { return TypePointer(); } - virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } - virtual bool canBeStored() const override { return false; } - virtual bool canLiveOutsideStorage() const override { return false; } - virtual bool isValueType() const override { return true; } - virtual unsigned sizeOnStack() const override { return 1; } - virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } - virtual std::string toString(bool) const override { return "inaccessible dynamic type"; } - virtual TypePointer decodingType() const override { return std::make_shared(256); } + Category category() const override { return Category::InaccessibleDynamic; } + + std::string richIdentifier() const override { return "t_inaccessible"; } + BoolResult isImplicitlyConvertibleTo(Type const&) const override { return false; } + BoolResult isExplicitlyConvertibleTo(Type const&) const override { return false; } + TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } + unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; } + bool canBeStored() const override { return false; } + bool canLiveOutsideStorage() const override { return false; } + bool isValueType() const override { return true; } + unsigned sizeOnStack() const override { return 1; } + bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } + std::string toString(bool) const override { return "inaccessible dynamic type"; } + TypePointer decodingType() const override; }; class RuleType: public Type diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 55831f87f..03dc40c5b 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -22,10 +22,9 @@ #include -#include #include - #include +#include #include #include @@ -40,18 +39,21 @@ string ABIFunctions::tupleEncoder( bool _encodeAsLibraryTypes ) { + EncodingOptions options; + options.encodeAsLibraryTypes = _encodeAsLibraryTypes; + options.encodeFunctionFromStack = true; + options.padded = true; + options.dynamicInplace = false; + string functionName = string("abi_encode_tuple_"); for (auto const& t: _givenTypes) functionName += t->identifier() + "_"; functionName += "_to_"; for (auto const& t: _targetTypes) functionName += t->identifier() + "_"; - if (_encodeAsLibraryTypes) - functionName += "_library"; + functionName += options.toFunctionNameSuffix(); return createExternallyUsedFunction(functionName, [&]() { - solAssert(!_givenTypes.empty(), ""); - // Note that the values are in reverse due to the difference in calling semantics. Whiskers templ(R"( function (headStart ) -> tail { @@ -62,7 +64,6 @@ string ABIFunctions::tupleEncoder( templ("functionName", functionName); size_t const headSize_ = headSize(_targetTypes); templ("headSize", to_string(headSize_)); - string valueParams; string encodeElements; size_t headPos = 0; size_t stackPos = 0; @@ -71,13 +72,6 @@ string ABIFunctions::tupleEncoder( solAssert(_givenTypes[i], ""); solAssert(_targetTypes[i], ""); size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); - string valueNames = ""; - for (size_t j = 0; j < sizeOnStack; j++) - { - valueNames += "value" + to_string(stackPos) + ", "; - valueParams = ", value" + to_string(stackPos) + valueParams; - stackPos++; - } bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); Whiskers elementTempl( dynamic ? @@ -89,20 +83,86 @@ string ABIFunctions::tupleEncoder( ( add(headStart, )) )") ); - elementTempl("values", valueNames); + string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); + elementTempl("values", values.empty() ? "" : values + ", "); elementTempl("pos", to_string(headPos)); - elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, true)); + elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); encodeElements += elementTempl.render(); - headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); + headPos += _targetTypes[i]->calldataHeadSize(); + stackPos += sizeOnStack; } solAssert(headPos == headSize_, ""); - templ("valueParams", valueParams); + string valueParams = suffixedVariableNameList("value", stackPos, 0); + templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); templ("encodeElements", encodeElements); return templ.render(); }); } +string ABIFunctions::tupleEncoderPacked( + TypePointers const& _givenTypes, + TypePointers const& _targetTypes +) +{ + EncodingOptions options; + options.encodeAsLibraryTypes = false; + options.encodeFunctionFromStack = true; + options.padded = false; + options.dynamicInplace = true; + + string functionName = string("abi_encode_tuple_packed_"); + for (auto const& t: _givenTypes) + functionName += t->identifier() + "_"; + functionName += "_to_"; + for (auto const& t: _targetTypes) + functionName += t->identifier() + "_"; + functionName += options.toFunctionNameSuffix(); + + return createExternallyUsedFunction(functionName, [&]() { + solAssert(!_givenTypes.empty(), ""); + + // Note that the values are in reverse due to the difference in calling semantics. + Whiskers templ(R"( + function (pos ) -> end { + + end := pos + } + )"); + templ("functionName", functionName); + string encodeElements; + size_t stackPos = 0; + for (size_t i = 0; i < _givenTypes.size(); ++i) + { + solAssert(_givenTypes[i], ""); + solAssert(_targetTypes[i], ""); + size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); + bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); + Whiskers elementTempl( + dynamic ? + string(R"( + pos := ( pos) + )") : + string(R"( + ( pos) + pos := add(pos, ) + )") + ); + string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); + elementTempl("values", values.empty() ? "" : values + ", "); + if (!dynamic) + elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false))); + elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); + encodeElements += elementTempl.render(); + stackPos += sizeOnStack; + } + string valueParams = suffixedVariableNameList("value", stackPos, 0); + templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); + templ("encodeElements", encodeElements); + + return templ.render(); + }); +} string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) { string functionName = string("abi_decode_tuple_"); @@ -111,15 +171,13 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) if (_fromMemory) functionName += "_fromMemory"; - solAssert(!_types.empty(), ""); - return createExternallyUsedFunction(functionName, [&]() { TypePointers decodingTypes; for (auto const& t: _types) decodingTypes.emplace_back(t->decodingType()); Whiskers templ(R"( - function (headStart, dataEnd) -> { + function (headStart, dataEnd) { if slt(sub(dataEnd, headStart), ) { revert(0, 0) } } @@ -141,8 +199,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) vector valueNamesLocal; for (size_t j = 0; j < sizeOnStack; j++) { - valueNamesLocal.push_back("value" + to_string(stackPos)); - valueReturnParams.push_back("value" + to_string(stackPos)); + valueNamesLocal.emplace_back("value" + to_string(stackPos)); + valueReturnParams.emplace_back("value" + to_string(stackPos)); stackPos++; } bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); @@ -167,9 +225,10 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) elementTempl("pos", to_string(headPos)); elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); decodeElements += elementTempl.render(); - headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize(); + headPos += decodingTypes[i]->calldataHeadSize(); } templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); + templ("arrow", valueReturnParams.empty() ? "" : "->"); templ("decodeElements", decodeElements); return templ.render(); @@ -178,406 +237,79 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) pair> ABIFunctions::requestedFunctions() { - string result; - for (auto const& f: m_requestedFunctions) - result += f.second; - m_requestedFunctions.clear(); - return make_pair(result, std::move(m_externallyUsedFunctions)); + std::set empty; + swap(empty, m_externallyUsedFunctions); + return make_pair(m_functionCollector->requestedFunctions(), std::move(empty)); } -string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) +string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const { - string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> cleaned { - - } - )"); - templ("functionName", functionName); - switch (_type.category()) - { - case Type::Category::Address: - templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); - break; - case Type::Category::Integer: - { - IntegerType const& type = dynamic_cast(_type); - if (type.numBits() == 256) - templ("body", "cleaned := value"); - else if (type.isSigned()) - templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); - else - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); - break; - } - case Type::Category::RationalNumber: - templ("body", "cleaned := value"); - break; - case Type::Category::Bool: - templ("body", "cleaned := iszero(iszero(value))"); - break; - case Type::Category::FixedPoint: - return cleanupFunction(*dynamic_cast(_type).asIntegerType(), _revertOnFailure); - case Type::Category::Array: - case Type::Category::Struct: - solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); - templ("body", "cleaned := value"); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& type = dynamic_cast(_type); - if (type.numBytes() == 32) - templ("body", "cleaned := value"); - else if (type.numBytes() == 0) - // This is disallowed in the type system. - solAssert(false, ""); - else - { - size_t numBits = type.numBytes() * 8; - u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); - templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); - } - break; - } - case Type::Category::Contract: - { - AddressType addressType(dynamic_cast(_type).isPayable() ? - StateMutability::Payable : - StateMutability::NonPayable - ); - templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); - break; - } - case Type::Category::Enum: - { - size_t members = dynamic_cast(_type).numberOfMembers(); - solAssert(members > 0, "empty enum should have caused a parser error."); - Whiskers w("if iszero(lt(value, )) { } cleaned := value"); - w("members", to_string(members)); - if (_revertOnFailure) - w("failure", "revert(0, 0)"); - else - w("failure", "invalid()"); - templ("body", w.render()); - break; - } - case Type::Category::InaccessibleDynamic: - templ("body", "cleaned := 0"); - break; - default: - solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); - } - - return templ.render(); - }); -} - -string ABIFunctions::conversionFunction(Type const& _from, Type const& _to) -{ - string functionName = - "convert_" + - _from.identifier() + - "_to_" + - _to.identifier(); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (value) -> converted { - - } - )"); - templ("functionName", functionName); - string body; - auto toCategory = _to.category(); - auto fromCategory = _from.category(); - switch (fromCategory) - { - case Type::Category::Address: - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(IntegerType(160), _to)) - .render(); - break; - case Type::Category::Integer: - case Type::Category::RationalNumber: - case Type::Category::Contract: - { - if (toCategory == Type::Category::FixedBytes) - { - solAssert( - fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, - "Invalid conversion to FixedBytesType requested." - ); - FixedBytesType const& toBytesType = dynamic_cast(_to); - body = - Whiskers("converted := ((value))") - ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) - ("clean", cleanupFunction(_from)) - .render(); - } - else if (toCategory == Type::Category::Enum) - { - solAssert(_from.mobileType(), ""); - body = - Whiskers("converted := ((value))") - ("cleanEnum", cleanupFunction(_to, false)) - // "mobileType()" returns integer type for rational - ("cleanInt", cleanupFunction(*_from.mobileType())) - .render(); - } - else if (toCategory == Type::Category::FixedPoint) - { - FixedPointType const& toFixedPointType = dynamic_cast(_to); - if (fromCategory == Type::Category::Integer) - { - body = - Whiskers("converted := ((value))") - ("toFixed", scaleFixedFunction(0, toFixedPointType.fractionalDigits(), dynamic_cast(_from).isSigned())) - ("cleanInt", cleanupFunction(_from, false)) - .render(); - } - else if (fromCategory == Type::Category::RationalNumber) - { - RationalNumberType const& fromRationalType = dynamic_cast(_from); - shared_ptr fromFixedType = fromRationalType.fixedPointType(); - solAssert(fromFixedType, ""); - const int fromFractionalDigits = fromFixedType->fractionalDigits(); - const int toFractionalDigits = toFixedPointType.fractionalDigits(); - body = - Whiskers("converted := (value)") - ("scale", scaleFixedFunction(fromFractionalDigits, toFractionalDigits, fromRationalType.isNegative())) - .render(); - } - else - { - solAssert(false, "Invalid conversion to FixedMxN requested."); - } - } - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - solAssert( - toCategory == Type::Category::Integer || - toCategory == Type::Category::Contract, - ""); - IntegerType const addressType(160); - IntegerType const& to = - toCategory == Type::Category::Integer ? - dynamic_cast(_to) : - addressType; - - // Clean according to the "to" type, except if this is - // a widening conversion. - IntegerType const* cleanupType = &to; - if (fromCategory != Type::Category::RationalNumber) - { - IntegerType const& from = - fromCategory == Type::Category::Integer ? - dynamic_cast(_from) : - addressType; - if (to.numBits() > from.numBits()) - cleanupType = &from; - } - body = - Whiskers("converted := (value)") - ("cleanInt", cleanupFunction(*cleanupType)) - .render(); - } - break; - } - case Type::Category::Bool: - { - solAssert(_from == _to, "Invalid conversion for bool."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(_from)) - .render(); - break; - } - case Type::Category::FixedPoint: - { - FixedPointType const& fromFixedType = dynamic_cast(_from); - if (toCategory == Type::Category::Integer) - { - body = - Whiskers("converted := ((value))") - ("cleanFixed", cleanupFunction(_from, false)) - ("toInt", scaleFixedFunction(fromFixedType.fractionalDigits(), 0, fromFixedType.isSigned())) - .render(); - } - else if (toCategory == Type::Category::FixedPoint) - { - FixedPointType const& toFixedType = dynamic_cast(_to); - body = - Whiskers("converted := ((value))") - ("scale", scaleFixedFunction(fromFixedType.fractionalDigits(), toFixedType.fractionalDigits(), fromFixedType.isSigned())) - ("cleanFixed", cleanupFunction(_from, false)) - .render(); - } - else - { - solAssert(false, "Invalid conversion from FixedMxN requested."); - } - break; - } - case Type::Category::Array: - solUnimplementedAssert(false, "Array conversion not implemented."); - break; - case Type::Category::Struct: - solUnimplementedAssert(false, "Struct conversion not implemented."); - break; - case Type::Category::FixedBytes: - { - FixedBytesType const& from = dynamic_cast(_from); - if (toCategory == Type::Category::Integer) - body = - Whiskers("converted := ((value))") - ("shift", shiftRightFunction(256 - from.numBytes() * 8)) - ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) - .render(); - else if (toCategory == Type::Category::Address) - body = - Whiskers("converted := (value)") - ("convert", conversionFunction(_from, IntegerType(160))) - .render(); - else - { - // clear for conversion to longer bytes - solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(from)) - .render(); - } - break; - } - case Type::Category::Function: - { - solAssert(false, "Conversion should not be called for function types."); - break; - } - case Type::Category::Enum: - { - solAssert(toCategory == Type::Category::Integer || _from == _to, ""); - EnumType const& enumType = dynamic_cast(_from); - body = - Whiskers("converted := (value)") - ("clean", cleanupFunction(enumType)) - .render(); - break; - } - case Type::Category::Tuple: - { - solUnimplementedAssert(false, "Tuple conversion not implemented."); - break; - } - default: - solAssert(false, ""); - } - - solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); - templ("body", body); - return templ.render(); - }); -} - -string ABIFunctions::cleanupCombinedExternalFunctionIdFunction() -{ - string functionName = "cleanup_combined_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr_and_selector) -> cleaned { - cleaned := (addr_and_selector) - } - )") - ("functionName", functionName) - ("clean", cleanupFunction(FixedBytesType(24))) - .render(); - }); -} - -string ABIFunctions::combineExternalFunctionIdFunction() -{ - string functionName = "combine_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr, selector) -> combined { - combined := (or((addr), and(selector, 0xffffffff))) - } - )") - ("functionName", functionName) - ("shl32", shiftLeftFunction(32)) - ("shl64", shiftLeftFunction(64)) - .render(); - }); -} - -string ABIFunctions::splitExternalFunctionIdFunction() -{ - string functionName = "split_external_function_id"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (combined) -> addr, selector { - combined := (combined) - selector := and(combined, 0xffffffff) - addr := (combined) - } - )") - ("functionName", functionName) - ("shr32", shiftRightFunction(32)) - ("shr64", shiftRightFunction(64)) - .render(); - }); + string suffix; + if (!padded) + suffix += "_nonPadded"; + if (dynamicInplace) + suffix += "_inplace"; + if (encodeFunctionFromStack) + suffix += "_fromStack"; + if (encodeAsLibraryTypes) + suffix += "_library"; + return suffix; } string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ) { - TypePointer toInterface = _to.fullEncodingType(_encodeAsLibraryTypes, true, false); + TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented."); Type const& to = *toInterface; if (_from.category() == Type::Category::StringLiteral) - return abiEncodingFunctionStringLiteral(_from, to, _encodeAsLibraryTypes); + return abiEncodingFunctionStringLiteral(_from, to, _options); else if (auto toArray = dynamic_cast(&to)) { solAssert(_from.category() == Type::Category::Array, ""); solAssert(to.dataStoredIn(DataLocation::Memory), ""); ArrayType const& fromArray = dynamic_cast(_from); - if (fromArray.location() == DataLocation::CallData) - return abiEncodingFunctionCalldataArray(fromArray, *toArray, _encodeAsLibraryTypes); - else if (!fromArray.isByteArray() && ( - fromArray.location() == DataLocation::Memory || - fromArray.baseType()->storageBytes() > 16 - )) - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _encodeAsLibraryTypes); - else if (fromArray.location() == DataLocation::Memory) - return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _encodeAsLibraryTypes); - else if (fromArray.location() == DataLocation::Storage) - return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _encodeAsLibraryTypes); - else - solAssert(false, ""); + + switch (fromArray.location()) + { + case DataLocation::CallData: + if ( + fromArray.isByteArray() || + *fromArray.baseType() == *TypeProvider::uint256() || + *fromArray.baseType() == FixedBytesType(32) + ) + return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + case DataLocation::Memory: + if (fromArray.isByteArray()) + return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + case DataLocation::Storage: + if (fromArray.baseType()->storageBytes() <= 16) + return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); + else + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); + default: + solAssert(false, ""); + } } else if (auto const* toStruct = dynamic_cast(&to)) { StructType const* fromStruct = dynamic_cast(&_from); solAssert(fromStruct, ""); - return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes); + return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options); } else if (_from.category() == Type::Category::Function) return abiEncodingFunctionFunctionType( dynamic_cast(_from), to, - _encodeAsLibraryTypes, - _fromStack + _options ); solAssert(_from.sizeOnStack() == 1, ""); @@ -588,7 +320,7 @@ string ABIFunctions::abiEncodingFunction( _from.identifier() + "_to_" + to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { solAssert(!to.isDynamicallyEncoded(), ""); @@ -599,38 +331,93 @@ string ABIFunctions::abiEncodingFunction( )"); templ("functionName", functionName); - if (_from.dataStoredIn(DataLocation::Storage) && to.isValueType()) + if (_from.dataStoredIn(DataLocation::Storage)) { // special case: convert storage reference type to value type - this is only // possible for library calls where we just forward the storage reference - solAssert(_encodeAsLibraryTypes, ""); - solAssert(to == IntegerType(256), ""); + solAssert(_options.encodeAsLibraryTypes, ""); + solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested."); + solAssert(to == *TypeProvider::uint256(), ""); templ("cleanupConvert", "value"); } else { + string cleanupConvert; if (_from == to) - templ("cleanupConvert", cleanupFunction(_from) + "(value)"); + cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; else - templ("cleanupConvert", conversionFunction(_from, to) + "(value)"); + cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; + if (!_options.padded) + cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; + templ("cleanupConvert", cleanupConvert); } return templ.render(); }); } -string ABIFunctions::abiEncodingFunctionCalldataArray( +string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction( + Type const& _givenType, + Type const& _targetType, + ABIFunctions::EncodingOptions const& _options +) +{ + string functionName = + "abi_encodeUpdatedPos_" + + _givenType.identifier() + + "_to_" + + _targetType.identifier() + + _options.toFunctionNameSuffix(); + return createFunction(functionName, [&]() { + string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options)); + string encoder = abiEncodingFunction(_givenType, _targetType, _options); + if (_targetType.isDynamicallyEncoded()) + return Whiskers(R"( + function (, pos) -> updatedPos { + updatedPos := (, pos) + } + )") + ("functionName", functionName) + ("encode", encoder) + ("values", values) + .render(); + else + { + unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded); + solAssert(encodedSize != 0, "Invalid encoded size."); + return Whiskers(R"( + function (, pos) -> updatedPos { + (, pos) + updatedPos := add(pos, ) + } + )") + ("functionName", functionName) + ("encode", encoder) + ("encodedSize", toCompactHexWithPrefix(encodedSize)) + ("values", values) + .render(); + } + }); +} + +string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { - solAssert(_to.isDynamicallySized(), ""); solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); auto const& fromArrayType = dynamic_cast(_from); auto const& toArrayType = dynamic_cast(_to); solAssert(fromArrayType.location() == DataLocation::CallData, ""); + solAssert( + fromArrayType.isByteArray() || + *fromArrayType.baseType() == *TypeProvider::uint256() || + *fromArrayType.baseType() == FixedBytesType(32), + "" + ); + solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); solAssert( *fromArrayType.copyForLocation(DataLocation::Memory, true) == @@ -643,33 +430,63 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { - solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently."); - // TODO if this is not a byte array, we might just copy byte-by-byte anyway, - // because the encoding is position-independent, but we have to check that. - Whiskers templ(R"( - // -> - function (start, length, pos) -> end { - // might update pos - (start, pos, length) - end := add(pos, (length)) - } - )"); - templ("storeLength", _to.isDynamicallySized() ? "mstore(pos, length) pos := add(pos, 0x20)" : ""); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", copyToMemoryFunction(true)); - templ("roundUpFun", roundUpFunction()); - return templ.render(); + bool needsPadding = _options.padded && fromArrayType.isByteArray(); + if (fromArrayType.isDynamicallySized()) + { + Whiskers templ(R"( + // -> + function (start, length, pos) -> end { + pos := (pos, length) + + (start, pos, length) + end := add(pos, ) + } + )"); + templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); + templ("functionName", functionName); + if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1) + templ("scaleLengthByStride", ""); + else + templ("scaleLengthByStride", + Whiskers(R"( + if gt(length, ) { revert(0, 0) } + length := mul(length, ) + )") + ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) + ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) + .render() + ); + templ("readableTypeNameFrom", _from.toString(true)); + templ("readableTypeNameTo", _to.toString(true)); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length"); + return templ.render(); + } + else + { + solAssert(fromArrayType.calldataStride() == 32, ""); + Whiskers templ(R"( + // -> + function (start, pos) { + (start, pos, ) + } + )"); + templ("functionName", functionName); + templ("readableTypeNameFrom", _from.toString(true)); + templ("readableTypeNameTo", _to.toString(true)); + templ("copyFun", m_utils.copyToMemoryFunction(true)); + templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride())); + return templ.render(); + } }); } string ABIFunctions::abiEncodingFunctionSimpleArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -677,32 +494,38 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); - solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.dataStoredIn(DataLocation::Storage), ""); solAssert(!_from.isByteArray(), ""); - solAssert(_from.dataStoredIn(DataLocation::Memory) || _from.baseType()->storageBytes() > 16, ""); + if (_from.dataStoredIn(DataLocation::Storage)) + solAssert(_from.baseType()->storageBytes() > 16, ""); return createFunction(functionName, [&]() { bool dynamic = _to.isDynamicallyEncoded(); bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); - bool inMemory = _from.dataStoredIn(DataLocation::Memory); + bool const usesTail = dynamicBase && !_options.dynamicInplace; + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + subOptions.padded = true; + string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions)); Whiskers templ( - dynamicBase ? + usesTail ? R"( // -> - function (value, pos) { - let length := (value) - // might update pos + function (value, pos) { + + pos := (pos, length) let headStart := pos let tail := add(pos, mul(length, 0x20)) - let srcPtr := (value) + let baseRef := (value) + let srcPtr := baseRef for { let i := 0 } lt(i, length) { i := add(i, 1) } { mstore(pos, sub(tail, headStart)) - tail := (, tail) + let := + tail := (, tail) srcPtr := (srcPtr) pos := add(pos, 0x20) } @@ -712,40 +535,60 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( )" : R"( // -> - function (value, pos) { - let length := (value) - // might update pos - let srcPtr := (value) + function (value, pos) { + + pos := (pos, length) + let baseRef := (value) + let srcPtr := baseRef for { let i := 0 } lt(i, length) { i := add(i, 1) } { - (, pos) + let := + pos := (, pos) srcPtr := (srcPtr) - pos := add(pos, ) } } )" ); templ("functionName", functionName); + templ("elementValues", elementValues); + bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized(); + if (lengthAsArgument) + { + templ("maybeLength", " length,"); + templ("declareLength", ""); + } + else + { + templ("maybeLength", ""); + templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)"); + } templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); - if (_to.isDynamicallySized()) - templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)"); - else - templ("storeLength", ""); - templ("dataAreaFun", arrayDataAreaFunction(_from)); - templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize())); - templ("encodeToMemoryFun", abiEncodingFunction( - *_from.baseType(), - *_to.baseType(), - _encodeAsLibraryTypes, - false - )); - templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); - templ("nextArrayElement", nextArrayElementFunction(_from)); + templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); + templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); + + templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); + switch (_from.location()) + { + case DataLocation::Memory: + templ("arrayElementAccess", "mload(srcPtr)"); + break; + case DataLocation::Storage: + if (_from.baseType()->isValueType()) + templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); + else + templ("arrayElementAccess", "srcPtr"); + break; + case DataLocation::CallData: + templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)"); + break; + default: + solAssert(false, ""); + } + templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); return templ.render(); }); } @@ -753,7 +596,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( string ABIFunctions::abiEncodingFunctionMemoryByteArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -761,7 +604,7 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); @@ -773,15 +616,16 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( Whiskers templ(R"( function (value, pos) -> end { let length := (value) - mstore(pos, length) - (add(value, 0x20), add(pos, 0x20), length) - end := add(add(pos, 0x20), (length)) + pos := (pos, length) + (add(value, 0x20), pos, length) + end := add(pos, ) } )"); templ("functionName", functionName); - templ("lengthFun", arrayLengthFunction(_from)); - templ("copyFun", copyToMemoryFunction(false)); - templ("roundUpFun", roundUpFunction()); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); + templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); + templ("copyFun", m_utils.copyToMemoryFunction(false)); + templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); return templ.render(); }); } @@ -789,7 +633,7 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( string ABIFunctions::abiEncodingFunctionCompactStorageArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -797,7 +641,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); @@ -815,29 +659,31 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( case 0 { // short byte array let length := and(div(slotValue, 2), 0x7f) - mstore(pos, length) - mstore(add(pos, 0x20), and(slotValue, not(0xff))) - ret := add(pos, 0x40) + pos := (pos, length) + mstore(pos, and(slotValue, not(0xff))) + ret := add(pos, ) } case 1 { // long byte array let length := div(slotValue, 2) - mstore(pos, length) - pos := add(pos, 0x20) + pos := (pos, length) let dataPos := (value) let i := 0 for { } lt(i, length) { i := add(i, 0x20) } { mstore(add(pos, i), sload(dataPos)) dataPos := add(dataPos, 1) } - ret := add(pos, i) + ret := add(pos, ) } } )"); templ("functionName", functionName); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); - templ("arrayDataSlot", arrayDataAreaFunction(_from)); + templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); + templ("lengthPaddedShort", _options.padded ? "0x20" : "length"); + templ("lengthPaddedLong", _options.padded ? "i" : "length"); + templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from)); return templ.render(); } else @@ -845,30 +691,47 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( // Multiple items per slot solAssert(_from.baseType()->storageBytes() <= 16, ""); solAssert(!_from.baseType()->isDynamicallyEncoded(), ""); + solAssert(!_to.baseType()->isDynamicallyEncoded(), ""); solAssert(_from.baseType()->isValueType(), ""); bool dynamic = _to.isDynamicallyEncoded(); size_t storageBytes = _from.baseType()->storageBytes(); size_t itemsPerSlot = 32 / storageBytes; - // This always writes full slot contents to memory, which might be - // more than desired, i.e. it writes beyond the end of memory. + solAssert(itemsPerSlot > 0, ""); + // The number of elements we need to handle manually after the loop. + size_t spill = size_t(_from.length() % itemsPerSlot); Whiskers templ( R"( // -> function (value, pos) { let length := (value) - // might update pos + pos := (pos, length) let originalPos := pos let srcPtr := (value) - for { let i := 0 } lt(i, length) { i := add(i, ) } - { + let itemCounter := 0 + if { + // Run the loop over all full slots + for { } lt(add(itemCounter, sub(, 1)), length) + { itemCounter := add(itemCounter, ) } + { + let data := sload(srcPtr) + <#items> + ((data), pos) + pos := add(pos, ) + + srcPtr := add(srcPtr, 1) + } + } + // Handle the last (not necessarily full) slot specially + if { let data := sload(srcPtr) <#items> - ((data), pos) - pos := add(pos, ) + if { + ((data), pos) + pos := add(pos, ) + itemCounter := add(itemCounter, 1) + } - srcPtr := add(srcPtr, 1) } - pos := add(originalPos, mul(length, )) } )" @@ -878,25 +741,41 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", arrayLengthFunction(_from)); - if (_to.isDynamicallySized()) - templ("storeLength", "mstore(pos, length) pos := add(pos, 0x20)"); + templ("lengthFun", m_utils.arrayLengthFunction(_from)); + templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); + templ("dataArea", m_utils.arrayDataAreaFunction(_from)); + // We skip the loop for arrays that fit a single slot. + if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot) + templ("useLoop", "1"); + else + templ("useLoop", "0"); + if (_from.isDynamicallySized() || spill != 0) + templ("useSpill", "1"); else - templ("storeLength", ""); - templ("dataArea", arrayDataAreaFunction(_from)); + templ("useSpill", "0"); templ("itemsPerSlot", to_string(itemsPerSlot)); - string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); - templ("elementEncodedSize", elementEncodedSize); + templ("stride", toCompactHexWithPrefix(_to.calldataStride())); + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + subOptions.padded = true; string encodeToMemoryFun = abiEncodingFunction( *_from.baseType(), *_to.baseType(), - _encodeAsLibraryTypes, - false + subOptions ); templ("encodeToMemoryFun", encodeToMemoryFun); std::vector> items(itemsPerSlot); for (size_t i = 0; i < itemsPerSlot; ++i) - items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8); + { + if (_from.isDynamicallySized()) + items[i]["inRange"] = "lt(itemCounter, length)"; + else if (i < spill) + items[i]["inRange"] = "1"; + else + items[i]["inRange"] = "0"; + items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false); + } templ("items", items); return templ.render(); } @@ -906,7 +785,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( string ABIFunctions::abiEncodingFunctionStruct( StructType const& _from, StructType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -914,13 +793,11 @@ string ABIFunctions::abiEncodingFunctionStruct( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); - solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported."); solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); return createFunction(functionName, [&]() { - bool fromStorage = _from.location() == DataLocation::Storage; bool dynamic = _to.isDynamicallyEncoded(); Whiskers templ(R"( // -> @@ -930,6 +807,8 @@ string ABIFunctions::abiEncodingFunctionStruct( <#members> { // + + let := } @@ -940,9 +819,14 @@ string ABIFunctions::abiEncodingFunctionStruct( templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); templ("return", dynamic ? " -> end " : ""); - templ("assignEnd", dynamic ? "end := tail" : ""); + if (dynamic && _options.dynamicInplace) + templ("assignEnd", "end := pos"); + else if (dynamic && !_options.dynamicInplace) + templ("assignEnd", "end := tail"); + else + templ("assignEnd", ""); // to avoid multiple loads from the same slot for subsequent members - templ("init", fromStorage ? "let slotValue := 0" : ""); + templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : ""); u256 previousSlotOffset(-1); u256 encodingOffset = 0; vector> members; @@ -951,67 +835,95 @@ string ABIFunctions::abiEncodingFunctionStruct( solAssert(member.type, ""); if (!member.type->canLiveOutsideStorage()) continue; - TypePointer memberTypeTo = member.type->fullEncodingType(_encodeAsLibraryTypes, true, false); + TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); auto memberTypeFrom = _from.memberType(member.name); solAssert(memberTypeFrom, ""); bool dynamicMember = memberTypeTo->isDynamicallyEncoded(); if (dynamicMember) solAssert(dynamic, ""); - Whiskers memberTempl(R"( - - let memberValue := - )" + ( - dynamicMember ? - string(R"( - mstore(add(pos, ), sub(tail, pos)) - tail := (memberValue, tail) - )") : - string(R"( - (memberValue, add(pos, )) - )") - ) - ); - if (fromStorage) + + members.push_back({}); + members.back()["preprocess"] = ""; + + switch (_from.location()) { - solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); - u256 storageSlotOffset; - size_t intraSlotOffset; - tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); - if (memberTypeFrom->isValueType()) + case DataLocation::Storage: { - if (storageSlotOffset != previousSlotOffset) + solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); + u256 storageSlotOffset; + size_t intraSlotOffset; + tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); + if (memberTypeFrom->isValueType()) { - memberTempl("preprocess", "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"); - previousSlotOffset = storageSlotOffset; + if (storageSlotOffset != previousSlotOffset) + { + members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; + previousSlotOffset = storageSlotOffset; + } + members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; } else - memberTempl("preprocess", ""); - memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8) + "(slotValue)"); + { + solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); + solAssert(intraSlotOffset == 0, ""); + members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; + } + break; } - else + case DataLocation::Memory: + { + string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); + members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; + break; + } + case DataLocation::CallData: { - solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); - solAssert(intraSlotOffset == 0, ""); - memberTempl("preprocess", ""); - memberTempl("retrieveValue", "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"); + string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name)); + members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))"; + break; } + default: + solAssert(false, ""); } + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + // Like with arrays, struct members are always padded. + subOptions.padded = true; + + string memberValues = suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions)); + members.back()["memberValues"] = memberValues; + + string encode; + if (_options.dynamicInplace) + encode = Whiskers{"pos := (, pos)"} + ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) + ("memberValues", memberValues) + .render(); else { - memberTempl("preprocess", ""); - string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); - memberTempl("retrieveValue", "mload(add(value, " + sourceOffset + "))"); + Whiskers encodeTempl( + dynamicMember ? + string(R"( + mstore(add(pos, ), sub(tail, pos)) + tail := (, tail) + )") : + "(, add(pos, ))" + ); + encodeTempl("memberValues", memberValues); + encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); + encodingOffset += memberTypeTo->calldataHeadSize(); + encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); + encode = encodeTempl.render(); } - memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); - encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); - memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false)); + members.back()["encode"] = encode; - members.push_back({}); - members.back()["encode"] = memberTempl.render(); members.back()["memberName"] = member.name; } templ("members", members); + if (_options.dynamicInplace) + solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size."); templ("headSize", toCompactHexWithPrefix(encodingOffset)); return templ.render(); }); @@ -1020,7 +932,7 @@ string ABIFunctions::abiEncodingFunctionStruct( string ABIFunctions::abiEncodingFunctionStringLiteral( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { solAssert(_from.category() == Type::Category::StringLiteral, ""); @@ -1030,7 +942,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { auto const& strType = dynamic_cast(_from); string const& value = strType.value(); @@ -1038,9 +950,10 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( if (_to.isDynamicallySized()) { + solAssert(_to.category() == Type::Category::Array, ""); Whiskers templ(R"( function (pos) -> end { - mstore(pos, ) + pos := (pos, ) <#word> mstore(add(pos, ), ) @@ -1051,12 +964,17 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( // TODO this can make use of CODECOPY for large strings once we have that in Yul size_t words = (value.size() + 31) / 32; - templ("overallSize", to_string(32 + words * 32)); templ("length", to_string(value.size())); + templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast(_to), _options)); + if (_options.padded) + templ("overallSize", to_string(words * 32)); + else + templ("overallSize", to_string(value.size())); + vector> wordParams(words); for (size_t i = 0; i < words; ++i) { - wordParams[i]["offset"] = to_string(32 + i * 32); + wordParams[i]["offset"] = to_string(i * 32); wordParams[i]["wordValue"] = "0x" + h256(value.substr(32 * i, 32), h256::AlignLeft).hex(); } templ("word", wordParams); @@ -1081,8 +999,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( string ABIFunctions::abiEncodingFunctionFunctionType( FunctionType const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ) { solAssert(_from.kind() == FunctionType::Kind::External, ""); @@ -1093,10 +1010,9 @@ string ABIFunctions::abiEncodingFunctionFunctionType( _from.identifier() + "_to_" + _to.identifier() + - (_fromStack ? "_fromStack" : "") + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); - if (_fromStack) + if (_options.encodeFunctionFromStack) return createFunction(functionName, [&]() { return Whiskers(R"( function (addr, function_id, pos) { @@ -1104,7 +1020,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("combineExtFun", combineExternalFunctionIdFunction()) + ("combineExtFun", m_utils.combineExternalFunctionIdFunction()) .render(); }); else @@ -1115,7 +1031,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType( } )") ("functionName", functionName) - ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + ("cleanExtFun", m_utils.cleanupFunction(_to)) .render(); }); } @@ -1129,7 +1045,7 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); - if (auto arrayType = dynamic_cast(decodingType.get())) + if (auto arrayType = dynamic_cast(decodingType)) { if (arrayType->dataStoredIn(DataLocation::CallData)) { @@ -1141,22 +1057,30 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo else return abiDecodingFunctionArray(*arrayType, _fromMemory); } - else if (auto const* structType = dynamic_cast(decodingType.get())) - return abiDecodingFunctionStruct(*structType, _fromMemory); - else if (auto const* functionType = dynamic_cast(decodingType.get())) + else if (auto const* structType = dynamic_cast(decodingType)) + { + if (structType->dataStoredIn(DataLocation::CallData)) + { + solAssert(!_fromMemory, ""); + return abiDecodingFunctionCalldataStruct(*structType); + } + else + return abiDecodingFunctionStruct(*structType, _fromMemory); + } + else if (auto const* functionType = dynamic_cast(decodingType)) return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); else return abiDecodingFunctionValueType(_type, _fromMemory); } -string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory) +string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory) { TypePointer decodingType = _type.decodingType(); solAssert(decodingType, ""); solAssert(decodingType->sizeOnStack() == 1, ""); solAssert(decodingType->isValueType(), ""); - solAssert(decodingType->calldataEncodedSize() == 32, ""); solAssert(!decodingType->isDynamicallyEncoded(), ""); + solAssert(decodingType->calldataEncodedSize() == 32, ""); string functionName = "abi_decode_" + @@ -1165,14 +1089,15 @@ string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromM return createFunction(functionName, [&]() { Whiskers templ(R"( function (offset, end) -> value { - value := ((offset)) + value := (offset) + (value) } )"); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); - // Cleanup itself should use the type and not decodingType, because e.g. + // Validation should use the type and not decodingType, because e.g. // the decoding type of an enum is a plain int. - templ("cleanup", cleanupFunction(_type, true)); + templ("validator", m_utils.validatorFunction(_type, true)); return templ.render(); }); @@ -1209,7 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from let elementPos := mstore(dst, (elementPos, end)) dst := add(dst, 0x20) - src := add(src, ) + src := add(src, ) } } )" @@ -1217,8 +1142,10 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + string calldataStride = toCompactHexWithPrefix(_type.calldataStride()); + templ("stride", calldataStride); if (_type.isDynamicallySized()) templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); else @@ -1227,14 +1154,11 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from { templ("staticBoundsCheck", ""); templ("retrieveElementPos", "add(offset, " + load + "(src))"); - templ("baseEncodedSize", "0x20"); } else { - string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize()); - templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }"); + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }"); templ("retrieveElementPos", "src"); - templ("baseEncodedSize", baseEncodedSize); } templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); return templ.render(); @@ -1243,14 +1167,11 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) { - // This does not work with arrays of complex types - the array access - // is not yet implemented in Solidity. solAssert(_type.dataStoredIn(DataLocation::CallData), ""); if (!_type.isDynamicallySized()) solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - if (_type.baseType()->isDynamicallyEncoded()) - solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity."); - solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), ""); + solAssert(_type.calldataStride() > 0, ""); + solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), ""); string functionName = "abi_decode_" + @@ -1265,7 +1186,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) length := calldataload(offset) if gt(length, 0xffffffffffffffff) { revert(0, 0) } arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } } )"; else @@ -1273,14 +1194,15 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) // function (offset, end) -> arrayPos { arrayPos := offset - if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(, )), end) { revert(0, 0) } } )"; Whiskers w{templ}; w("functionName", functionName); w("readableTypeName", _type.toString(true)); - w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize())); - w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length())); + w("stride", toCompactHexWithPrefix(_type.calldataStride())); + if (!_type.isDynamicallySized()) + w("length", toCompactHexWithPrefix(_type.length())); return w.render(); }); } @@ -1312,22 +1234,43 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ ); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); - templ("allocate", allocationFunction()); - templ("allocationSize", arrayAllocationSizeFunction(_type)); - templ("copyToMemFun", copyToMemoryFunction(!_fromMemory)); + templ("allocate", m_utils.allocationFunction()); + templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); + templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory)); return templ.render(); }); } +string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + string functionName = + "abi_decode_" + + _type.identifier(); + + return createFunction(functionName, [&]() { + Whiskers w{R"( + // + function (offset, end) -> value { + if slt(sub(end, offset), ) { revert(0, 0) } + value := offset + } + )"}; + w("functionName", functionName); + w("readableTypeName", _type.toString(true)); + w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); + return w.render(); + }); +} + string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) { + solAssert(!_type.dataStoredIn(DataLocation::CallData), ""); string functionName = "abi_decode_" + _type.identifier() + (_fromMemory ? "_fromMemory" : ""); - solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), ""); - return createFunction(functionName, [&]() { Whiskers templ(R"( // @@ -1344,9 +1287,9 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr )"); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); - templ("allocate", allocationFunction()); - solAssert(_type.memorySize() < u256("0xffffffffffffffff"), ""); - templ("memorySize", toCompactHexWithPrefix(_type.memorySize())); + templ("allocate", m_utils.allocationFunction()); + solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), ""); + templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize())); size_t headPos = 0; vector> members; for (auto const& member: _type.members(nullptr)) @@ -1376,7 +1319,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr members.push_back({}); members.back()["decode"] = memberTempl.render(); members.back()["memberName"] = member.name; - headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize(); + headPos += decodingType->calldataHeadSize(); } templ("members", members); templ("minimumSize", toCompactHexWithPrefix(headPos)); @@ -1399,367 +1342,130 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, { return Whiskers(R"( function (offset, end) -> addr, function_selector { - addr, function_selector := ((offset)) + addr, function_selector := ((offset, end)) } )") ("functionName", functionName) - ("load", _fromMemory ? "mload" : "calldataload") - ("splitExtFun", splitExternalFunctionIdFunction()) + ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false)) + ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) .render(); } else { return Whiskers(R"( function (offset, end) -> fun { - fun := ((offset)) + fun := (offset) + (fun) } )") ("functionName", functionName) ("load", _fromMemory ? "mload" : "calldataload") - ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction()) + ("validateExtFun", m_utils.validatorFunction(_type, true)) .render(); } }); } -string ABIFunctions::copyToMemoryFunction(bool _fromCalldata) +string ABIFunctions::calldataAccessFunction(Type const& _type) { - string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; + solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); + string functionName = "calldata_access_" + _type.identifier(); return createFunction(functionName, [&]() { - if (_fromCalldata) + if (_type.isDynamicallyEncoded()) { - return Whiskers(R"( - function (src, dst, length) { - calldatacopy(dst, src, length) - // clear end - mstore(add(dst, length), 0) + unsigned int tailSize = _type.calldataEncodedTailSize(); + solAssert(tailSize > 1, ""); + Whiskers w(R"( + function (base_ref, ptr) -> { + let rel_offset_of_tail := calldataload(ptr) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + value := add(rel_offset_of_tail, base_ref) + } - )") - ("functionName", functionName) - .render(); + )"); + if (_type.isDynamicallySized()) + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, ""); + w("handleLength", Whiskers(R"( + length := calldataload(value) + value := add(value, 0x20) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + )")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render()); + w("return", "value, length"); + } + else + { + w("handleLength", ""); + w("return", "value"); + } + w("neededLength", toCompactHexWithPrefix(tailSize)); + w("functionName", functionName); + return w.render(); } - else + else if (_type.isValueType()) { + string decodingFunction; + if (auto const* functionType = dynamic_cast(&_type)) + decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false); + else + decodingFunction = abiDecodingFunctionValueType(_type, false); + // Note that the second argument to the decoding function should be discarded after inlining. return Whiskers(R"( - function (src, dst, length) { - let i := 0 - for { } lt(i, length) { i := add(i, 32) } - { - mstore(add(dst, i), mload(add(src, i))) - } - if gt(i, length) - { - // clear end - mstore(add(dst, length), 0) - } + function (baseRef, ptr) -> value { + value := (ptr, add(ptr, 32)) } )") ("functionName", functionName) + ("decodingFunction", decodingFunction) .render(); } - }); -} - -string ABIFunctions::shiftLeftFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - string functionName = "shift_left_" + to_string(_numBits); - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shl(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := mul(value, ) - } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -string ABIFunctions::shiftRightFunction(size_t _numBits) -{ - solAssert(_numBits < 256, ""); - - // 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"; - if (m_evmVersion.hasBitwiseShifting()) - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := shr(, value) - } - )") - ("functionName", functionName) - ("numBits", to_string(_numBits)) - .render(); - }); - } - else - { - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> newValue { - newValue := div(value, ) + else + { + solAssert( + _type.category() == Type::Category::Array || + _type.category() == Type::Category::Struct, + "" + ); + return Whiskers(R"( + function (baseRef, ptr) -> value { + value := ptr } - )") - ("functionName", functionName) - ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) - .render(); - }); - } -} - -std::string ABIFunctions::scaleFixedFunction(int _fromFractionalDigits, int _toFractionalDigits, bool _signed) -{ - string functionName = "scale_fixed_" + to_string(_fromFractionalDigits) + "_to_" + to_string(_toFractionalDigits) + (_signed ? "_signed" : "_unsigned"); - return createFunction(functionName, [&]() { - bigint const& changeFactor = pow(bigint(10), abs(_fromFractionalDigits - _toFractionalDigits)); - solAssert(changeFactor < bigint(1) << 128, ""); - return - Whiskers(R"( - function (value) -> scaled { - scaled := (value, ); - } )") ("functionName", functionName) - ("scaleFunc", _toFractionalDigits >= _fromFractionalDigits ? "mul" : _signed ? "sdiv" : "div") - ("factor", toString(changeFactor)) .render(); - }); -} - -string ABIFunctions::roundUpFunction() -{ - string functionName = "round_up_to_mul_of_32"; - return createFunction(functionName, [&]() { - return - Whiskers(R"( - function (value) -> result { - result := and(add(value, 31), not(31)) - } - )") - ("functionName", functionName) - .render(); - }); -} - -string ABIFunctions::arrayLengthFunction(ArrayType const& _type) -{ - string functionName = "array_length_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (value) -> length { - - } - )"); - w("functionName", functionName); - string body; - if (!_type.isDynamicallySized()) - body = "length := " + toCompactHexWithPrefix(_type.length()); - else - { - switch (_type.location()) - { - case DataLocation::CallData: - solAssert(false, "called regular array length function on calldata array"); - break; - case DataLocation::Memory: - body = "length := mload(value)"; - break; - case DataLocation::Storage: - if (_type.isByteArray()) - { - // Retrieve length both for in-place strings and off-place strings: - // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 - // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it - // computes (x & (-1)) / 2, which is equivalent to just x / 2. - body = R"( - length := sload(value) - let mask := sub(mul(0x100, iszero(and(length, 1))), 1) - length := div(and(length, mask), 2) - )"; - } - else - body = "length := sload(value)"; - break; - } - } - solAssert(!body.empty(), ""); - w("body", body); - return w.render(); - }); -} - -string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::Memory), ""); - string functionName = "array_allocation_size_" + _type.identifier(); - return createFunction(functionName, [&]() { - Whiskers w(R"( - function (length) -> size { - // Make sure we can allocate memory without overflow - if gt(length, 0xffffffffffffffff) { revert(0, 0) } - size := - - } - )"); - w("functionName", functionName); - if (_type.isByteArray()) - // Round up - w("allocationSize", "and(add(length, 0x1f), not(0x1f))"); - else - w("allocationSize", "mul(length, 0x20)"); - if (_type.isDynamicallySized()) - w("addLengthSlot", "size := add(size, 0x20)"); - else - w("addLengthSlot", ""); - return w.render(); - }); -} - -string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type) -{ - string functionName = "array_dataslot_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.dataStoredIn(DataLocation::Memory)) - { - if (_type.isDynamicallySized()) - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := add(memPtr, 0x20) - } - )") - ("functionName", functionName) - .render(); - else - return Whiskers(R"( - function (memPtr) -> dataPtr { - dataPtr := memPtr - } - )") - ("functionName", functionName) - .render(); - } - else if (_type.dataStoredIn(DataLocation::Storage)) - { - if (_type.isDynamicallySized()) - { - Whiskers w(R"( - function (slot) -> dataSlot { - mstore(0, slot) - dataSlot := keccak256(0, 0x20) - } - )"); - w("functionName", functionName); - return w.render(); - } - else - { - Whiskers w(R"( - function (slot) -> dataSlot { - dataSlot := slot - } - )"); - w("functionName", functionName); - return w.render(); - } - } - else - { - // Not used for calldata - solAssert(false, ""); } }); } -string ABIFunctions::nextArrayElementFunction(ArrayType const& _type) +string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) { - solAssert(!_type.isByteArray(), ""); - solAssert( - _type.location() == DataLocation::Memory || - _type.location() == DataLocation::Storage, - "" - ); - solAssert( - _type.location() == DataLocation::Memory || - _type.baseType()->storageBytes() > 16, - "" - ); - string functionName = "array_nextElement_" + _type.identifier(); + string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { - if (_type.location() == DataLocation::Memory) + if (_type.isDynamicallySized() && !_options.dynamicInplace) return Whiskers(R"( - function (memPtr) -> nextPtr { - nextPtr := add(memPtr, 0x20) + function (pos, length) -> updated_pos { + mstore(pos, length) + updated_pos := add(pos, 0x20) } )") ("functionName", functionName) .render(); - else if (_type.location() == DataLocation::Storage) + else return Whiskers(R"( - function (slot) -> nextSlot { - nextSlot := add(slot, 1) + function (pos, length) -> updated_pos { + updated_pos := pos } )") ("functionName", functionName) .render(); - else - solAssert(false, ""); - }); -} - -string ABIFunctions::allocationFunction() -{ - string functionName = "allocateMemory"; - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (size) -> memPtr { - memPtr := mload() - let newFreePtr := add(memPtr, size) - // protect against overflow - if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } - mstore(, newFreePtr) - } - )") - ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) - ("functionName", functionName) - .render(); }); } string ABIFunctions::createFunction(string const& _name, function const& _creator) { - if (!m_requestedFunctions.count(_name)) - { - auto fun = _creator(); - solAssert(!fun.empty(), ""); - m_requestedFunctions[_name] = fun; - } - return _name; + return m_functionCollector->createFunction(_name, _creator); } string ABIFunctions::createExternallyUsedFunction(string const& _name, function const& _creator) @@ -1773,12 +1479,15 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes) { size_t headSize = 0; for (auto const& t: _targetTypes) - { - if (t->isDynamicallyEncoded()) - headSize += 0x20; - else - headSize += t->calldataEncodedSize(); - } + headSize += t->calldataHeadSize(); return headSize; } + +size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options) +{ + if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack) + return 1; + else + return _type.sizeOnStack(); +} diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 27a0d5e0e..eba01d765 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -22,36 +22,47 @@ #pragma once -#include +#include +#include -#include +#include -#include #include -#include #include +#include +#include -namespace dev { -namespace solidity { +namespace dev +{ +namespace solidity +{ class Type; class ArrayType; class StructType; class FunctionType; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; using TypePointers = std::vector; -/// -/// Class to generate encoding and decoding functions. Also maintains a collection -/// of "functions to be generated" in order to avoid generating the same function -/// multiple times. -/// -/// Make sure to include the result of ``requestedFunctions()`` to a block that -/// is visible from the code that was generated here, or use named labels. +/** + * Class to generate encoding and decoding functions. Also maintains a collection + * of "functions to be generated" in order to avoid generating the same function + * multiple times. + * + * Make sure to include the result of ``requestedFunctions()`` to a block that + * is visible from the code that was generated here, or use named labels. + */ class ABIFunctions { public: - explicit ABIFunctions(EVMVersion _evmVersion = EVMVersion{}) : m_evmVersion(_evmVersion) {} + explicit ABIFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector = std::make_shared() + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)), + m_utils(_evmVersion, m_functionCollector) + {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes /// into memory, converting the types to @a _targetTypes on the fly. @@ -61,16 +72,26 @@ class ABIFunctions /// The values represent stack slots. If a type occupies more or less than one /// stack slot, it takes exactly that number of values. /// Returns a pointer to the end of the area written in memory. - /// Does not allocate memory (does not change the memory head pointer), but writes + /// Does not allocate memory (does not change the free memory pointer), but writes /// to memory starting at $headStart and an unrestricted amount after that. - /// Assigns the end of encoded memory either to $value0 or (if that is not present) - /// to $headStart. std::string tupleEncoder( TypePointers const& _givenTypes, TypePointers const& _targetTypes, bool _encodeAsLibraryTypes = false ); + /// @returns name of an assembly function to encode values of @a _givenTypes + /// with packed encoding into memory, converting the types to @a _targetTypes on the fly. + /// Parameters are: ... , i.e. + /// the layout on the stack is ... with + /// the top of the stack on the right. + /// The values represent stack slots. If a type occupies more or less than one + /// stack slot, it takes exactly that number of values. + /// Returns a pointer to the end of the area written in memory. + /// Does not allocate memory (does not change the free memory pointer), but writes + /// to memory starting at memPos and an unrestricted amount after that. + std::string tupleEncoderPacked(TypePointers const& _givenTypes, TypePointers const& _targetTypes); + /// @returns name of an assembly function to ABI-decode values of @a _types /// into memory. If @a _fromMemory is true, decodes from memory instead of /// from calldata. @@ -88,26 +109,24 @@ class ABIFunctions std::pair> requestedFunctions(); private: - /// @returns the name of the cleanup function for the given type and - /// adds its implementation to the requested functions. - /// @param _revertOnFailure if true, causes revert on invalid data, - /// otherwise an assertion failure. - std::string cleanupFunction(Type const& _type, bool _revertOnFailure = false); - - /// @returns the name of the function that converts a value of type @a _from - /// to a value of type @a _to. The resulting vale is guaranteed to be in range - /// (i.e. "clean"). Asserts on failure. - std::string conversionFunction(Type const& _from, Type const& _to); - - std::string cleanupCombinedExternalFunctionIdFunction(); - - /// @returns a function that combines the address and selector to a single value - /// for use in the ABI. - std::string combineExternalFunctionIdFunction(); - - /// @returns a function that splits the address and selector from a single value - /// for use in the ABI. - std::string splitExternalFunctionIdFunction(); + struct EncodingOptions + { + /// Pad/signextend value types and bytes/string to multiples of 32 bytes. + /// If false, data is always left-aligned. + /// Note that this is always re-set to true for the elements of arrays and structs. + bool padded = true; + /// Store arrays and structs in place without "data pointer" and do not store the length. + bool dynamicInplace = false; + /// Only for external function types: The value is a pair of address / function id instead + /// of a memory pointer to the compression representation. + bool encodeFunctionFromStack = false; + /// Encode storage pointers as storage pointers (we are targeting a library call). + bool encodeAsLibraryTypes = false; + + /// @returns a string to uniquely identify the encoding options for the encoding + /// function name. Skips everything that has its default value. + std::string toFunctionNameSuffix() const; + }; /// @returns the name of the ABI encoding function with the given type /// and queues the generation of the function to the requested functions. @@ -116,40 +135,49 @@ class ABIFunctions std::string abiEncodingFunction( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options + ); + /// @returns the name of a function that internally calls `abiEncodingFunction` + /// but always returns the updated encoding position, even if the type is + /// statically encoded. + std::string abiEncodeAndReturnUpdatedPosFunction( + Type const& _givenType, + Type const& _targetType, + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given calldata array. - std::string abiEncodingFunctionCalldataArray( + /// Uses calldatacopy and does not perform cleanup or validation and can therefore only + /// be used for byte arrays and arrays with the base type uint256 or bytes32. + std::string abiEncodingFunctionCalldataArrayWithoutCleanup( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes + 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, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); std::string abiEncodingFunctionMemoryByteArray( ArrayType const& _givenType, ArrayType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given storage array /// where multiple items are packed into the same storage slot. std::string abiEncodingFunctionCompactStorageArray( ArrayType const& _givenType, ArrayType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for struct types. std::string abiEncodingFunctionStruct( StructType const& _givenType, StructType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); // @returns the name of the ABI encoding function with the given type @@ -158,14 +186,13 @@ class ABIFunctions std::string abiEncodingFunctionStringLiteral( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); std::string abiEncodingFunctionFunctionType( FunctionType const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ); /// @returns the name of the ABI decoding function for the given type @@ -188,45 +215,22 @@ class ABIFunctions std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); /// Part of @a abiDecodingFunction for byte array types. std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory); + /// Part of @a abiDecodingFunction for calldata struct types. + std::string abiDecodingFunctionCalldataStruct(StructType const& _type); /// Part of @a abiDecodingFunction for struct types. std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); /// Part of @a abiDecodingFunction for array types. std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); - /// @returns a function that copies raw bytes of dynamic length from calldata - /// or memory to memory. - /// Pads with zeros and might write more than exactly length. - std::string copyToMemoryFunction(bool _fromCalldata); - - std::string shiftLeftFunction(size_t _numBits); - std::string shiftRightFunction(size_t _numBits); - - /// @returns a function that scales a fixed point type from one amount of fractional digits to another - /// Expects the inputted fixed type to be clean - std::string scaleFixedFunction(int _fromFractionalDigits, int _toFractionalDigits, bool _signed); - - /// @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. - std::string roundUpFunction(); - - std::string arrayLengthFunction(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. - std::string arrayAllocationSizeFunction(ArrayType const& _type); - /// @returns the name of a function that converts a storage slot number - /// or a memory pointer to the slot number / memory pointer for the data position of an array - /// which is stored in that slot / memory area. - std::string arrayDataAreaFunction(ArrayType const& _type); - /// @returns the name of a function that advances an array data pointer to the next element. - /// Only works for memory arrays and storage arrays that store one item per slot. - std::string nextArrayElementFunction(ArrayType const& _type); - - /// @returns the name of a function that allocates memory. - /// Modifies the "free memory pointer" - /// Arguments: size - /// Return value: pointer - std::string allocationFunction(); + /// @returns the name of a function that retrieves an element from calldata. + std::string calldataAccessFunction(Type const& _type); + + /// @returns the name of a function used during encoding that stores the length + /// if the array is dynamically sized (and the options do not request in-place encoding). + /// It returns the new encoding position. + /// If the array is not dynamically sized (or in-place encoding was requested), + /// does nothing and just returns the position again. + std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options); /// Helper function that uses @a _creator to create a function and add it to /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both @@ -241,10 +245,16 @@ class ABIFunctions /// @returns the size of the static part of the encoding of the given types. static size_t headSize(TypePointers const& _targetTypes); - /// Map from function name to code for a multi-use function. - std::map m_requestedFunctions; + /// @returns the number of variables needed to store a type. + /// This is one for almost all types. The exception being dynamically sized calldata arrays or + /// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack + /// is true), for which it is two. + static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options); + + langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; std::set m_externallyUsedFunctions; - EVMVersion m_evmVersion; + YulUtilFunctions m_utils; }; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index d33f749cf..81d1fb991 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -21,16 +21,21 @@ */ #include -#include + +#include +#include #include #include -#include -#include #include +#include +#include + using namespace std; using namespace dev; -using namespace solidity; +using namespace dev::eth; +using namespace langutil; +using namespace dev::solidity; void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const { @@ -40,7 +45,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack layout: [source_ref] [source length] target_ref (top) solAssert(_targetType.location() == DataLocation::Storage, ""); - TypePointer uint256 = make_shared(256); + TypePointer uint256 = TypeProvider::uint256(); TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); @@ -70,8 +75,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons } // stack: target_ref source_ref source_length - TypePointer targetType = _targetType.shared_from_this(); - TypePointer sourceType = _sourceType.shared_from_this(); + TypePointer targetType = &_targetType; + TypePointer sourceType = &_sourceType; m_context.callLowLevelFunction( "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), 3, @@ -238,7 +243,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons else if (_sourceType.location() == DataLocation::Memory) _context << sourceBaseType->memoryHeadSize(); else - _context << sourceBaseType->calldataEncodedSize(true); + _context << sourceBaseType->calldataHeadSize(); _context << Instruction::ADD << swapInstruction(2 + byteOffsetSize); @@ -289,20 +294,13 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord "Nested dynamic arrays not implemented here." ); CompilerUtils utils(m_context); - unsigned baseSize = 1; - if (!_sourceType.isByteArray()) - { - // We always pad the elements, regardless of _padToWordBoundaries. - baseSize = _sourceType.baseType()->calldataEncodedSize(); - solAssert(baseSize >= 0x20, ""); - } if (_sourceType.location() == DataLocation::CallData) { if (!_sourceType.isDynamicallySized()) m_context << _sourceType.length(); - if (baseSize > 1) - m_context << u256(baseSize) << Instruction::MUL; + if (!_sourceType.isByteArray()) + convertLengthToSize(_sourceType); string routine = "calldatacopy(target, source, len)\n"; if (_padToWordBoundaries) @@ -332,7 +330,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::DUP3 << Instruction::DUP5; accessIndex(_sourceType, false); MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType().get())) + if (auto baseArray = dynamic_cast(_sourceType.baseType())) copyArrayToMemory(*baseArray, _padToWordBoundaries); else utils.storeInMemoryDynamic(*_sourceType.baseType()); @@ -353,15 +351,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; m_context << Instruction::SWAP1; } - // convert length to size - if (baseSize > 1) - m_context << u256(baseSize) << Instruction::MUL; + if (!_sourceType.isByteArray()) + convertLengthToSize(_sourceType); // stack: m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; // We can resort to copying full 32 bytes only if // - the length is known to be a multiple of 32 or // - we will pad to full 32 bytes later anyway. - if (((baseSize % 32) == 0) || _padToWordBoundaries) + if (!_sourceType.isByteArray() || _padToWordBoundaries) utils.memoryCopy32(); else utils.memoryCopy(); @@ -369,11 +366,8 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context << Instruction::SWAP1 << Instruction::POP; // stack: - bool paddingNeeded = false; - if (_sourceType.isDynamicallySized()) - paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); - else - paddingNeeded = _padToWordBoundaries && (((_sourceType.length() * baseSize) % 32) != 0); + bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArray(); + if (paddingNeeded) { // stack: @@ -450,10 +444,10 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord m_context.appendJumpTo(loopEnd); m_context << longByteArray; } - // compute memory end offset - if (baseSize > 1) + else // convert length to memory size - m_context << u256(baseSize) << Instruction::MUL; + m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL; + m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; if (_sourceType.isDynamicallySized()) { @@ -489,7 +483,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord else m_context << Instruction::DUP2 << u256(0); StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType().get())) + if (auto baseArray = dynamic_cast(_sourceType.baseType())) copyArrayToMemory(*baseArray, _padToWordBoundaries); else utils.storeInMemoryDynamic(*_sourceType.baseType()); @@ -510,7 +504,12 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset if (haveByteOffset) m_context << Instruction::SWAP1 << Instruction::POP; - if (_padToWordBoundaries && baseSize % 32 != 0) + if (!_sourceType.isByteArray()) + { + solAssert(_sourceType.calldataStride() % 32 == 0, ""); + solAssert(_sourceType.memoryStride() % 32 == 0, ""); + } + if (_padToWordBoundaries && _sourceType.isByteArray()) { // memory_end_offset - start is the actual length (we want to compute the ceil of). // memory_offset - start is its next multiple of 32, but it might be off by 32. @@ -526,7 +525,7 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord void ArrayUtils::clearArray(ArrayType const& _typeIn) const { - TypePointer type = _typeIn.shared_from_this(); + TypePointer type = &_typeIn; m_context.callLowLevelFunction( "$clearArray_" + _typeIn.identifier(), 2, @@ -580,7 +579,7 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::ADD << Instruction::SWAP1; if (_type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); else ArrayUtils(_context).clearStorageLoop(_type.baseType()); _context << Instruction::POP; @@ -620,8 +619,8 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; // stack: data_pos_end data_pos - if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - clearStorageLoop(make_shared(256)); + if (_type.storageStride() < 32) + clearStorageLoop(TypeProvider::uint256()); else clearStorageLoop(_type.baseType()); // cleanup @@ -631,7 +630,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const { - TypePointer type = _typeIn.shared_from_this(); + TypePointer type = &_typeIn; m_context.callLowLevelFunction( "$resizeDynamicArray_" + _typeIn.identifier(), 2, @@ -728,7 +727,7 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const ArrayUtils(_context).convertLengthToSize(_type); _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); _context << Instruction::POP; // stack: ref new_length current_length first_word solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); @@ -766,8 +765,8 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const // stack: ref new_length data_pos new_size delete_end _context << Instruction::SWAP2 << Instruction::ADD; // stack: ref new_length delete_end delete_start - if (_type.isByteArray() || _type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(make_shared(256)); + if (_type.storageStride() < 32) + ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); else ArrayUtils(_context).clearStorageLoop(_type.baseType()); @@ -897,17 +896,22 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const // Stack: ArrayReference oldLength m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; // Stack ArrayReference newLength - m_context << Instruction::DUP2 << Instruction::DUP2; - // Stack ArrayReference newLength ArrayReference newLength; - accessIndex(_type, false); - // Stack: ArrayReference newLength storage_slot byte_offset - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); + + if (_type.baseType()->category() != Type::Category::Mapping) + { + m_context << Instruction::DUP2 << Instruction::DUP2; + // Stack ArrayReference newLength ArrayReference newLength; + accessIndex(_type, false); + // Stack: ArrayReference newLength storage_slot byte_offset + StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); + } + // Stack: ArrayReference newLength m_context << Instruction::SWAP1 << Instruction::SSTORE; } } -void ArrayUtils::clearStorageLoop(TypePointer const& _type) const +void ArrayUtils::clearStorageLoop(TypePointer _type) const { m_context.callLowLevelFunction( "$clearStorageLoop_" + _type->identifier(), @@ -931,8 +935,11 @@ void ArrayUtils::clearStorageLoop(TypePointer const& _type) const eth::AssemblyItem loopStart = _context.appendJumpToNew(); _context << loopStart; // check for loop condition - _context << Instruction::DUP1 << Instruction::DUP3 - << Instruction::GT << Instruction::ISZERO; + _context << + Instruction::DUP1 << + Instruction::DUP3 << + Instruction::GT << + Instruction::ISZERO; eth::AssemblyItem zeroLoopEnd = _context.newTag(); _context.appendConditionalJumpTo(zeroLoopEnd); // delete @@ -979,9 +986,9 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con if (!_arrayType.isByteArray()) { if (_arrayType.location() == DataLocation::Memory) - m_context << _arrayType.baseType()->memoryHeadSize(); + m_context << _arrayType.memoryStride(); else - m_context << _arrayType.baseType()->calldataEncodedSize(); + m_context << _arrayType.calldataStride(); m_context << Instruction::MUL; } else if (_pad) @@ -1024,7 +1031,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept } } -void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const +void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const { /// Stack: reference [length] index DataLocation location = _arrayType.location(); @@ -1044,28 +1051,37 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c m_context << Instruction::SWAP1 << Instruction::POP; // stack: - m_context << Instruction::SWAP1; - // stack: switch (location) { case DataLocation::Memory: - case DataLocation::CallData: - if (location == DataLocation::Memory && _arrayType.isDynamicallySized()) + // 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()) { - m_context << Instruction::SWAP1; - if (location == DataLocation::CallData) - m_context << _arrayType.baseType()->calldataEncodedSize(); - else - m_context << u256(_arrayType.memoryHeadSize()); + m_context << _arrayType.calldataStride(); m_context << Instruction::MUL; } + // stack: + if (_keepReference) + m_context << Instruction::DUP2; m_context << Instruction::ADD; break; case DataLocation::Storage: { + if (_keepReference) + m_context << Instruction::DUP2; + else + m_context << Instruction::SWAP1; + // stack: [] + eth::AssemblyItem endTag = m_context.newTag(); if (_arrayType.isByteArray()) { @@ -1111,6 +1127,50 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c } } +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 daf50bf52..9cb5338ae 100644 --- a/libsolidity/codegen/ArrayUtils.h +++ b/libsolidity/codegen/ArrayUtils.h @@ -32,7 +32,7 @@ namespace solidity class CompilerContext; class Type; class ArrayType; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; /** * Class that provides code generation for handling arrays. @@ -81,7 +81,7 @@ class ArrayUtils /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Stack pre: end_ref start_ref /// Stack post: end_ref - void clearStorageLoop(TypePointer const& _type) const; + void clearStorageLoop(TypePointer _type) const; /// Converts length to size (number of storage slots or calldata/memory bytes). /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. /// Stack pre: length @@ -97,11 +97,17 @@ class ArrayUtils /// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef. /// If @a _arrayType is a byte array, takes tight coding into account. void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const; - /// Performs bounds checking and returns a reference on the stack. + /// Checks whether the index is out of range and returns the absolute offset of the element reference[index] + /// (i.e. reference + index * size_of_base_type). + /// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack. /// Stack pre: reference [length] index - /// Stack post (storage): storage_slot byte_offset - /// Stack post: memory/calldata_offset - void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; + /// 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/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 55f1d252b..55db6290d 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -21,8 +21,9 @@ */ #include -#include + #include +#include using namespace std; using namespace dev; @@ -30,20 +31,30 @@ using namespace dev::solidity; void Compiler::compileContract( ContractDefinition const& _contract, - std::map const& _contracts, + std::map> const& _otherCompilers, bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); - runtimeCompiler.compileContract(_contract, _contracts); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); + runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); // This might modify m_runtimeContext because it can access runtime functions at // creation time. - ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); - m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); + OptimiserSettings creationSettings{m_optimiserSettings}; + // The creation code will be executed at most once, so we modify the optimizer + // settings accordingly. + creationSettings.expectedExecutionsPerDeployment = 1; + ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); + + m_context.optimise(m_optimiserSettings); +} - m_context.optimise(m_optimize, m_optimizeRuns); +std::shared_ptr Compiler::runtimeAssemblyPtr() const +{ + solAssert(m_context.runtimeContext(), ""); + return m_context.runtimeContext()->assemblyPtr(); } eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 4028ae63d..34bae7a27 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -23,12 +23,11 @@ #pragma once #include -#include - +#include +#include #include - -#include #include +#include namespace dev { namespace solidity { @@ -36,9 +35,8 @@ namespace solidity { class Compiler { public: - explicit Compiler(EVMVersion _evmVersion = EVMVersion{}, bool _optimize = false, unsigned _runs = 200): - m_optimize(_optimize), - m_optimizeRuns(_runs), + explicit Compiler(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_optimiserSettings(std::move(_optimiserSettings)), m_runtimeContext(_evmVersion), m_context(_evmVersion, &m_runtimeContext) { } @@ -47,11 +45,15 @@ class Compiler /// @arg _metadata contains the to be injected metadata CBOR void compileContract( ContractDefinition const& _contract, - std::map const& _contracts, + std::map> const& _otherCompilers, bytes const& _metadata ); /// @returns Entire assembly. eth::Assembly const& assembly() const { return m_context.assembly(); } + /// @returns Entire assembly as a shared pointer to non-const. + std::shared_ptr assemblyPtr() const { return m_context.assemblyPtr(); } + /// @returns Runtime assembly. + std::shared_ptr runtimeAssemblyPtr() const; /// @returns The entire assembled object (with constructor). eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } /// @returns Only the runtime object (without constructor). @@ -76,8 +78,7 @@ class Compiler eth::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const; private: - bool const m_optimize; - unsigned const m_optimizeRuns; + OptimiserSettings const m_optimiserSettings; CompilerContext m_runtimeContext; size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_context; diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index bf3ee8edc..7ca1a164d 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -21,20 +21,27 @@ */ #include -#include + #include #include #include +#include #include -#include -#include -#include -#include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include +#include #include +#include +#include +#include + #include #include @@ -43,16 +50,15 @@ // Change to "define" to output all intermediate code #undef SOL_OUTPUT_ASM #ifdef SOL_OUTPUT_ASM -#include +#include #endif using namespace std; - -namespace dev -{ -namespace solidity -{ +using namespace langutil; +using namespace dev::eth; +using namespace dev; +using namespace dev::solidity; void CompilerContext::addStateVariable( VariableDeclaration const& _declaration, @@ -125,8 +131,10 @@ void CompilerContext::appendMissingLowLevelFunctions() } } -void CompilerContext::addVariable(VariableDeclaration const& _declaration, - unsigned _offsetToCurrent) +void CompilerContext::addVariable( + VariableDeclaration const& _declaration, + unsigned _offsetToCurrent +) { solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack(); @@ -176,11 +184,18 @@ unsigned CompilerContext::numberOfLocalVariables() const return m_localVariables.size(); } -eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const +shared_ptr CompilerContext::compiledContract(ContractDefinition const& _contract) const { - auto ret = m_compiledContracts.find(&_contract); - solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); - return *ret->second; + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->assemblyPtr(); +} + +shared_ptr CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const +{ + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->runtimeAssemblyPtr(); } bool CompilerContext::isLocalVariable(Declaration const* _declaration) const @@ -274,7 +289,7 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const return m_asm->deposit() - _offset - 1; } -pair CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const +pair CompilerContext::storageLocationOfVariable(Declaration const& _declaration) const { auto it = m_stateVariables.find(&_declaration); solAssert(it != m_stateVariables.end(), "Variable not found in storage."); @@ -345,24 +360,33 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node) void CompilerContext::appendInlineAssembly( string const& _assembly, vector const& _localVariables, - set const&, - bool _system + set const& _externallyUsedFunctions, + bool _system, + OptimiserSettings const& _optimiserSettings ) { int startStackHeight = stackHeight(); + set externallyUsedIdentifiers; + for (auto const& fun: _externallyUsedFunctions) + externallyUsedIdentifiers.insert(yul::YulString(fun)); + for (auto const& var: _localVariables) + externallyUsedIdentifiers.insert(yul::YulString(var)); + yul::ExternalIdentifierAccess identifierAccess; identifierAccess.resolve = [&]( - assembly::Identifier const& _identifier, + yul::Identifier const& _identifier, yul::IdentifierContext, - bool - ) + bool _insideFunction + ) -> size_t { + if (_insideFunction) + return size_t(-1); auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name.str()); return it == _localVariables.end() ? size_t(-1) : 1; }; identifierAccess.generateCode = [&]( - assembly::Identifier const& _identifier, + yul::Identifier const& _identifier, yul::IdentifierContext _context, yul::AbstractAssembly& _assembly ) @@ -390,42 +414,79 @@ void CompilerContext::appendInlineAssembly( ErrorList errors; ErrorReporter errorReporter(errors); - auto scanner = make_shared(CharStream(_assembly), "--CODEGEN--"); - auto parserResult = assembly::Parser(errorReporter, assembly::AsmFlavour::Strict).parse(scanner, false); + auto scanner = make_shared(langutil::CharStream(_assembly, "--CODEGEN--")); + yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); + auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); #ifdef SOL_OUTPUT_ASM - cout << assembly::AsmPrinter()(*parserResult) << endl; + cout << yul::AsmPrinter()(*parserResult) << endl; #endif - assembly::AsmAnalysisInfo analysisInfo; + + auto reportError = [&](string const& _context) + { + string message = + "Error parsing/analyzing inline assembly block:\n" + + _context + "\n" + "------------------ Input: -----------------\n" + + _assembly + "\n" + "------------------ Errors: ----------------\n"; + for (auto const& error: errorReporter.errors()) + message += SourceReferenceFormatter::formatErrorInformation(*error); + message += "-------------------------------------------\n"; + + solAssert(false, message); + }; + + yul::AsmAnalysisInfo analysisInfo; bool analyzerResult = false; if (parserResult) - analyzerResult = assembly::AsmAnalyzer( + analyzerResult = yul::AsmAnalyzer( analysisInfo, errorReporter, - m_evmVersion, boost::none, - assembly::AsmFlavour::Strict, + dialect, identifierAccess.resolve ).analyze(*parserResult); if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) + reportError("Invalid assembly generated by code generator."); + + // Several optimizer steps cannot handle externally supplied stack variables, + // so we essentially only optimize the ABI functions. + if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { - string message = - "Error parsing/analyzing inline assembly block:\n" - "------------------ Input: -----------------\n" + - _assembly + "\n" - "------------------ Errors: ----------------\n"; - for (auto const& error: errorReporter.errors()) - message += SourceReferenceFormatter::formatExceptionInformation( - *error, - error->typeNameCstr(), - [&](string const&) -> Scanner const& { return *scanner; } - ); - message += "-------------------------------------------\n"; + bool const isCreation = m_runtimeContext != nullptr; + yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); + yul::Object obj; + obj.code = parserResult; + obj.analysisInfo = make_shared(analysisInfo); + yul::OptimiserSuite::run( + dialect, + &meter, + obj, + _optimiserSettings.optimizeStackAllocation, + externallyUsedIdentifiers + ); + analysisInfo = std::move(*obj.analysisInfo); + parserResult = std::move(obj.code); - solAssert(false, message); +#ifdef SOL_OUTPUT_ASM + cout << "After optimizer: " << endl; + cout << yul::AsmPrinter()(*parserResult) << endl; +#endif } + if (!errorReporter.errors().empty()) + reportError("Failed to analyze inline assembly block."); + solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system); + yul::CodeGenerator::assemble( + *parserResult, + analysisInfo, + *m_asm, + m_evmVersion, + identifierAccess, + _system, + _optimiserSettings.optimizeStackAllocation + ); // Reset the source location to the one of the node (instead of the CODEGEN source location) updateSourceLocation(); @@ -444,7 +505,7 @@ FunctionDefinition const& CompilerContext::resolveVirtualFunction( if ( function->name() == name && !function->isConstructor() && - FunctionType(*function).hasEqualParameterTypes(functionType) + FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType) ) return *function; solAssert(false, "Super function " + name + " not found."); @@ -464,6 +525,21 @@ void CompilerContext::updateSourceLocation() m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } +eth::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) +{ + // Constructing it this way so that we notice changes in the fields. + eth::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0}; + asmSettings.isCreation = true; + asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; + asmSettings.runPeephole = _settings.runPeephole; + asmSettings.runDeduplicate = _settings.runDeduplicate; + asmSettings.runCSE = _settings.runCSE; + asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; + asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; + asmSettings.evmVersion = m_evmVersion; + return asmSettings; +} + eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( Declaration const& _declaration, CompilerContext& _context @@ -519,6 +595,3 @@ void CompilerContext::FunctionCompilationQueue::startFunction(Declaration const& m_functionsToCompile.pop(); m_alreadyCompiledFunctions.insert(&_function); } - -} -} diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 377fff677..ff0d1a52c 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -22,29 +22,29 @@ #pragma once +#include +#include +#include #include #include -#include - -#include -#include -#include +#include -#include #include - +#include +#include #include +#include #include #include #include #include -#include namespace dev { namespace solidity { +class Compiler; /** * Context to be shared by all units that compile the same contract. @@ -53,7 +53,7 @@ namespace solidity { class CompilerContext { public: - explicit CompilerContext(EVMVersion _evmVersion = EVMVersion{}, CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared()), m_evmVersion(_evmVersion), m_runtimeContext(_runtimeContext), @@ -63,7 +63,7 @@ class CompilerContext m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } - EVMVersion const& evmVersion() const { return m_evmVersion; } + langutil::EVMVersion const& evmVersion() const { return m_evmVersion; } /// Update currently enabled set of experimental features. void setExperimentalFeatures(std::set const& _features) { m_experimentalFeatures = _features; } @@ -80,8 +80,9 @@ class CompilerContext /// Returns the number of currently allocated local variables. unsigned numberOfLocalVariables() const; - void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } - eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; + void setOtherCompilers(std::map> const& _otherCompilers) { m_otherCompilers = _otherCompilers; } + std::shared_ptr compiledContract(ContractDefinition const& _contract) const; + std::shared_ptr compiledContractRuntime(ContractDefinition const& _contract) const; void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } @@ -173,11 +174,10 @@ class CompilerContext /// the data. CompilerContext& appendConditionalRevert(bool _forwardReturnData = false); /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } - /// Appends a JUMP to a specific tag and return to preceed current instruction - /// must make sure that destination shall jump returnTag on the stack - CompilerContext& appendJumpToAndReturn(eth::AssemblyItem const& _tag); - + CompilerContext& appendJumpTo( + eth::AssemblyItem const& _tag, + eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary + ) { *m_asm << _tag.pushTag(); return appendJump(_jumpType); } /// Appends pushing of a new tag and @returns the new tag. eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data @@ -209,7 +209,7 @@ class CompilerContext /// Append elements to the current instruction list and adjust @a m_stackOffset. CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(dev::eth::Instruction _instruction) { m_asm->append(_instruction); return *this; } CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } @@ -224,25 +224,26 @@ class CompilerContext std::string const& _assembly, std::vector const& _localVariables = std::vector(), std::set const& _externallyUsedFunctions = std::set(), - bool _system = false + bool _system = false, + OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() ); /// Appends arbitrary data to the end of the bytecode. void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } /// Run optimisation step. - void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } + void optimise(OptimiserSettings const& _settings) { m_asm->optimise(translateOptimiserSettings(_settings)); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. - CompilerContext* runtimeContext() { return m_runtimeContext; } + CompilerContext* runtimeContext() const { return m_runtimeContext; } /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } /// @returns a const reference to the underlying assembly. eth::Assembly const& assembly() const { return *m_asm; } - /// @returns non-const reference to the underlying assembly. Should be avoided in favour of - /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return *m_asm; } + /// @returns a shared pointer to the assembly. + /// Should be avoided except when adding sub-assemblies. + std::shared_ptr assemblyPtr() const { return m_asm; } /// @arg _sourceCodes is the map of input files to source code strings std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const @@ -278,10 +279,12 @@ class CompilerContext std::vector::const_iterator _searchStart ); /// @returns an iterator to the contract directly above the given contract. - std::vector::const_iterator superContract(const ContractDefinition &_contract) const; + std::vector::const_iterator superContract(ContractDefinition const& _contract) const; /// Updates source location set in the assembly. void updateSourceLocation(); + eth::Assembly::OptimiserSettings translateOptimiserSettings(OptimiserSettings const& _settings); + /** * Helper class that manages function labels and ensures that referenced functions are * compiled in a specific order. @@ -302,7 +305,7 @@ class CompilerContext Declaration const* nextFunctionToCompile() const; /// Informs the queue that we are about to compile the given function, i.e. removes /// the function from the queue of functions to compile. - void startFunction(const Declaration &_function); + void startFunction(Declaration const& _function); /// Labels pointing to the entry points of functions. std::map m_entryLabels; @@ -318,11 +321,11 @@ class CompilerContext eth::AssemblyPointer m_asm; /// Version of the EVM to compile against. - EVMVersion m_evmVersion; + langutil::EVMVersion m_evmVersion; /// Activated experimental features. std::set m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. - std::map m_compiledContracts; + std::map> m_otherCompilers; /// Storage offsets of state variables std::map> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 982d6e550..f8b80c680 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -23,26 +23,23 @@ #include #include +#include +#include #include #include -#include - #include - #include using namespace std; +using namespace langutil; +using namespace dev; +using namespace dev::eth; +using namespace dev::solidity; -namespace dev -{ -namespace solidity -{ - -const unsigned CompilerUtils::dataStartOffset = 4; -const size_t CompilerUtils::freeMemoryPointer = 64; -const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; -const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; -const unsigned CompilerUtils::identityContractAddress = 4; +unsigned const CompilerUtils::dataStartOffset = 4; +size_t const CompilerUtils::freeMemoryPointer = 64; +size_t const CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; +size_t const CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area."); static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer."); @@ -71,6 +68,13 @@ void CompilerUtils::allocateMemory() storeFreeMemoryPointer(); } +void CompilerUtils::allocateMemory(u256 const& size) +{ + fetchFreeMemoryPointer(); + m_context << Instruction::DUP1 << size << Instruction::ADD; + storeFreeMemoryPointer(); +} + void CompilerUtils::toSizeAfterFreeMemoryPointer() { fetchFreeMemoryPointer(); @@ -80,17 +84,65 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer() void CompilerUtils::revertWithStringData(Type const& _argumentType) { - solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), ""); + solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), ""); fetchFreeMemoryPointer(); m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32)); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(4) << Instruction::ADD; // Stack: - abiEncode({_argumentType.shared_from_this()}, {make_shared(DataLocation::Memory, true)}); + abiEncode({&_argumentType}, {TypeProvider::array(DataLocation::Memory, true)}); toSizeAfterFreeMemoryPointer(); m_context << Instruction::REVERT; } +void CompilerUtils::accessCalldataTail(Type const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::CallData), ""); + solAssert(_type.isDynamicallyEncoded(), ""); + + unsigned int tailSize = _type.calldataEncodedTailSize(); + solAssert(tailSize > 1, ""); + + // returns the absolute offset of the tail in "base_ref" + m_context.appendInlineAssembly(Whiskers(R"({ + let rel_offset_of_tail := calldataload(ptr_to_tail) + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { revert(0, 0) } + base_ref := add(base_ref, rel_offset_of_tail) + })")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"}); + // stack layout: + + if (!_type.isDynamicallySized()) + { + m_context << Instruction::POP; + // stack layout: + solAssert( + _type.category() == Type::Category::Struct || + _type.category() == Type::Category::Array, + "Invalid dynamically encoded base type on tail access." + ); + } + else + { + auto const* arrayType = dynamic_cast(&_type); + solAssert(!!arrayType, "Invalid dynamically sized type."); + unsigned int calldataStride = arrayType->calldataStride(); + solAssert(calldataStride > 0, ""); + + // returns the absolute offset of the tail in "base_ref" + // and the length of the tail in "length" + m_context.appendInlineAssembly( + Whiskers(R"({ + length := calldataload(base_ref) + base_ref := add(base_ref, 0x20) + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } + })")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), + {"base_ref", "length"} + ); + // stack layout: + } +} + unsigned CompilerUtils::loadFromMemory( unsigned _offset, Type const& _type, @@ -119,7 +171,7 @@ void CompilerUtils::loadFromMemoryDynamic( solAssert(!_fromCalldata, ""); solAssert(_padToWordBoundaries, ""); if (_keepUpdatedMemoryOffset) - m_context << arrayType->memorySize() << Instruction::ADD; + m_context << arrayType->memoryDataSize() << Instruction::ADD; } else { @@ -135,7 +187,7 @@ void CompilerUtils::loadFromMemoryDynamic( void CompilerUtils::storeInMemory(unsigned _offset) { - unsigned numBytes = prepareMemoryStore(IntegerType(256), true); + unsigned numBytes = prepareMemoryStore(*TypeProvider::uint256(), true); if (numBytes > 0) m_context << u256(_offset) << Instruction::MSTORE; } @@ -149,7 +201,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ref->location() == DataLocation::Memory, "Only in-memory reference type can be stored." ); - storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries); + storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries); } else if (auto str = dynamic_cast(&_type)) { @@ -200,7 +252,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem //@todo this does not yet support nested dynamic arrays size_t encodedSize = 0; for (auto const& t: _typeParameters) - encodedSize += t->decodingType()->calldataEncodedSize(true); + encodedSize += t->decodingType()->calldataHeadSize(); m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"}); m_context << Instruction::DUP2 << Instruction::ADD; @@ -231,6 +283,15 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem { // compute data pointer m_context << Instruction::DUP1 << Instruction::MLOAD; + // stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset + + fetchFreeMemoryPointer(); + // stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset dstmem + moveIntoStack(4); + // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset + m_context << Instruction::DUP5; + // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset dstmem + // Check that the data pointer is valid and that length times // item size is still inside the range. Whiskers templ(R"({ @@ -243,11 +304,17 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem gt(array_length, 0x100000000), gt(add(array_data_start, mul(array_length, )), input_end) ) { revert(0, 0) } + mstore(dst, array_length) + dst := add(dst, 0x20) })"); - templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true))); - m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"}); - // stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k) - moveIntoStack(3); + templ("item_size", to_string(arrayType.calldataStride())); + m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"}); + // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata + m_context << Instruction::SWAP1; + // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset dstdata data_ptr + ArrayUtils(m_context).copyArrayToMemory(arrayType, true); + // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset mem_end + storeFreeMemoryPointer(); m_context << u256(0x20) << Instruction::ADD; } else @@ -255,17 +322,17 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // Size has already been checked for this one. moveIntoStack(2); m_context << Instruction::DUP3; - m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD; + m_context << u256(arrayType.calldataHeadSize()) << Instruction::ADD; } } else { // first load from calldata and potentially convert to memory if arrayType is memory - TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false); + TypePointer calldataType = TypeProvider::withLocation(&arrayType, DataLocation::CallData, false); if (calldataType->isDynamicallySized()) { // put on stack: data_pointer length - loadFromMemoryDynamic(IntegerType(256), !_fromMemory); + loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory); m_context << Instruction::SWAP1; // stack: input_end base_offset next_pointer data_offset m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"}); @@ -276,15 +343,14 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem {"input_end", "base_offset", "next_ptr", "array_head_ptr"} ); // retrieve length - loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true); + loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true); // stack: input_end base_offset next_pointer array_length data_pointer m_context << Instruction::SWAP2; // stack: input_end base_offset data_pointer array_length next_pointer - unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true); m_context.appendInlineAssembly(R"({ if or( gt(array_length, 0x100000000), - gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end) + gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end) ) { revert(0, 0) } })", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); } @@ -293,7 +359,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // size has already been checked // stack: input_end base_offset data_offset m_context << Instruction::DUP1; - m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD; + m_context << u256(calldataType->calldataHeadSize()) << Instruction::ADD; } if (arrayType.location() == DataLocation::Memory) { @@ -349,11 +415,15 @@ void CompilerUtils::encodeToMemory( if (_givenTypes.empty()) return; - else if (_padToWordBoundaries && !_copyDynamicDataInPlace && encoderV2) + if (encoderV2) { // Use the new Yul-based encoding function + solAssert( + _padToWordBoundaries != _copyDynamicDataInPlace, + "Non-padded and in-place encoding can only be combined." + ); auto stackHeightBefore = m_context.stackHeight(); - abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes); + abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries); solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), ""); return; } @@ -401,8 +471,7 @@ void CompilerUtils::encodeToMemory( type = _givenTypes[i]; // delay conversion } else { convertType(*_givenTypes[i], *targetType, true); - } - if (auto arrayType = dynamic_cast(type.get())) { + if (auto arrayType = dynamic_cast(type)) ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); } else { @@ -433,7 +502,7 @@ void CompilerUtils::encodeToMemory( auto const& strType = dynamic_cast(*_givenTypes[i]); m_context << u256(strType.value().size()); /* TODO:: ENI*/ - storeInMemoryDynamic(IntegerType(256), true); + storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... storeInMemoryDynamic(strType, _padToWordBoundaries); } @@ -449,7 +518,7 @@ void CompilerUtils::encodeToMemory( m_context << dupInstruction(1 + arrayType.sizeOnStack()); ArrayUtils(m_context).retrieveLength(arrayType, 1); // stack: ... - storeInMemoryDynamic(IntegerType(256), true); + storeInMemoryDynamic(*TypeProvider::uint256(), true); // stack: ... // copy the new memory pointer m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP; @@ -472,15 +541,22 @@ void CompilerUtils::encodeToMemory( void CompilerUtils::abiEncodeV2( TypePointers const& _givenTypes, TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes + bool _encodeAsLibraryTypes, + bool _padToWordBoundaries ) { + if (!_padToWordBoundaries) + solAssert(!_encodeAsLibraryTypes, "Library calls cannot be packed."); + // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> auto ret = m_context.pushNewTag(); moveIntoStack(sizeOnStack(_givenTypes) + 1); - string encoderName = m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes); + string encoderName = + _padToWordBoundaries ? + m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) : + m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes); m_context.appendJumpTo(m_context.namedTag(encoderName)); m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1); m_context << ret.tag(); @@ -512,16 +588,11 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) codecopy(memptr, codesize(), size) memptr := add(memptr, size) })"); - templ("element_size", to_string(_type.isByteArray() ? 1 : _type.baseType()->memoryHeadSize())); + templ("element_size", to_string(_type.memoryStride())); m_context.appendInlineAssembly(templ.render(), {"length", "memptr"}); } else { - // TODO: Potential optimization: - // When we create a new multi-dimensional dynamic array, each element - // is initialized to an empty array. It actually does not hurt - // to re-use exactly the same empty array for all elements. Currently, - // a new one is created each time. auto repeat = m_context.newTag(); m_context << repeat; pushZeroValue(*_type.baseType()); @@ -648,10 +719,13 @@ void CompilerUtils::convertType( bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer; if (chopSignBitsPending) { - const IntegerType& targetIntegerType = dynamic_cast(_targetType); + IntegerType const& targetIntegerType = dynamic_cast(_targetType); chopSignBitsPending = targetIntegerType.isSigned(); } + if (targetTypeCategory == Type::Category::FixedPoint) + solUnimplemented("Not yet implemented - FixedPointType."); + switch (stackTypeCategory) { case Type::Category::FixedBytes: @@ -776,7 +850,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; @@ -807,9 +886,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; } @@ -831,12 +910,11 @@ void CompilerUtils::convertType( { auto const& arrayType = dynamic_cast(_targetType); solAssert(arrayType.isByteArray(), ""); - u256 storageSize(32 + ((data.size() + 31) / 32) * 32); - m_context << storageSize; - allocateMemory(); + unsigned storageSize = 32 + ((data.size() + 31) / 32) * 32; + allocateMemory(storageSize); // stack: mempos m_context << Instruction::DUP1 << u256(data.size()); - storeInMemoryDynamic(IntegerType(256)); + storeInMemoryDynamic(*TypeProvider::uint256()); // stack: mempos datapos storeStringData(data); } @@ -885,12 +963,11 @@ void CompilerUtils::convertType( if (targetType.isDynamicallySized()) { m_context << Instruction::DUP2; - storeInMemoryDynamic(IntegerType(256)); + storeInMemoryDynamic(*TypeProvider::uint256()); } // stack: (variably sized) if (targetType.baseType()->isValueType()) { - solAssert(typeOnStack.baseType()->isValueType(), ""); copyToStackTop(2 + stackSize, stackSize); ArrayUtils(m_context).copyArrayToMemory(typeOnStack); } @@ -924,10 +1001,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; @@ -938,8 +1016,7 @@ void CompilerUtils::convertType( auto& targetType = dynamic_cast(_targetType); auto& typeOnStack = dynamic_cast(_typeOnStack); solAssert( - targetType.location() != DataLocation::CallData && - typeOnStack.location() != DataLocation::CallData + targetType.location() != DataLocation::CallData , ""); switch (targetType.location()) { @@ -953,28 +1030,56 @@ void CompilerUtils::convertType( break; case DataLocation::Memory: // Copy the array to a free position in memory, unless it is already in memory. - if (typeOnStack.location() != DataLocation::Memory) + switch (typeOnStack.location()) { - solAssert(typeOnStack.location() == DataLocation::Storage, ""); - // stack: - m_context << typeOnStack.memorySize(); - allocateMemory(); - m_context << Instruction::SWAP1 << Instruction::DUP2; - // stack: - for (auto const& member: typeOnStack.members(nullptr)) + case DataLocation::Storage: + { + auto conversionImpl = + [typeOnStack = &typeOnStack, targetType = &targetType](CompilerContext& _context) { - if (!member.type->canLiveOutsideStorage()) - continue; - pair const& offsets = typeOnStack.storageOffsetsOfMember(member.name); - m_context << offsets.first << Instruction::DUP3 << Instruction::ADD; - m_context << u256(offsets.second); - StorageItem(m_context, *member.type).retrieveValue(SourceLocation(), true); - TypePointer targetMemberType = targetType.memberType(member.name); - solAssert(!!targetMemberType, "Member not found in target type."); - convertType(*member.type, *targetMemberType, true); - storeInMemoryDynamic(*targetMemberType, true); - } - m_context << Instruction::POP << Instruction::POP; + CompilerUtils utils(_context); + // stack: + utils.allocateMemory(typeOnStack->memoryDataSize()); + _context << Instruction::SWAP1 << Instruction::DUP2; + // stack: + for (auto const& member: typeOnStack->members(nullptr)) + { + if (!member.type->canLiveOutsideStorage()) + continue; + pair const& offsets = typeOnStack->storageOffsetsOfMember(member.name); + _context << offsets.first << Instruction::DUP3 << Instruction::ADD; + _context << u256(offsets.second); + StorageItem(_context, *member.type).retrieveValue(SourceLocation(), true); + TypePointer targetMemberType = targetType->memberType(member.name); + solAssert(!!targetMemberType, "Member not found in target type."); + utils.convertType(*member.type, *targetMemberType, true); + utils.storeInMemoryDynamic(*targetMemberType, true); + } + _context << Instruction::POP << Instruction::POP; + }; + if (typeOnStack.recursive()) + m_context.callLowLevelFunction( + "$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + targetType.identifier(), + 1, + 1, + conversionImpl + ); + else + conversionImpl(m_context); + break; + } + case DataLocation::CallData: + { + solUnimplementedAssert(!typeOnStack.isDynamicallyEncoded(), ""); + m_context << Instruction::DUP1; + m_context << Instruction::CALLDATASIZE; + m_context << Instruction::SUB; + abiDecode({&targetType}, false); + break; + } + case DataLocation::Memory: + // nothing to do + break; } break; case DataLocation::CallData: @@ -1083,6 +1188,14 @@ void CompilerUtils::pushZeroValue(Type const& _type) m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { _context.appendInvalid(); }); + if (CompilerContext* runCon = m_context.runtimeContext()) + { + leftShiftNumberOnStack(32); + m_context << runCon->lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { + _context.appendInvalid(); + }).toSubAssemblyTag(m_context.runtimeSub()); + m_context << Instruction::OR; + } return; } } @@ -1102,24 +1215,24 @@ void CompilerUtils::pushZeroValue(Type const& _type) return; } - TypePointer type = _type.shared_from_this(); + TypePointer type = &_type; m_context.callLowLevelFunction( "$pushZeroValue_" + referenceType->identifier(), 0, 1, [type](CompilerContext& _context) { CompilerUtils utils(_context); - _context << u256(max(32u, type->calldataEncodedSize())); - utils.allocateMemory(); + + utils.allocateMemory(max(32u, type->memoryDataSize())); _context << Instruction::DUP1; - if (auto structType = dynamic_cast(type.get())) + if (auto structType = dynamic_cast(type)) for (auto const& member: structType->members(nullptr)) { utils.pushZeroValue(*member.type); utils.storeInMemoryDynamic(*member.type); } - else if (auto arrayType = dynamic_cast(type.get())) + else if (auto arrayType = dynamic_cast(type)) { solAssert(!arrayType->isDynamicallySized(), ""); if (arrayType->length() > 0) @@ -1216,10 +1329,10 @@ void CompilerUtils::popAndJump(unsigned _toHeight, eth::AssemblyItem const& _jum m_context.adjustStackOffset(amount); } -unsigned CompilerUtils::sizeOnStack(vector> const& _variableTypes) +unsigned CompilerUtils::sizeOnStack(vector const& _variableTypes) { unsigned size = 0; - for (shared_ptr const& type: _variableTypes) + for (Type const* const& type: _variableTypes) size += type->sizeOnStack(); return size; } @@ -1230,16 +1343,39 @@ void CompilerUtils::computeHashStatic() m_context << u256(32) << u256(0) << Instruction::KECCAK256; } +void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation) +{ + string which = _creation ? "Creation" : "Runtime"; + m_context.callLowLevelFunction( + "$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(), + 1, + 1, + [&contract, _creation](CompilerContext& _context) + { + // copy the contract's code into memory + shared_ptr assembly = + _creation ? + _context.compiledContract(contract) : + _context.compiledContractRuntime(contract); + // pushes size + auto subroutine = _context.addSubroutine(assembly); + _context << Instruction::DUP1 << subroutine; + _context << Instruction::DUP4 << Instruction::CODECOPY; + _context << Instruction::ADD; + } + ); +} + void CompilerUtils::storeStringData(bytesConstRef _data) { //@todo provide both alternatives to the optimiser // stack: mempos - if (_data.size() <= 128) + if (_data.size() <= 32) { for (unsigned i = 0; i < _data.size(); i += 32) { m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft)); - storeInMemoryDynamic(IntegerType(256)); + storeInMemoryDynamic(*TypeProvider::uint256()); } m_context << Instruction::POP; } @@ -1254,6 +1390,8 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) { + solAssert(_type.isValueType(), ""); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast(&_type)) @@ -1266,6 +1404,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda } solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); + bool cleanupNeeded = true; if (isExternalFunctionType) splitExternalFunctionType(true); else if (numBytes != 32) @@ -1275,10 +1414,16 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda int shiftFactor = (32 - numBytes) * 8; rightShiftNumberOnStack(shiftFactor); if (leftAligned) + { leftShiftNumberOnStack(shiftFactor); + cleanupNeeded = false; + } + else if (IntegerType const* intType = dynamic_cast(&_type)) + if (!intType->isSigned()) + cleanupNeeded = false; } if (_fromCalldata) - convertType(_type, _type, true, false, true); + convertType(_type, _type, cleanupNeeded, false, true); return numBytes; } @@ -1319,6 +1464,8 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) "Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")." ); + solAssert(!_type.isDynamicallyEncoded(), ""); + unsigned numBytes = _type.calldataEncodedSize(_padToWords); solAssert( diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index ac3d5ee02..1842ac404 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -22,8 +22,10 @@ #pragma once -#include #include +#include +#include +#include namespace dev { namespace solidity { @@ -49,6 +51,10 @@ class CompilerUtils /// Stack pre: /// Stack post: void allocateMemory(); + /// Allocates a number of bytes in memory as given on the stack. + /// Stack pre: + /// Stack post: + void allocateMemory(u256 const& size); /// Appends code that transforms memptr to (memptr - free_memptr) memptr /// Stack pre: /// Stack post: @@ -61,6 +67,13 @@ class CompilerUtils /// Stack post: void revertWithStringData(Type const& _argumentType); + /// Computes the absolute calldata offset of a tail given a base reference and the (absolute) + /// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also + /// returns the array length on the stack. + /// Stack pre: base_ref tail_ptr + /// Stack post: tail_ref [length] + void accessCalldataTail(Type const& _type); + /// Loads data from memory to the stack. /// @param _offset offset in memory (or calldata) /// @param _type data type to load @@ -69,7 +82,7 @@ class CompilerUtils /// @returns the number of bytes consumed in memory. unsigned loadFromMemory( unsigned _offset, - Type const& _type = IntegerType(256), + Type const& _type = *TypeProvider::uint256(), bool _fromCalldata = false, bool _padToWords = false ); @@ -88,7 +101,6 @@ class CompilerUtils void storeStringData(bytesConstRef _data); /// Stores a 256 bit integer from stack in memory. /// @param _offset offset in memory - /// @param _type type of the data on the stack void storeInMemory(unsigned _offset); /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack /// and also updates that. For reference types, only copies the data pointer. Fails for @@ -170,7 +182,8 @@ class CompilerUtils void abiEncodeV2( TypePointers const& _givenTypes, TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes = false + bool _encodeAsLibraryTypes = false, + bool _padToWordBoundaries = true ); /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, @@ -197,6 +210,11 @@ class CompilerUtils /// Stack post: void memoryCopy(); + /// Stores the given string in memory. + /// Stack pre: mempos + /// Stack post: + void storeStringData(bytesConstRef _data); + /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) /// external function type
into two stack slots: /// address (right aligned), function identifier (right aligned) @@ -259,7 +277,7 @@ class CompilerUtils template static unsigned sizeOnStack(std::vector const& _variables); - static unsigned sizeOnStack(std::vector> const& _variableTypes); + static unsigned sizeOnStack(std::vector const& _variableTypes); /// Helper function to shift top value on the stack to the left. /// Stack pre: @@ -274,21 +292,25 @@ class CompilerUtils /// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type. void computeHashStatic(); + /// Apppends code that copies the code of the given contract to memory. + /// Stack pre: Memory position + /// Stack post: Updated memory position + /// @param creation if true, copies creation code, if false copies runtime code. + /// @note the contract has to be compiled already, so beware of cyclic dependencies! + void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode); + /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. - static const unsigned dataStartOffset; + static unsigned const dataStartOffset; /// Position of the free-memory-pointer in memory; - static const size_t freeMemoryPointer; + static size_t const freeMemoryPointer; /// Position of the memory slot that is always zero. - static const size_t zeroPointer; + static size_t const zeroPointer; /// Starting offset for memory available to the user (aka the contract). - static const size_t generalPurposeMemoryStart; + static size_t const generalPurposeMemoryStart; private: - /// Address of the precompiled identity contract. - static const unsigned identityContractAddress; - /// Appends code that cleans higher-order bits for integer types. void cleanHigherOrderBits(IntegerType const& _typeOnStack); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index a73065430..e9ebdd592 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -20,25 +20,29 @@ * Solidity compiler. */ -#include -#include #include -#include -#include -#include +#include #include +#include #include +#include +#include + +#include #include #include #include -#include +#include +#include #include using namespace std; using namespace dev; +using namespace langutil; +using namespace dev::eth; using namespace dev::solidity; namespace @@ -52,7 +56,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; @@ -62,7 +72,7 @@ class StackHeightChecker void ContractCompiler::compileContract( ContractDefinition const& _contract, - std::map const& _contracts + map> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -72,7 +82,7 @@ void ContractCompiler::compileContract( // This has to be the first code in the contract. appendDelegatecallCheck(); - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); // This generates the dispatch function for externally visible functions // and adds the function to the compilation queue. Additionally internal functions, // which are referenced directly or indirectly will be added. @@ -84,7 +94,7 @@ void ContractCompiler::compileContract( size_t ContractCompiler::compileConstructor( ContractDefinition const& _contract, - std::map const& _contracts + std::map> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -92,18 +102,18 @@ size_t ContractCompiler::compileConstructor( return deployLibrary(_contract); else { - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); return packIntoContractCreator(_contract); } } void ContractCompiler::initializeContext( ContractDefinition const& _contract, - map const& _compiledContracts + map> const& _otherCompilers ) { m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); - m_context.setCompiledContracts(_compiledContracts); + m_context.setOtherCompilers(_otherCompilers); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); CompilerUtils(m_context).initialiseFreeMemoryPointer(); registerStateVariables(_contract); @@ -178,6 +188,7 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract) solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); m_context.pushSubroutineSize(m_context.runtimeSub()); m_context.pushSubroutineOffset(m_context.runtimeSub()); + // This code replaces the address added by appendDeployTimeAddress(). m_context.appendInlineAssembly(R"( { // If code starts at 11, an mstore(0) writes to the full PUSH20 plus data @@ -185,8 +196,7 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract) let codepos := 11 codecopy(codepos, subOffset, subSize) // Check that the first opcode is a PUSH20 - switch eq(0x73, byte(0, mload(codepos))) - case 0 { invalid() } + if iszero(eq(0x73, byte(0, mload(codepos)))) { invalid() } mstore(0, address()) mstore8(codepos, 0x73) return(codepos, subSize) @@ -227,34 +237,21 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program if (!_constructor.parameters().empty()) { - unsigned argumentSize = 0; - for (ASTPointer const& var: _constructor.parameters()) - if (var->annotation().type->isDynamicallySized()) - { - argumentSize = 0; - break; - } - else - argumentSize += var->annotation().type->calldataEncodedSize(); - CompilerUtils(m_context).fetchFreeMemoryPointer(); - if (argumentSize == 0) - { - // argument size is dynamic, use CODESIZE to determine it - m_context.appendProgramSize(); // program itself - // CODESIZE is program plus manually added arguments - m_context << Instruction::CODESIZE << Instruction::SUB; - } - else - m_context << u256(argumentSize); + // CODESIZE returns the actual size of the code, + // which is the size of the generated code (``programSize``) + // plus the constructor arguments added to the transaction payload. + m_context.appendProgramSize(); + m_context << Instruction::CODESIZE << Instruction::SUB; // stack: m_context << Instruction::DUP1; m_context.appendProgramSize(); m_context << Instruction::DUP4 << Instruction::CODECOPY; - m_context << Instruction::DUP2 << Instruction::ADD; - m_context << Instruction::DUP1; + // stack: + m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD; + // stack: CompilerUtils(m_context).storeFreeMemoryPointer(); - // stack: + // stack: CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true); } _constructor.accept(*this); @@ -270,10 +267,93 @@ void ContractCompiler::appendDelegatecallCheck() // "We have not been called via DELEGATECALL". } +void ContractCompiler::appendInternalSelector( + map, eth::AssemblyItem const> const& _entryPoints, + vector> const& _ids, + eth::AssemblyItem const& _notFoundTag, + size_t _runs +) +{ + // Code for selecting from n functions without split: + // n times: dup1, push4 , eq, push2/3 , jumpi + // push2/3 jump + // (called SELECT[n]) + // Code for selecting from n functions with split: + // dup1, push4 , gt, push2/3, jumpi + // SELECT[n/2] + // tag_less: + // SELECT[n/2] + // + // This means each split adds 16-18 bytes of additional code (note the additional jump out!) + // The average execution cost if we do not split at all are: + // (3 + 3 + 3 + 3 + 10) * n/2 = 24 * n/2 = 12 * n + // If we split once: + // (3 + 3 + 3 + 3 + 10) + 24 * n/4 = 24 * (n/4 + 1) = 6 * n + 24; + // + // We should split if + // _runs * 12 * n > _runs * (6 * n + 24) + 17 * createDataGas + // <=> _runs * 6 * (n - 4) > 17 * createDataGas + // + // Which also means that the execution itself is not profitable + // unless we have at least 5 functions. + + // Start with some comparisons to avoid overflow, then do the actual comparison. + bool split = false; + if (_ids.size() <= 4) + split = false; + else if (_runs > (17 * eth::GasCosts::createDataGas) / 6) + split = true; + else + split = (_runs * 6 * (_ids.size() - 4) > 17 * eth::GasCosts::createDataGas); + + if (split) + { + size_t pivotIndex = _ids.size() / 2; + FixedHash<4> pivot{_ids.at(pivotIndex)}; + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; + eth::AssemblyItem lessTag{m_context.appendConditionalJump()}; + // Here, we have funid >= pivot + vector> larger{_ids.begin() + pivotIndex, _ids.end()}; + appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs); + m_context << lessTag; + // Here, we have funid < pivot + vector> smaller{_ids.begin(), _ids.begin() + pivotIndex}; + appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs); + } + else + { + for (auto const& id: _ids) + { + m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; + m_context.appendConditionalJumpTo(_entryPoints.at(id)); + } + m_context.appendJumpTo(_notFoundTag); + } +} + +namespace +{ + +// Helper function to check if any function is payable +bool hasPayableFunctions(ContractDefinition const& _contract) +{ + FunctionDefinition const* fallback = _contract.fallbackFunction(); + if (fallback && fallback->isPayable()) + return true; + + for (auto const& it: _contract.interfaceFunctions()) + if (it.second->isPayable()) + return true; + + return false; +} + +} + void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract) { map, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions(); - map, const eth::AssemblyItem> callDataUnpackerEntryPoints; + map, eth::AssemblyItem const> callDataUnpackerEntryPoints; if (_contract.isLibrary()) { @@ -281,6 +361,15 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac } FunctionDefinition const* fallback = _contract.fallbackFunction(); + solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions"); + + bool needToAddCallvalueCheck = true; + if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) + { + appendCallValueCheck(); + needToAddCallvalueCheck = false; + } + eth::AssemblyItem notFound = m_context.newTag(); // directly jump to fallback if the data is too short to contain a function selector // also guards against short data @@ -289,22 +378,26 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // retrieve the function signature hash from the calldata if (!interfaceFunctions.empty()) + { CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); - // stack now is: ? - for (auto const& it: interfaceFunctions) - { - callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << Instruction::EQ; - m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); + // stack now is: ? + vector> sortedIDs; + for (auto const& it: interfaceFunctions) + { + callDataUnpackerEntryPoints.emplace(it.first, m_context.newTag()); + sortedIDs.emplace_back(it.first); + } + std::sort(sortedIDs.begin(), sortedIDs.end()); + appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); } - m_context.appendJumpTo(notFound); m_context << notFound; + if (fallback) { solAssert(!_contract.isLibrary(), ""); - if (!fallback->isPayable()) + if (!fallback->isPayable() && needToAddCallvalueCheck) appendCallValueCheck(); solAssert(fallback->isFallback(), ""); @@ -334,7 +427,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. - if (!functionType->isPayable() && !_contract.isLibrary()) + if (!functionType->isPayable() && !_contract.isLibrary() && needToAddCallvalueCheck) appendCallValueCheck(); // Return tag is used to jump out of the function. @@ -346,7 +439,10 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB; CompilerUtils(m_context).abiDecode(functionType->parameterTypes()); } - m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); + m_context.appendJumpTo( + m_context.functionEntryLabel(functionType->declaration()), + eth::AssemblyItem::JumpType::IntoFunction + ); m_context << returnTag; // Return tag and input parameters get consumed. m_context.adjustStackOffset( @@ -517,7 +613,7 @@ void ContractCompiler::initializeStateVariables(ContractDefinition const& _contr solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); for (VariableDeclaration const* variable: _contract.stateVariables()) if (variable->value() && !variable->isConstant()) - ExpressionCompiler(m_context, m_optimise).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); } bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) @@ -530,9 +626,9 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_optimise).appendConstStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_optimise).appendStateVariableAccessor(_variableDeclaration); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableAccessor(_variableDeclaration); return false; } @@ -632,14 +728,14 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { unsigned startStackHeight = m_context.stackHeight(); yul::ExternalIdentifierAccess identifierAccess; - identifierAccess.resolve = [&](assembly::Identifier const& _identifier, yul::IdentifierContext, bool) + identifierAccess.resolve = [&](yul::Identifier const& _identifier, yul::IdentifierContext, bool) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); if (ref == _inlineAssembly.annotation().externalReferences.end()) return size_t(-1); return ref->second.valueSize; }; - identifierAccess.generateCode = [&](assembly::Identifier const& _identifier, yul::IdentifierContext _context, yul::AbstractAssembly& _assembly) + identifierAccess.generateCode = [&](yul::Identifier const& _identifier, yul::IdentifierContext _context, yul::AbstractAssembly& _assembly) { auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); @@ -670,8 +766,46 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) } else if (auto variable = dynamic_cast(decl)) { - solAssert(!variable->isConstant(), ""); - if (m_context.isStateVariable(decl)) + if (variable->isConstant()) + { + u256 value; + if (variable->value()->annotation().type->category() == Type::Category::RationalNumber) + { + value = dynamic_cast(*variable->value()->annotation().type).literalValue(nullptr); + if (FixedBytesType const* bytesType = dynamic_cast(variable->type())) + value = value << (256 - 8 * bytesType->numBytes()); + else + solAssert(variable->type()->category() == Type::Category::Integer, ""); + } + else if (Literal const* literal = dynamic_cast(variable->value().get())) + { + TypePointer type = literal->annotation().type; + + switch (type->category()) + { + case Type::Category::Bool: + case Type::Category::Address: + solAssert(*type == *variable->annotation().type, ""); + value = type->literalValue(literal); + break; + case Type::Category::StringLiteral: + { + StringLiteralType const& stringLiteral = dynamic_cast(*type); + solAssert(variable->type()->category() == Type::Category::FixedBytes, ""); + unsigned const numBytes = dynamic_cast(*variable->type()).numBytes(); + solAssert(stringLiteral.value().size() <= numBytes, ""); + value = u256(h256(stringLiteral.value(), h256::AlignLeft)); + break; + } + default: + solAssert(false, ""); + } + } + else + solAssert(false, "Invalid constant in inline assembly."); + m_context << value; + } + else if (m_context.isStateVariable(decl)) { auto const& location = m_context.storageLocationOfVariable(*decl); if (ref->second.isSlot) @@ -751,11 +885,14 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) } }; solAssert(_inlineAssembly.annotation().analysisInfo, ""); - assembly::CodeGenerator::assemble( + yul::CodeGenerator::assemble( _inlineAssembly.operations(), *_inlineAssembly.annotation().analysisInfo, - m_context.nonConstAssembly(), - identifierAccess + *m_context.assemblyPtr(), + m_context.evmVersion(), + identifierAccess, + false, + m_optimiserSettings.optimizeStackAllocation ); m_context.setStackOffset(startStackHeight); return false; @@ -789,14 +926,14 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); - m_breakTags.push_back({loopEnd, m_context.stackHeight()}); + m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); m_context << loopStart; if (_whileStatement.isDoWhile()) { eth::AssemblyItem condition = m_context.newTag(); - m_continueTags.push_back({condition, m_context.stackHeight()}); + m_continueTags.emplace_back(condition, m_context.stackHeight()); _whileStatement.body().accept(*this); @@ -807,7 +944,7 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) } else { - m_continueTags.push_back({loopStart, m_context.stackHeight()}); + m_continueTags.emplace_back(loopStart, m_context.stackHeight()); compileExpression(_whileStatement.condition()); m_context << Instruction::ISZERO; m_context.appendConditionalJumpTo(loopEnd); @@ -838,8 +975,8 @@ bool ContractCompiler::visit(ForStatement const& _forStatement) if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); - m_breakTags.push_back({loopEnd, m_context.stackHeight()}); - m_continueTags.push_back({loopNext, m_context.stackHeight()}); + m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); + m_continueTags.emplace_back(loopNext, m_context.stackHeight()); m_context << loopStart; // if there is no terminating condition in for, default is to always be true @@ -910,7 +1047,7 @@ bool ContractCompiler::visit(Return const& _return) TypePointer expectedType; if (expression->annotation().type->category() == Type::Category::Tuple || types.size() != 1) - expectedType = make_shared(types); + expectedType = TypeProvider::tuple(move(types)); else expectedType = types.front(); compileExpression(*expression, expectedType); @@ -944,9 +1081,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()) @@ -954,7 +1091,7 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar CompilerUtils utils(m_context); compileExpression(*expression); TypePointers valueTypes; - if (auto tupleType = dynamic_cast(expression->annotation().type.get())) + if (auto tupleType = dynamic_cast(expression->annotation().type)) valueTypes = tupleType->components(); else valueTypes = TypePointers{expression->annotation().type}; @@ -1020,7 +1157,13 @@ void ContractCompiler::appendMissingFunctions() m_context.appendMissingLowLevelFunctions(); auto abiFunctions = m_context.abiFunctions().requestedFunctions(); if (!abiFunctions.first.empty()) - m_context.appendInlineAssembly("{" + move(abiFunctions.first) + "}", {}, abiFunctions.second, true); + m_context.appendInlineAssembly( + "{" + move(abiFunctions.first) + "}", + {}, + abiFunctions.second, + true, + m_optimiserSettings + ); } void ContractCompiler::appendModifierOrFunctionCode() @@ -1072,7 +1215,7 @@ void ContractCompiler::appendModifierOrFunctionCode() if (codeBlock) { - m_returnTags.push_back({m_context.newTag(), m_context.stackHeight()}); + m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight()); codeBlock->accept(*this); solAssert(!m_returnTags.empty(), ""); @@ -1095,7 +1238,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler expressionCompiler(m_context, m_optimise); + ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); expressionCompiler.compile(_expression); if (_targetType) CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 4a06450a3..8d9ab7286 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -22,14 +22,17 @@ #pragma once -#include -#include #include #include #include #include -namespace dev { -namespace solidity { +#include +#include + +namespace dev +{ +namespace solidity +{ /** * Code generator at the contract level. Can be used to generate code for exactly one contract @@ -38,23 +41,26 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): - m_optimise(_optimise), + explicit ContractCompiler( + ContractCompiler* _runtimeCompiler, + CompilerContext& _context, + OptimiserSettings _optimiserSettings + ): + m_optimiserSettings(std::move(_optimiserSettings)), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_context.evmVersion(), _runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( ContractDefinition const& _contract, - std::map const& _contracts + std::map> const& _otherCompilers ); /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( ContractDefinition const& _contract, - std::map const& _contracts + std::map> const& _otherCompilers ); private: @@ -62,7 +68,7 @@ class ContractCompiler: private ASTConstVisitor /// information about the contract like the AST annotations. void initializeContext( ContractDefinition const& _contract, - std::map const& _compiledContracts + std::map> const& _otherCompilers ); /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. @@ -81,6 +87,14 @@ class ContractCompiler: private ASTConstVisitor /// This is done by inserting a specific push constant as the first instruction /// whose data will be modified in memory at deploy time. void appendDelegatecallCheck(); + /// Appends the function selector. Is called recursively to create a binary search tree. + /// @a _runs the number of intended executions of the contract to tune the split point. + void appendInternalSelector( + std::map, eth::AssemblyItem const> const& _entryPoints, + std::vector> const& _ids, + eth::AssemblyItem const& _notFoundTag, + size_t _runs + ); void appendFunctionSelector(ContractDefinition const& _contract); void appendCallValueCheck(); void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); @@ -88,23 +102,23 @@ class ContractCompiler: private ASTConstVisitor void registerStateVariables(ContractDefinition const& _contract); void initializeStateVariables(ContractDefinition const& _contract); - virtual bool visit(VariableDeclaration const& _variableDeclaration) override; - virtual bool visit(FunctionDefinition const& _function) override; - virtual bool visit(InlineAssembly const& _inlineAssembly) override; - virtual bool visit(IfStatement const& _ifStatement) override; - virtual bool visit(WhileStatement const& _whileStatement) override; - virtual bool visit(ForStatement const& _forStatement) override; - virtual bool visit(FireAllRulesStatement const& _fars) override; - virtual bool visit(Continue const& _continueStatement) override; - virtual bool visit(Break const& _breakStatement) override; - virtual bool visit(Return const& _return) override; - virtual bool visit(Throw const& _throw) override; - virtual bool visit(EmitStatement const& _emit) override; - virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; - virtual bool visit(ExpressionStatement const& _expressionStatement) override; - virtual bool visit(PlaceholderStatement const&) override; - virtual bool visit(Block const& _block) override; - virtual void endVisit(Block const& _block) override; + bool visit(VariableDeclaration const& _variableDeclaration) override; + bool visit(FunctionDefinition const& _function) override; + bool visit(InlineAssembly const& _inlineAssembly) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(WhileStatement const& _whileStatement) override; + bool visit(FireAllRulesStatement const& _fars) override; + bool visit(ForStatement const& _forStatement) override; + bool visit(Continue const& _continueStatement) override; + bool visit(Break const& _breakStatement) override; + bool visit(Return const& _return) override; + bool visit(Throw const& _throw) override; + bool visit(EmitStatement const& _emit) override; + bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; + bool visit(ExpressionStatement const& _expressionStatement) override; + bool visit(PlaceholderStatement const&) override; + bool visit(Block const& _block) override; + void endVisit(Block const& _block) override; /// Repeatedly visits all function which are referenced but which are not compiled yet. void appendMissingFunctions(); @@ -123,7 +137,7 @@ class ContractCompiler: private ASTConstVisitor /// Sets the stack height for the visited loop. void storeStackHeight(ASTNode const* _node); - bool const m_optimise; + OptimiserSettings const m_optimiserSettings; /// Pointer to the runtime compiler in case this is a creation compiler. ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index facd376a4..7d968edd8 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -20,29 +20,32 @@ * Solidity AST to EVM bytecode compiler for expressions. */ -#include -#include -#include -#include -#include -#include -#include #include + +#include +#include #include #include #include #include #include -#include +#include +#include +#include #include +#include +#include +#include +#include + using namespace std; +using namespace langutil; +using namespace dev; +using namespace dev::eth; +using namespace dev::solidity; -namespace dev -{ -namespace solidity -{ void ExpressionCompiler::compile(Expression const& _expression) { @@ -77,8 +80,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl) { solAssert(_varDecl.isConstant(), ""); - _varDecl.value()->accept(*this); - utils().convertType(*_varDecl.value()->annotation().type, *_varDecl.annotation().type); + acceptAndConvert(*_varDecl.value(), *_varDecl.annotation().type); // append return m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1); @@ -102,26 +104,57 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& for (size_t i = 0; i < paramTypes.size(); ++i) { - if (auto mappingType = dynamic_cast(returnType.get())) + if (auto mappingType = dynamic_cast(returnType)) { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - solUnimplementedAssert( - !paramTypes[i]->isDynamicallySized(), - "Accessors for mapping with dynamically-sized keys not yet implemented." - ); + // pop offset m_context << Instruction::POP; - // move storage offset to memory. - utils().storeInMemory(32); - // move key to memory. - utils().copyToStackTop(paramTypes.size() - i, 1); - utils().storeInMemory(0); - m_context << u256(64) << u256(0) << Instruction::KECCAK256; + if (paramTypes[i]->isDynamicallySized()) + { + solAssert( + dynamic_cast(*paramTypes[i]).isByteArray(), + "Expected string or byte array for mapping key type" + ); + + // stack: + + // copy key[i] to top. + utils().copyToStackTop(paramTypes.size() - i + 1, 1); + + m_context.appendInlineAssembly(R"({ + let key_len := mload(key_ptr) + // Temp. use the memory after the array data for the slot + // position + let post_data_ptr := add(key_ptr, add(key_len, 0x20)) + let orig_data := mload(post_data_ptr) + mstore(post_data_ptr, slot_pos) + let hash := keccak256(add(key_ptr, 0x20), add(key_len, 0x20)) + mstore(post_data_ptr, orig_data) + slot_pos := hash + })", {"slot_pos", "key_ptr"}); + + m_context << Instruction::POP; + } + else + { + solAssert(paramTypes[i]->isValueType(), "Expected value type for mapping key"); + + // move storage offset to memory. + utils().storeInMemory(32); + + // move key to memory. + utils().copyToStackTop(paramTypes.size() - i, 1); + utils().storeInMemory(0); + m_context << u256(64) << u256(0); + m_context << Instruction::KECCAK256; + } + // push offset m_context << u256(0); returnType = mappingType->valueType(); } - else if (auto arrayType = dynamic_cast(returnType.get())) + else if (auto arrayType = dynamic_cast(returnType)) { // pop offset m_context << Instruction::POP; @@ -145,7 +178,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& unsigned retSizeOnStack = 0; auto returnTypes = accessorType.returnParameterTypes(); solAssert(returnTypes.size() >= 1, ""); - if (StructType const* structType = dynamic_cast(returnType.get())) + if (StructType const* structType = dynamic_cast(returnType)) { // remove offset m_context << Instruction::POP; @@ -155,7 +188,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { if (returnTypes[i]->category() == Type::Category::Mapping) continue; - if (auto arrayType = dynamic_cast(returnTypes[i].get())) + if (auto arrayType = dynamic_cast(returnTypes[i])) if (!arrayType->isByteArray()) continue; pair const& offsets = structType->storageOffsetsOfMember(names[i]); @@ -193,14 +226,12 @@ bool ExpressionCompiler::visit(Conditional const& _condition) CompilerContext::LocationSetter locationSetter(m_context, _condition); _condition.condition().accept(*this); eth::AssemblyItem trueTag = m_context.appendConditionalJump(); - _condition.falseExpression().accept(*this); - utils().convertType(*_condition.falseExpression().annotation().type, *_condition.annotation().type); + acceptAndConvert(_condition.falseExpression(), *_condition.annotation().type); eth::AssemblyItem endTag = m_context.appendJumpToNew(); m_context << trueTag; int offset = _condition.annotation().type->sizeOnStack(); m_context.adjustStackOffset(-offset); - _condition.trueExpression().accept(*this); - utils().convertType(*_condition.trueExpression().annotation().type, *_condition.annotation().type); + acceptAndConvert(_condition.trueExpression(), *_condition.annotation().type); m_context << endTag; return false; } @@ -285,14 +316,12 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple) ArrayType const& arrayType = dynamic_cast(*_tuple.annotation().type); solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); - m_context << max(u256(32u), arrayType.memorySize()); - utils().allocateMemory(); + utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize())); m_context << Instruction::DUP1; for (auto const& component: _tuple.components()) { - component->accept(*this); - utils().convertType(*component->annotation().type, *arrayType.baseType(), true); + acceptAndConvert(*component, *arrayType.baseType(), true); utils().storeInMemoryDynamic(*arrayType.baseType(), true); } @@ -437,20 +466,16 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) { return dynamic_cast(&_e) || _e.annotation().type->category() == Type::Category::RationalNumber; }; - bool swap = m_optimize && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); + bool swap = m_optimiseOrderLiterals && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { - leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); - rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); + acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); + acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); } else { - rightExpression.accept(*this); - utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded); - leftExpression.accept(*this); - utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded); + acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); + acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); } if (TokenTraits::isShiftOp(c_op)) // shift only cares about the signedness of both sides @@ -472,9 +497,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { solAssert(_functionCall.arguments().size() == 1, ""); solAssert(_functionCall.names().empty(), ""); - Expression const& firstArgument = *_functionCall.arguments().front(); - firstArgument.accept(*this); - utils().convertType(*firstArgument.annotation().type, *_functionCall.annotation().type); + acceptAndConvert(*_functionCall.arguments().front(), *_functionCall.annotation().type); return false; } @@ -486,7 +509,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) functionType = structType.constructorType(); } else - functionType = dynamic_pointer_cast(_functionCall.expression().annotation().type); + functionType = dynamic_cast(_functionCall.expression().annotation().type); TypePointers parameterTypes = functionType->parameterTypes(); vector> const& callArguments = _functionCall.arguments(); @@ -515,14 +538,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type); auto const& structType = dynamic_cast(*type.actualType()); - m_context << max(u256(32u), structType.memorySize()); - utils().allocateMemory(); + utils().allocateMemory(max(u256(32u), structType.memoryDataSize())); m_context << Instruction::DUP1; for (unsigned i = 0; i < arguments.size(); ++i) { - arguments[i]->accept(*this); - utils().convertType(*arguments[i]->annotation().type, *functionType->parameterTypes()[i]); + acceptAndConvert(*arguments[i], *functionType->parameterTypes()[i]); utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]); } m_context << Instruction::POP; @@ -542,14 +563,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) eth::AssemblyItem returnLabel = m_context.pushNewTag(); for (unsigned i = 0; i < arguments.size(); ++i) - { - arguments[i]->accept(*this); - utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]); - } + acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); { bool shortcutTaken = false; if (auto identifier = dynamic_cast(&_functionCall.expression())) + { + solAssert(!function.bound(), ""); if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) { // Do not directly visit the identifier, because this way, we can avoid @@ -558,6 +578,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false); shortcutTaken = true; } + } if (!shortcutTaken) _functionCall.expression().accept(*this); @@ -610,22 +631,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } ContractDefinition const* contract = &dynamic_cast(*function.returnParameterTypes().front()).contractDefinition(); - m_context.callLowLevelFunction( - "$copyContractCreationCodeToMemory_" + contract->type()->identifier(), - 0, - 1, - [contract](CompilerContext& _context) - { - // copy the contract's code into memory - eth::Assembly const& assembly = _context.compiledContract(*contract); - CompilerUtils(_context).fetchFreeMemoryPointer(); - // pushes size - auto subroutine = _context.addSubroutine(make_shared(assembly)); - _context << Instruction::DUP1 << subroutine; - _context << Instruction::DUP4 << Instruction::CODECOPY; - _context << Instruction::ADD; - } - ); + utils().fetchFreeMemoryPointer(); + utils().copyContractCodeToMemory(*contract, true); utils().abiEncode(argumentTypes, function.parameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment @@ -648,8 +655,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack layout: contract_address function_id [gas] [value] _functionCall.expression().accept(*this); - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, IntegerType(256), true); + acceptAndConvert(*arguments.front(), *TypeProvider::uint256(), true); // Note that function is not the original function, but the ".gas" function. // Its values of gasSet and valueSet is equal to the original function's though. unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); @@ -674,11 +680,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Provide the gas stipend manually at first because we may send zero ether. // Will be zeroed if we send more than zero ether. m_context << u256(eth::GasCosts::callStipend); - arguments.front()->accept(*this); - utils().convertType( - *arguments.front()->annotation().type, - *function.parameterTypes().front(), true - ); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); // gas <- gas * !value m_context << Instruction::SWAP1 << Instruction::DUP2; m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; @@ -707,8 +709,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } break; case FunctionType::Kind::Selfdestruct: - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; case FunctionType::Kind::Revert: @@ -734,9 +735,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); // Optimization: If type is bytes or string, then do not encode, // but directly compute keccak256 on memory. - if (*argType == ArrayType::bytesMemory() || *argType == ArrayType::stringMemory()) + if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory()) { - ArrayUtils(m_context).retrieveLength(ArrayType::bytesMemory()); + ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory()); m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD; } else @@ -756,12 +757,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0); for (unsigned arg = logNumber; arg > 0; --arg) - { - arguments[arg]->accept(*this); - utils().convertType(*arguments[arg]->annotation().type, *function.parameterTypes()[arg], true); - } + acceptAndConvert(*arguments[arg], *function.parameterTypes()[arg], true); arguments.front()->accept(*this); utils().fetchFreeMemoryPointer(); + solAssert(function.parameterTypes().front()->isValueType(), ""); utils().packedEncode( {arguments.front()->annotation().type}, {function.parameterTypes().front()} @@ -775,28 +774,32 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) _functionCall.expression().accept(*this); auto const& event = dynamic_cast(function.declaration()); unsigned numIndexed = 0; + TypePointers paramTypes = function.parameterTypes(); // All indexed arguments go to the stack for (unsigned arg = arguments.size(); arg > 0; --arg) if (event.parameters()[arg - 1]->isIndexed()) { ++numIndexed; arguments[arg - 1]->accept(*this); - if (auto const& arrayType = dynamic_pointer_cast(function.parameterTypes()[arg - 1])) + if (auto const& referenceType = dynamic_cast(paramTypes[arg - 1])) { utils().fetchFreeMemoryPointer(); utils().packedEncode( {arguments[arg - 1]->annotation().type}, - {arrayType} + {referenceType} ); utils().toSizeAfterFreeMemoryPointer(); m_context << Instruction::KECCAK256; } else + { + solAssert(paramTypes[arg - 1]->isValueType(), ""); utils().convertType( *arguments[arg - 1]->annotation().type, - *function.parameterTypes()[arg - 1], + *paramTypes[arg - 1], true ); + } } if (!event.isAnonymous()) { @@ -813,7 +816,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { arguments[arg]->accept(*this); nonIndexedArgTypes.push_back(arguments[arg]->annotation().type); - nonIndexedParamTypes.push_back(function.parameterTypes()[arg]); + nonIndexedParamTypes.push_back(paramTypes[arg]); } utils().fetchFreeMemoryPointer(); utils().abiEncode(nonIndexedArgTypes, nonIndexedParamTypes); @@ -824,23 +827,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } case FunctionType::Kind::BlockHash: { - arguments[0]->accept(*this); - utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true); + acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true); m_context << Instruction::BLOCKHASH; break; } case FunctionType::Kind::AddMod: case FunctionType::Kind::MulMod: { - arguments[2]->accept(*this); - utils().convertType(*arguments[2]->annotation().type, IntegerType(256)); + acceptAndConvert(*arguments[2], *TypeProvider::uint256()); m_context << Instruction::DUP1 << Instruction::ISZERO; m_context.appendConditionalInvalid(); for (unsigned i = 1; i < 3; i ++) - { - arguments[2 - i]->accept(*this); - utils().convertType(*arguments[2 - i]->annotation().type, IntegerType(256)); - } + acceptAndConvert(*arguments[2 - i], *TypeProvider::uint256()); if (function.kind() == FunctionType::Kind::AddMod) m_context << Instruction::ADDMOD; else @@ -852,10 +850,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::RIPEMD160: { _functionCall.expression().accept(*this); - static const map contractAddresses{{FunctionType::Kind::ECRecover, 1}, - {FunctionType::Kind::SHA256, 2}, - {FunctionType::Kind::RIPEMD160, 3}}; - m_context << contractAddresses.find(function.kind())->second; + static map const contractAddresses{ + {FunctionType::Kind::ECRecover, 1}, + {FunctionType::Kind::SHA256, 2}, + {FunctionType::Kind::RIPEMD160, 3} + }; + m_context << contractAddresses.at(function.kind()); for (unsigned i = function.sizeOnStack(); i > 0; --i) m_context << swapInstruction(i); appendExternalFunctionCall(function, arguments); @@ -868,10 +868,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(function.parameterTypes().size() == 1, ""); solAssert(!!function.parameterTypes()[0], ""); TypePointer paramType = function.parameterTypes()[0]; - shared_ptr arrayType = + ArrayType const* arrayType = function.kind() == FunctionType::Kind::ArrayPush ? - make_shared(DataLocation::Storage, paramType) : - make_shared(DataLocation::Storage); + TypeProvider::array(DataLocation::Storage, paramType) : + TypeProvider::array(DataLocation::Storage); // stack: ArrayReference arguments[0]->accept(*this); @@ -922,8 +922,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) solAssert(arguments.size() == 1, ""); // Fetch requested length. - arguments[0]->accept(*this); - utils().convertType(*arguments[0]->annotation().type, IntegerType(256)); + acceptAndConvert(*arguments[0], *TypeProvider::uint256()); // Stack: requested_length utils().fetchFreeMemoryPointer(); @@ -962,8 +961,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Assert: case FunctionType::Kind::Require: { - arguments.front()->accept(*this); - utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); + acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); if (arguments.size() > 1) { // Users probably expect the second argument to be evaluated @@ -1053,11 +1051,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature) { // hash the signature - if (auto const* stringType = dynamic_cast(selectorType.get())) + if (auto const* stringType = dynamic_cast(selectorType)) { FixedHash<4> hash(dev::keccak256(stringType->value())); m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32)); - dataOnStack = make_shared(4); + dataOnStack = TypeProvider::fixedBytes(4); } else { @@ -1068,7 +1066,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << Instruction::KECCAK256; // stack: - dataOnStack = make_shared(32); + dataOnStack = TypeProvider::fixedBytes(32); } } else @@ -1099,7 +1097,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) arguments.front()->accept(*this); TypePointer firstArgType = arguments.front()->annotation().type; TypePointers targetTypes; - if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type.get())) + if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type)) targetTypes = targetTupleType->components(); else targetTypes = TypePointers{_functionCall.annotation().type}; @@ -1110,7 +1108,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().abiDecode(targetTypes, false); else { - utils().convertType(*firstArgType, ArrayType::bytesMemory()); + utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; m_context << Instruction::SWAP1 << Instruction::MLOAD; // stack now: @@ -1151,6 +1149,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context.eniHandler().clearENIObjects(); break; } + case FunctionType::Kind::MetaType: + // No code to generate. + break; default: solAssert(false, "Invalid function type."); } @@ -1169,15 +1170,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); // Check whether the member is a bound function. ASTString const& member = _memberAccess.memberName(); - if (auto funType = dynamic_cast(_memberAccess.annotation().type.get())) + if (auto funType = dynamic_cast(_memberAccess.annotation().type)) if (funType->bound()) { - _memberAccess.expression().accept(*this); - utils().convertType( - *_memberAccess.expression().annotation().type, - *funType->selfType(), - true - ); + acceptAndConvert(_memberAccess.expression(), *funType->selfType(), true); if (funType->kind() == FunctionType::Kind::Internal) { FunctionDefinition const& funDef = dynamic_cast(funType->declaration()); @@ -1198,12 +1194,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // Special processing for TypeType because we do not want to visit the library itself // for internal functions, or enum/struct definitions. - if (TypeType const* type = dynamic_cast(_memberAccess.expression().annotation().type.get())) + if (TypeType const* type = dynamic_cast(_memberAccess.expression().annotation().type)) { - if (dynamic_cast(type->actualType().get())) + if (dynamic_cast(type->actualType())) { solAssert(_memberAccess.annotation().type, "_memberAccess has no type"); - if (auto funType = dynamic_cast(_memberAccess.annotation().type.get())) + if (auto variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) + appendVariable(*variable, static_cast(_memberAccess)); + else if (auto funType = dynamic_cast(_memberAccess.annotation().type)) { switch (funType->kind()) { @@ -1245,16 +1243,14 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "unsupported member function"); } } - else if (dynamic_cast(_memberAccess.annotation().type.get())) + else if (dynamic_cast(_memberAccess.annotation().type)) { // no-op } - else if (auto variable = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) - appendVariable(*variable, static_cast(_memberAccess)); else _memberAccess.expression().accept(*this); } - else if (auto enumType = dynamic_cast(type->actualType().get())) + else if (auto enumType = dynamic_cast(type->actualType())) { _memberAccess.expression().accept(*this); m_context << enumType->memberValue(_memberAccess.memberName()); @@ -1312,7 +1308,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) identifier = FunctionType(*function).externalIdentifier(); else solAssert(false, "Contract member is neither variable nor function."); - utils().convertType(type, type.isPayable() ? AddressType::addressPayable() : AddressType::address(), true); + utils().convertType(type, type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address(), true); m_context << identifier; } else @@ -1330,7 +1326,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { utils().convertType( *_memberAccess.expression().annotation().type, - AddressType::address(), + *TypeProvider::address(), true ); m_context << Instruction::BALANCE; @@ -1347,7 +1343,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if ((set{"call", "callcode", "delegatecall", "staticcall"}).count(member)) utils().convertType( *_memberAccess.expression().annotation().type, - AddressType::address(), + *TypeProvider::address(), true ); else @@ -1362,8 +1358,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) utils().leftShiftNumberOnStack(224); } else - solAssert(!!_memberAccess.expression().annotation().type->memberType(member), - "Invalid member access to function."); + solAssert( + !!_memberAccess.expression().annotation().type->memberType(member), + "Invalid member access to function." + ); break; case Type::Category::Magic: // we can ignore the kind of magic and only look at the name of the member @@ -1394,12 +1392,45 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "Gas has been removed."); else if (member == "blockhash") solAssert(false, "Blockhash has been removed."); + else if (member == "creationCode" || member == "runtimeCode") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + utils().fetchFreeMemoryPointer(); + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + utils().copyContractCodeToMemory(contract, member == "creationCode"); + // Stack: start end + m_context.appendInlineAssembly( + Whiskers(R"({ + mstore(start, sub(end, add(start, 0x20))) + mstore(, and(add(end, 31), not(31))) + })")("free", to_string(CompilerUtils::freeMemoryPointer)).render(), + {"start", "end"} + ); + m_context << Instruction::POP; + } + else if (member == "name") + { + TypePointer arg = dynamic_cast(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast(*arg).contractDefinition(); + utils().allocateMemory(((contract.name().length() + 31) / 32) * 32 + 32); + // store string length + m_context << u256(contract.name().length()) << Instruction::DUP2 << Instruction::MSTORE; + // adjust pointer + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + utils().storeStringData(contract.name()); + } + else if ((set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}).count(member)) + { + // no-op + } else solAssert(false, "Unknown magic member."); break; case Type::Category::Struct: { StructType const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + TypePointer const& memberType = _memberAccess.annotation().type; switch (type.location()) { case DataLocation::Storage: @@ -1412,7 +1443,40 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) case DataLocation::Memory: { m_context << type.memoryOffsetOfMember(member) << Instruction::ADD; - setLValue(_memberAccess, *_memberAccess.annotation().type); + setLValue(_memberAccess, *memberType); + break; + } + case DataLocation::CallData: + { + if (_memberAccess.annotation().type->isDynamicallyEncoded()) + { + m_context << Instruction::DUP1; + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + CompilerUtils(m_context).accessCalldataTail(*memberType); + } + else + { + m_context << type.calldataOffsetOfMember(member) << Instruction::ADD; + // For non-value types the calldata offset is returned directly. + if (memberType->isValueType()) + { + solAssert(memberType->calldataEncodedSize() > 0, ""); + solAssert(memberType->storageBytes() <= 32, ""); + if (memberType->storageBytes() < 32 && m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) + { + m_context << u256(32); + CompilerUtils(m_context).abiDecodeV2({memberType}, false); + } + else + CompilerUtils(m_context).loadFromMemoryDynamic(*memberType, true, true, false); + } + else + solAssert( + memberType->category() == Type::Category::Array || + memberType->category() == Type::Category::Struct, + "" + ); + } break; } default: @@ -1502,7 +1566,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) TypePointers{keyType} ); m_context << Instruction::SWAP1; - utils().storeInMemoryDynamic(IntegerType(256)); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); utils().toSizeAfterFreeMemoryPointer(); } else @@ -1511,7 +1575,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) appendExpressionCopyToMemory(*keyType, *_indexAccess.indexExpression()); m_context << Instruction::SWAP1; solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - utils().storeInMemoryDynamic(IntegerType(256)); + utils().storeInMemoryDynamic(*TypeProvider::uint256()); m_context << u256(0); } m_context << Instruction::KECCAK256; @@ -1523,13 +1587,12 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) ArrayType const& arrayType = dynamic_cast(baseType); solAssert(_indexAccess.indexExpression(), "Index expression expected."); - _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType(256), true); + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); // stack layout: [] - ArrayUtils(m_context).accessIndex(arrayType); switch (arrayType.location()) { case DataLocation::Storage: + ArrayUtils(m_context).accessIndex(arrayType); if (arrayType.isByteArray()) { solAssert(!arrayType.isString(), "Index access to string is not allowed."); @@ -1539,18 +1602,11 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) setLValueToStorageItem(_indexAccess); break; case DataLocation::Memory: + ArrayUtils(m_context).accessIndex(arrayType); setLValue(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray()); break; case DataLocation::CallData: - //@todo if we implement this, the value in calldata has to be added to the base offset - solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); - if (arrayType.baseType()->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic( - *arrayType.baseType(), - true, - !arrayType.isByteArray(), - false - ); + ArrayUtils(m_context).accessCallDataArrayElement(arrayType); break; } } @@ -1559,8 +1615,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) FixedBytesType const& fixedBytesType = dynamic_cast(baseType); solAssert(_indexAccess.indexExpression(), "Index expression expected."); - _indexAccess.indexExpression()->accept(*this); - utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType(256), true); + acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true); // stack layout: // check out-of-bounds access m_context << u256(fixedBytesType.numBytes()); @@ -2002,6 +2057,8 @@ void ExpressionCompiler::appendExternalFunctionCall( retSize = 0; break; } + else if (retType->decodingType()) + retSize += retType->decodingType()->calldataEncodedSize(); else retSize += retType->calldataEncodedSize(); } @@ -2059,12 +2116,18 @@ void ExpressionCompiler::appendExternalFunctionCall( // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place. // Move arguments to memory, will not update the free memory pointer (but will update the memory // pointer on the stack). + bool encodeInPlace = _functionType.takesArbitraryParameters() || _functionType.isBareCall(); + if (_functionType.kind() == FunctionType::Kind::ECRecover) + // This would be the only combination of padding and in-place encoding, + // but all parameters of ecrecover are value types anyway. + encodeInPlace = false; + bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; utils().encodeToMemory( argumentTypes, parameterTypes, _functionType.padArguments(), - _functionType.takesArbitraryParameters() || _functionType.isBareCall(), - isDelegateCall + encodeInPlace, + encodeForLibraryCall ); // Stack now: @@ -2177,7 +2240,7 @@ void ExpressionCompiler::appendExternalFunctionCall( mstore(v, returndatasize()) returndatacopy(add(v, 0x20), 0, returndatasize()) } - })", {"v"}); + })", {"v"}); } else utils().pushZeroPointer(); @@ -2211,7 +2274,7 @@ void ExpressionCompiler::appendExternalFunctionCall( needToUpdateFreeMemoryPtr = true; else for (auto const& retType: returnTypes) - if (dynamic_cast(retType.get())) + if (dynamic_cast(retType)) needToUpdateFreeMemoryPtr = true; // Stack: return_data_start @@ -2240,9 +2303,7 @@ void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types."); - /// TODO:: Expression -> Memory - _expression.accept(*this); - utils().convertType(*_expression.annotation().type, _expectedType, true); + acceptAndConvert(_expression, _expectedType, true); utils().storeInMemoryDynamic(_expectedType); } @@ -2252,10 +2313,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex setLValueFromDeclaration(_variable, _expression); } else - { - _variable.value()->accept(*this); - utils().convertType(*_variable.value()->annotation().type, *_variable.annotation().type); - } + acceptAndConvert(*_variable.value(), *_variable.annotation().type); } void ExpressionCompiler::appendFact(FactDeclaration const & _fact) @@ -2298,10 +2356,13 @@ bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op) return false; } -CompilerUtils ExpressionCompiler::utils() +void ExpressionCompiler::acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded) { - return CompilerUtils(m_context); + _expression.accept(*this); + utils().convertType(*_expression.annotation().type, _type, _cleanupNeeded); } -} +CompilerUtils ExpressionCompiler::utils() +{ + return CompilerUtils(m_context); } diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index a172c3ebc..9becd575c 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -23,14 +23,15 @@ #pragma once -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include + +#include +#include +#include namespace dev { namespace eth @@ -54,11 +55,8 @@ class ArrayType; class ExpressionCompiler: private ASTConstVisitor { public: - /// Appends code for a State Variable accessor function - static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); - - explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): - m_optimize(_optimize), m_context(_compilerContext) {} + explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimiseOrderLiterals): + m_optimiseOrderLiterals(_optimiseOrderLiterals), m_context(_compilerContext) {} /// Compile the given @a _expression and leave its value on the stack. void compile(Expression const& _expression); @@ -70,21 +68,21 @@ class ExpressionCompiler: private ASTConstVisitor void appendStateVariableAccessor(VariableDeclaration const& _varDecl); /// Appends code for a Constant State Variable accessor function - void appendConstStateVariableAccessor(const VariableDeclaration& _varDecl); + void appendConstStateVariableAccessor(VariableDeclaration const& _varDecl); private: friend class RuleEngineCompiler; - virtual bool visit(Conditional const& _condition) override; - virtual bool visit(Assignment const& _assignment) override; - virtual bool visit(TupleExpression const& _tuple) override; - virtual bool visit(UnaryOperation const& _unaryOperation) override; - virtual bool visit(BinaryOperation const& _binaryOperation) override; - virtual bool visit(FunctionCall const& _functionCall) override; - virtual bool visit(NewExpression const& _newExpression) override; - virtual bool visit(MemberAccess const& _memberAccess) override; - virtual bool visit(IndexAccess const& _indexAccess) override; - virtual void endVisit(Identifier const& _identifier) override; - virtual void endVisit(Literal const& _literal) override; + bool visit(Conditional const& _condition) override; + bool visit(Assignment const& _assignment) override; + bool visit(TupleExpression const& _tuple) override; + bool visit(UnaryOperation const& _unaryOperation) override; + bool visit(BinaryOperation const& _binaryOperation) override; + bool visit(FunctionCall const& _functionCall) override; + bool visit(NewExpression const& _newExpression) override; + bool visit(MemberAccess const& _memberAccess) override; + bool visit(IndexAccess const& _indexAccess) override; + void endVisit(Identifier const& _identifier) override; + void endVisit(Literal const& _literal) override; ///@{ ///@name Append code for various operator types @@ -127,10 +125,12 @@ class ExpressionCompiler: private ASTConstVisitor /// operation. static bool cleanupNeededForOp(Type::Category _type, Token _op); + void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false); + /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); - bool m_optimize; + bool m_optimiseOrderLiterals; CompilerContext& m_context; std::unique_ptr m_currentLValue; diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 0faa77f9b..bfa77632f 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -21,18 +21,21 @@ */ #include -#include -#include + #include +#include #include +#include using namespace std; using namespace dev; -using namespace solidity; +using namespace dev::eth; +using namespace dev::solidity; +using namespace langutil; StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): - LValue(_compilerContext, _declaration.annotation().type.get()), + LValue(_compilerContext, _declaration.annotation().type), m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), m_size(m_dataType->sizeOnStack()) { @@ -200,6 +203,12 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const CompilerUtils(m_context).splitExternalFunctionType(false); cleaned = true; } + else if (fun->kind() == FunctionType::Kind::Internal) + { + m_context << Instruction::DUP1 << Instruction::ISZERO; + CompilerUtils(m_context).pushZeroValue(*fun); + m_context << Instruction::MUL << Instruction::OR; + } } if (!cleaned) { @@ -282,7 +291,8 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc { solAssert( _sourceType.category() == m_dataType->category(), - "Wrong type conversation for assignment."); + "Wrong type conversation for assignment." + ); if (m_dataType->category() == Type::Category::Array) { m_context << Instruction::POP; // remove byte offset @@ -307,9 +317,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported."); for (auto const& member: structType.members(nullptr)) { - // assign each member that is not a mapping + // assign each member that can live outside of storage TypePointer const& memberType = member.type; - if (memberType->category() == Type::Category::Mapping) + if (!memberType->canLiveOutsideStorage()) continue; TypePointer sourceMemberType = sourceType.memberType(member.name); if (sourceType.location() == DataLocation::Storage) @@ -413,11 +423,8 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const } } -/// Used in StorageByteArrayElement -static FixedBytesType byteType(1); - StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): - LValue(_compilerContext, &byteType) + LValue(_compilerContext, TypeProvider::byte()) { } @@ -468,8 +475,8 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer m_context << Instruction::SWAP1 << Instruction::SSTORE; } -StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): - LValue(_compilerContext, _arrayType.memberType("length").get()), +StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType): + LValue(_compilerContext, _arrayType.memberType("length")), m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); diff --git a/libsolidity/codegen/LValue.h b/libsolidity/codegen/LValue.h index c576f9de9..3072ff11a 100644 --- a/libsolidity/codegen/LValue.h +++ b/libsolidity/codegen/LValue.h @@ -22,10 +22,10 @@ #pragma once +#include +#include #include #include -#include -#include namespace dev { @@ -49,23 +49,23 @@ class LValue m_context(_compilerContext), m_dataType(_dataType) {} public: - virtual ~LValue() {} + virtual ~LValue() = default; /// @returns the number of stack slots occupied by the lvalue reference virtual unsigned sizeOnStack() const { return 1; } /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, /// also removes the reference from the stack. /// @a _location source location of the current expression, used for error reporting. - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const = 0; + virtual void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const = 0; /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. /// @a _location is the source location of the expression that caused this operation. /// Stack pre: value [lvalue_ref] /// Stack post: if !_move: value_of(lvalue_ref) virtual void storeValue(Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), bool _move = false) const = 0; + langutil::SourceLocation const& _location = {}, bool _move = false) const = 0; /// Stores zero in the lvalue. Removes the reference from the stack if @a _removeReference is true. /// @a _location is the source location of the requested operation virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const = 0; @@ -82,15 +82,15 @@ class StackVariable: public LValue public: StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration); - virtual unsigned sizeOnStack() const override { return 0; } - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + unsigned sizeOnStack() const override { return 0; } + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; @@ -108,15 +108,15 @@ class MemoryItem: public LValue { public: MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded = true); - virtual unsigned sizeOnStack() const override { return 1; } - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + unsigned sizeOnStack() const override { return 1; } + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; private: @@ -136,15 +136,15 @@ class StorageItem: public LValue StorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. StorageItem(CompilerContext& _compilerContext, Type const& _type); - virtual unsigned sizeOnStack() const override { return 2; } - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + unsigned sizeOnStack() const override { return 2; } + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; }; @@ -158,15 +158,15 @@ class StorageByteArrayElement: public LValue public: /// Constructs the LValue and assumes that the storage reference is already on the stack. StorageByteArrayElement(CompilerContext& _compilerContext); - virtual unsigned sizeOnStack() const override { return 2; } - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + unsigned sizeOnStack() const override { return 2; } + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; }; @@ -181,14 +181,14 @@ class StorageArrayLength: public LValue public: /// Constructs the LValue, assumes that the reference to the array head is already on the stack. StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType); - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; @@ -205,15 +205,15 @@ class TupleObject: public LValue /// Constructs the LValue assuming that the other LValues are present on the stack. /// Empty unique_ptrs are possible if e.g. some values should be ignored during assignment. TupleObject(CompilerContext& _compilerContext, std::vector>&& _lvalues); - virtual unsigned sizeOnStack() const override; - virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + unsigned sizeOnStack() const override; + void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _move = false ) const override; virtual void setToZero( - SourceLocation const& _location = SourceLocation(), + langutil::SourceLocation const& _location = {}, bool _removeReference = true ) const override; diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.cpp b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp new file mode 100644 index 000000000..9dc66cea2 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.cpp @@ -0,0 +1,52 @@ +/* + 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 . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#include + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string MultiUseYulFunctionCollector::requestedFunctions() +{ + string result; + for (auto const& f: m_requestedFunctions) + result += f.second; + m_requestedFunctions.clear(); + return result; +} + +string MultiUseYulFunctionCollector::createFunction(string const& _name, function const& _creator) +{ + if (!m_requestedFunctions.count(_name)) + { + string fun = _creator(); + solAssert(!fun.empty(), ""); + solAssert(fun.find("function " + _name) != string::npos, "Function not properly named."); + m_requestedFunctions[_name] = std::move(fun); + } + return _name; +} diff --git a/libsolidity/codegen/MultiUseYulFunctionCollector.h b/libsolidity/codegen/MultiUseYulFunctionCollector.h new file mode 100644 index 000000000..321474f90 --- /dev/null +++ b/libsolidity/codegen/MultiUseYulFunctionCollector.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +/** + * Container of (unparsed) Yul functions identified by name which are meant to be generated + * only once. + */ +class MultiUseYulFunctionCollector +{ +public: + /// Helper function that uses @a _creator to create a function and add it to + /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both + /// cases. + std::string createFunction(std::string const& _name, std::function const& _creator); + + /// @returns concatenation of all generated functions. + /// Clears the internal list, i.e. calling it again will result in an + /// empty return value. + std::string requestedFunctions(); + +private: + /// Map from function name to code for a multi-use function. + std::map m_requestedFunctions; +}; + +} +} diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp new file mode 100644 index 000000000..9bb4a9984 --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -0,0 +1,1827 @@ +/* + 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 . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +string YulUtilFunctions::combineExternalFunctionIdFunction() +{ + string functionName = "combine_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (addr, selector) -> combined { + combined := (or((addr), and(selector, 0xffffffff))) + } + )") + ("functionName", functionName) + ("shl32", shiftLeftFunction(32)) + ("shl64", shiftLeftFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::splitExternalFunctionIdFunction() +{ + string functionName = "split_external_function_id"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (combined) -> addr, selector { + combined := (combined) + selector := and(combined, 0xffffffff) + addr := (combined) + } + )") + ("functionName", functionName) + ("shr32", shiftRightFunction(32)) + ("shr64", shiftRightFunction(64)) + .render(); + }); +} + +string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata) +{ + string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory"; + return m_functionCollector->createFunction(functionName, [&]() { + if (_fromCalldata) + { + return Whiskers(R"( + function (src, dst, length) { + calldatacopy(dst, src, length) + // clear end + mstore(add(dst, length), 0) + } + )") + ("functionName", functionName) + .render(); + } + else + { + return Whiskers(R"( + function (src, dst, length) { + let i := 0 + for { } lt(i, length) { i := add(i, 32) } + { + mstore(add(dst, i), mload(add(src, i))) + } + if gt(i, length) + { + // clear end + mstore(add(dst, length), 0) + } + } + )") + ("functionName", functionName) + .render(); + } + }); +} + +string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType) +{ + string functionName = + string(_assert ? "assert_helper" : "require_helper") + + (_messageType ? ("_" + _messageType->identifier()) : ""); + + solAssert(!_assert || !_messageType, "Asserts can't have messages!"); + + return m_functionCollector->createFunction(functionName, [&]() { + if (!_messageType) + return Whiskers(R"( + function (condition) { + if iszero(condition) { } + } + )") + ("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)") + ("functionName", functionName) + .render(); + + int const hashHeaderSize = 4; + int const byteSize = 8; + u256 const errorHash = + u256(FixedHash::Arith( + FixedHash(dev::keccak256("Error(string)")) + )) << (256 - hashHeaderSize * byteSize); + + string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector) + .tupleEncoder( + {_messageType}, + {TypeProvider::stringMemory()} + ); + + return Whiskers(R"( + function (condition ) { + if iszero(condition) { + let fmp := mload() + mstore(fmp, ) + let end := (add(fmp, ) ) + revert(fmp, sub(end, fmp)) + } + } + )") + ("functionName", functionName) + ("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("errorHash", formatNumber(errorHash)) + ("abiEncodeFunc", encodeFunc) + ("hashHeaderSize", to_string(hashHeaderSize)) + ("messageVars", + (_messageType->sizeOnStack() > 0 ? ", " : "") + + suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack()) + ) + .render(); + }); +} + +string YulUtilFunctions::leftAlignFunction(Type const& _type) +{ + string functionName = string("leftAlign_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> aligned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "aligned := value"); + else + templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)"); + break; + } + case Type::Category::RationalNumber: + solAssert(false, "Left align requested for rational number."); + break; + case Type::Category::Bool: + templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + case Type::Category::Struct: + solAssert(false, "Left align requested for non-value type."); + break; + case Type::Category::FixedBytes: + templ("body", "aligned := value"); + break; + case Type::Category::Contract: + templ("body", "aligned := " + leftAlignFunction(*TypeProvider::address()) + "(value)"); + break; + case Type::Category::Enum: + { + unsigned storageBytes = dynamic_cast(_type).storageBytes(); + templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + solAssert(false, "Left align requested for inaccessible dynamic type."); + break; + default: + solAssert(false, "Left align of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::shiftLeftFunction(size_t _numBits) +{ + solAssert(_numBits < 256, ""); + + string functionName = "shift_left_" + to_string(_numBits); + 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) +{ + solAssert(_numBits < 256, ""); + + // 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"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> newValue { + newValue := + + shr(, value) + + div(value, ) + + } + )") + ("functionName", functionName) + ("hasShifts", m_evmVersion.hasBitwiseShifting()) + ("numBits", to_string(_numBits)) + ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) + .render(); + }); +} + +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, ""); + solAssert(_shiftBytes <= 32, ""); + size_t numBits = _numBytes * 8; + size_t shiftBits = _shiftBytes * 8; + string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value, toInsert) -> result { + let mask := + toInsert := (toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + )") + ("functionName", functionName) + ("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits)) + ("shl", shiftLeftFunction(shiftBits)) + .render(); + }); +} + +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"; + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (value) -> result { + result := and(add(value, 31), not(31)) + } + )") + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type) +{ + 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 { + + // 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) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); + }); +} + +string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type) +{ + string functionName = "checked_mul_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return + // Multiplication by zero could be treated separately and directly return zero. + Whiskers(R"( + function (x, y) -> product { + + // 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) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + ("maxValue", toCompactHexWithPrefix(u256(_type.maxValue()))) + ("minValue", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); + }); +} + +string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type) +{ + string functionName = "checked_div_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function (x, y) -> r { + if iszero(y) { revert(0, 0) } + + // overflow for minVal / -1 + if and( + eq(x, ), + eq(y, sub(0, 1)) + ) { revert(0, 0) } + + r := sdiv(x, y) + } + )") + ("functionName", functionName) + ("signed", _type.isSigned()) + ("minVal", toCompactHexWithPrefix(u256(_type.minValue()))) + .render(); + }); +} + +string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type) +{ + 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 { + + // 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(); + }); +} + +string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type) +{ + string functionName = "array_length_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (value) -> length { + + + length := mload(value) + + + length := sload(value) + + // Retrieve length both for in-place strings and off-place strings: + // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 + // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it + // computes (x & (-1)) / 2, which is equivalent to just x / 2. + let mask := sub(mul(0x100, iszero(and(length, 1))), 1) + length := div(and(length, mask), 2) + + + + length := + + } + )"); + w("functionName", functionName); + w("dynamic", _type.isDynamicallySized()); + if (!_type.isDynamicallySized()) + w("length", toCompactHexWithPrefix(_type.length())); + w("memory", _type.location() == DataLocation::Memory); + w("storage", _type.location() == DataLocation::Storage); + w("byteArray", _type.isByteArray()); + if (_type.isDynamicallySized()) + solAssert( + _type.location() != DataLocation::CallData, + "called regular array length function on calldata array" + ); + return w.render(); + }); +} + +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()->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(); + + solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes"); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (start, end) { + for {} lt(start, end) { start := add(start, ) } + { + (start, 0) + } + } + )") + ("functionName", functionName) + ("setToZero", storageSetToZeroFunction(_type)) + ("increment", _type.storageSize().str()) + .render(); + }); +} + +string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type) +{ + solAssert(_type.location() == DataLocation::Storage, ""); + + if (_type.baseType()->storageBytes() < 32) + { + solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); + } + + if (_type.baseType()->isValueType()) + solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); + + string functionName = "clear_storage_array_" + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (slot) { + + (slot, 0) + + (slot, add(slot, ())) + + } + )") + ("functionName", functionName) + ("dynamic", _type.isDynamicallySized()) + ("resizeArray", _type.isDynamicallySized() ? resizeDynamicArrayFunction(_type) : "") + ( + "clearRange", + clearStorageRangeFunction( + (_type.baseType()->storageBytes() < 32) ? + *TypeProvider::uint256() : + *_type.baseType() + ) + ) + ("lenToSize", arrayConvertLengthToSize(_type)) + ("len", _type.length().str()) + .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) + ("stride", to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride())) + ("byteArray", _type.isByteArray()) + ("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256())) + .render(); + default: + solAssert(false, ""); + } + + }); +} +string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type) +{ + solAssert(_type.dataStoredIn(DataLocation::Memory), ""); + string functionName = "array_allocation_size_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers w(R"( + function (length) -> size { + // Make sure we can allocate memory without overflow + if gt(length, 0xffffffffffffffff) { revert(0, 0) } + + // round up + size := and(add(length, 0x1f), not(0x1f)) + + size := mul(length, 0x20) + + + // add length slot + size := add(size, 0x20) + + } + )"); + w("functionName", functionName); + w("byteArray", _type.isByteArray()); + w("dynamic", _type.isDynamicallySized()); + return w.render(); + }); +} + +string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type) +{ + string functionName = "array_dataslot_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + // No special processing for calldata arrays, because they are stored as + // offset of the data area and length on the stack, so the offset already + // points to the data area. + // This might change, if calldata arrays are stored in a single + // stack slot at some point. + return Whiskers(R"( + function (ptr) -> data { + data := ptr + + + data := add(ptr, 0x20) + + + mstore(0, ptr) + data := keccak256(0, 0x20) + + + } + )") + ("functionName", functionName) + ("dynamic", _type.isDynamicallySized()) + ("memory", _type.location() == DataLocation::Memory) + ("storage", _type.location() == DataLocation::Storage) + .render(); + }); +} + +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::memoryArrayIndexAccessFunction(ArrayType const& _type) +{ + string functionName = "memory_array_index_access_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (baseRef, index) -> addr { + if iszero(lt(index, (baseRef))) { + invalid() + } + + let offset := mul(index, ) + + offset := add(offset, 32) + + addr := add(baseRef, offset) + } + )") + ("functionName", functionName) + ("arrayLen", arrayLengthFunction(_type)) + ("stride", to_string(_type.memoryStride())) + ("dynamicallySized", _type.isDynamicallySized()) + .render(); + }); +} + +string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/) +{ + solUnimplemented("Calldata arrays not yet implemented!"); +} + +string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type) +{ + solAssert(!_type.isByteArray(), ""); + if (_type.dataStoredIn(DataLocation::Storage)) + solAssert(_type.baseType()->storageBytes() > 16, ""); + string functionName = "array_nextElement_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (ptr) -> next { + next := add(ptr, ) + } + )"); + templ("functionName", functionName); + switch (_type.location()) + { + case DataLocation::Memory: + templ("advance", "0x20"); + break; + case DataLocation::Storage: + { + u256 size = _type.baseType()->storageSize(); + solAssert(size >= 1, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + case DataLocation::CallData: + { + u256 size = _type.calldataStride(); + solAssert(size >= 32 && size % 32 == 0, ""); + templ("advance", toCompactHexWithPrefix(size)); + break; + } + } + return templ.render(); + }); +} + +string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType) +{ + solAssert(_keyType.sizeOnStack() <= 1, ""); + + string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + if (_mappingType.keyType()->isDynamicallySized()) + return Whiskers(R"( + function (slot ) -> dataSlot { + dataSlot := (slot ) + } + )") + ("functionName", functionName) + ("key", _keyType.sizeOnStack() > 0 ? "key" : "") + ("comma", _keyType.sizeOnStack() > 0 ? "," : "") + ("hash", packedHashFunction( + {&_keyType, TypeProvider::uint256()}, + {_mappingType.keyType(), TypeProvider::uint256()} + )) + .render(); + else + { + solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); + solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), ""); + solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, ""); + Whiskers templ(R"( + function (slot ) -> dataSlot { + mstore(0, ) + mstore(0x20, slot) + dataSlot := keccak256(0, 0x40) + } + )"); + templ("functionName", functionName); + templ("key", _keyType.sizeOnStack() == 1 ? ", key" : ""); + if (_keyType.sizeOnStack() == 0) + templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()"); + else + templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)"); + return templ.render(); + } + }); +} + +string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes) +{ + solUnimplementedAssert(!_splitFunctionTypes, ""); + string functionName = + "read_from_storage_" + + string(_splitFunctionTypes ? "split_" : "") + + "offset_" + + to_string(_offset) + + "_" + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + solAssert(_type.sizeOnStack() == 1, ""); + return Whiskers(R"( + function (slot) -> value { + value := (sload(slot)) + } + )") + ("functionName", functionName) + ("extract", extractFromStorageValue(_type, _offset, false)) + .render(); + }); +} + +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::readFromMemory(Type const& _type) +{ + return readFromMemoryOrCalldata(_type, false); +} + +string YulUtilFunctions::readFromCalldata(Type const& _type) +{ + return readFromMemoryOrCalldata(_type, true); +} + +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::writeToMemoryFunction(Type const& _type) +{ + string const functionName = + string("write_to_memory_") + + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&] { + solAssert(!dynamic_cast(&_type), ""); + if (auto ref = dynamic_cast(&_type)) + { + solAssert( + ref->location() == DataLocation::Memory, + "Can only update types with location memory." + ); + + return Whiskers(R"( + function (memPtr, value) { + mstore(memPtr, value) + } + )") + ("functionName", functionName) + .render(); + } + else if ( + _type.category() == Type::Category::Function && + dynamic_cast(_type).kind() == FunctionType::Kind::External + ) + { + return Whiskers(R"( + function (memPtr, addr, selector) { + mstore(memPtr, (addr, selector)) + } + )") + ("functionName", functionName) + ("combine", combineExternalFunctionIdFunction()) + .render(); + } + else if (_type.isValueType()) + { + return Whiskers(R"( + function (memPtr, value) { + mstore(memPtr, (value)) + } + )") + ("functionName", functionName) + ("cleanup", cleanupFunction(_type)) + .render(); + } + else // Should never happen + { + solAssert( + false, + "Memory store of type " + _type.toString(true) + " not allowed." + ); + } + }); +} + +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, ""); + + string functionName = + "extract_from_storage_value_" + + string(_splitFunctionTypes ? "split_" : "") + + "offset_" + + to_string(_offset) + + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + return Whiskers(R"( + function (slot_value) -> value { + value := ((slot_value)) + } + )") + ("functionName", functionName) + ("shr", shiftRightFunction(_offset * 8)) + ("cleanupStorage", cleanupFromStorageFunction(_type, _splitFunctionTypes)) + .render(); + }); +} + +string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes) +{ + solAssert(_type.isValueType(), ""); + solUnimplementedAssert(!_splitFunctionTypes, ""); + + string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&] { + Whiskers templ(R"( + function (value) -> cleaned { + cleaned := + } + )"); + templ("functionName", functionName); + + unsigned storageBytes = _type.storageBytes(); + if (IntegerType const* type = dynamic_cast(&_type)) + if (type->isSigned() && storageBytes != 32) + { + templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)"); + return templ.render(); + } + + if (storageBytes == 32) + templ("cleaned", "value"); + else if (_type.leftAligned()) + templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)"); + else + templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")"); + + return templ.render(); + }); +} + +string YulUtilFunctions::prepareStoreFunction(Type const& _type) +{ + solUnimplementedAssert(_type.category() != Type::Category::Function, ""); + + string functionName = "prepare_store_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> ret { + ret := + } + )"); + templ("functionName", functionName); + if (_type.category() == Type::Category::FixedBytes) + templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)"); + else + templ("actualPrepare", "value"); + return templ.render(); + }); +} + +string YulUtilFunctions::allocationFunction() +{ + string functionName = "allocateMemory"; + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (size) -> memPtr { + memPtr := mload() + let newFreePtr := add(memPtr, size) + // protect against overflow + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(, newFreePtr) + } + )") + ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)) + ("functionName", functionName) + .render(); + }); +} + +string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type) +{ + solUnimplementedAssert(!_type.isByteArray(), ""); + + string functionName = "allocate_memory_array_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (length) -> memPtr { + memPtr := ((length)) + + mstore(memPtr, length) + + } + )") + ("functionName", functionName) + ("alloc", allocationFunction()) + ("allocSize", arrayAllocationSizeFunction(_type)) + ("dynamic", _type.isDynamicallySized()) + .render(); + }); +} + +string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to) +{ + if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1) + return conversionFunctionSpecial(_from, _to); + + string functionName = + "convert_" + + _from.identifier() + + "_to_" + + _to.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> converted { + + } + )"); + templ("functionName", functionName); + string body; + auto toCategory = _to.category(); + auto fromCategory = _from.category(); + switch (fromCategory) + { + case Type::Category::Address: + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(IntegerType(160), _to)) + .render(); + break; + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Contract: + { + if (RationalNumberType const* rational = dynamic_cast(&_from)) + solUnimplementedAssert(!rational->isFractional(), "Not yet implemented - FixedPointType."); + if (toCategory == Type::Category::FixedBytes) + { + solAssert( + fromCategory == Type::Category::Integer || fromCategory == Type::Category::RationalNumber, + "Invalid conversion to FixedBytesType requested." + ); + FixedBytesType const& toBytesType = dynamic_cast(_to); + body = + Whiskers("converted := ((value))") + ("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8)) + ("clean", cleanupFunction(_from)) + .render(); + } + else if (toCategory == Type::Category::Enum) + { + solAssert(_from.mobileType(), ""); + body = + Whiskers("converted := ((value))") + ("cleanEnum", cleanupFunction(_to)) + // "mobileType()" returns integer type for rational + ("cleanInt", cleanupFunction(*_from.mobileType())) + .render(); + } + else if (toCategory == Type::Category::FixedPoint) + solUnimplemented("Not yet implemented - FixedPointType."); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + solAssert( + toCategory == Type::Category::Integer || + toCategory == Type::Category::Contract, + ""); + IntegerType const addressType(160); + IntegerType const& to = + toCategory == Type::Category::Integer ? + dynamic_cast(_to) : + addressType; + + // Clean according to the "to" type, except if this is + // a widening conversion. + IntegerType const* cleanupType = &to; + if (fromCategory != Type::Category::RationalNumber) + { + IntegerType const& from = + fromCategory == Type::Category::Integer ? + dynamic_cast(_from) : + addressType; + if (to.numBits() > from.numBits()) + cleanupType = &from; + } + body = + Whiskers("converted := (value)") + ("cleanInt", cleanupFunction(*cleanupType)) + .render(); + } + break; + } + case Type::Category::Bool: + { + solAssert(_from == _to, "Invalid conversion for bool."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(_from)) + .render(); + break; + } + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Array: + { + bool equal = _from == _to; + + if (!equal) + { + ArrayType const& from = dynamic_cast(_from); + ArrayType const& to = dynamic_cast(_to); + + if (*from.mobileType() == *to.mobileType()) + equal = true; + } + + if (equal) + body = "converted := value"; + else + solUnimplementedAssert(false, "Array conversion not implemented."); + + break; + } + case Type::Category::Struct: + solUnimplementedAssert(false, "Struct conversion not implemented."); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& from = dynamic_cast(_from); + if (toCategory == Type::Category::Integer) + body = + Whiskers("converted := ((value))") + ("shift", shiftRightFunction(256 - from.numBytes() * 8)) + ("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to)) + .render(); + else if (toCategory == Type::Category::Address) + body = + Whiskers("converted := (value)") + ("convert", conversionFunction(_from, IntegerType(160))) + .render(); + else + { + // clear for conversion to longer bytes + solAssert(toCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(from)) + .render(); + } + break; + } + case Type::Category::Function: + { + solAssert(false, "Conversion should not be called for function types."); + break; + } + case Type::Category::Enum: + { + solAssert(toCategory == Type::Category::Integer || _from == _to, ""); + EnumType const& enumType = dynamic_cast(_from); + body = + Whiskers("converted := (value)") + ("clean", cleanupFunction(enumType)) + .render(); + break; + } + case Type::Category::Tuple: + { + solUnimplementedAssert(false, "Tuple conversion not implemented."); + break; + } + default: + solAssert(false, ""); + } + + solAssert(!body.empty(), _from.canonicalName() + " to " + _to.canonicalName()); + templ("body", body); + return templ.render(); + }); +} + +string YulUtilFunctions::cleanupFunction(Type const& _type) +{ + string functionName = string("cleanup_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) -> cleaned { + + } + )"); + templ("functionName", functionName); + switch (_type.category()) + { + case Type::Category::Address: + templ("body", "cleaned := " + cleanupFunction(IntegerType(160)) + "(value)"); + break; + case Type::Category::Integer: + { + IntegerType const& type = dynamic_cast(_type); + if (type.numBits() == 256) + templ("body", "cleaned := value"); + else if (type.isSigned()) + templ("body", "cleaned := signextend(" + to_string(type.numBits() / 8 - 1) + ", value)"); + else + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << type.numBits()) - 1) + ")"); + break; + } + case Type::Category::RationalNumber: + templ("body", "cleaned := value"); + break; + case Type::Category::Bool: + templ("body", "cleaned := iszero(iszero(value))"); + break; + case Type::Category::FixedPoint: + solUnimplemented("Fixed point types not implemented."); + break; + case Type::Category::Function: + solAssert(dynamic_cast(_type).kind() == FunctionType::Kind::External, ""); + templ("body", "cleaned := " + cleanupFunction(FixedBytesType(24)) + "(value)"); + break; + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type."); + templ("body", "cleaned := value"); + break; + case Type::Category::FixedBytes: + { + FixedBytesType const& type = dynamic_cast(_type); + if (type.numBytes() == 32) + templ("body", "cleaned := value"); + else if (type.numBytes() == 0) + // This is disallowed in the type system. + solAssert(false, ""); + else + { + size_t numBits = type.numBytes() * 8; + u256 mask = ((u256(1) << numBits) - 1) << (256 - numBits); + templ("body", "cleaned := and(value, " + toCompactHexWithPrefix(mask) + ")"); + } + break; + } + case Type::Category::Contract: + { + AddressType addressType(dynamic_cast(_type).isPayable() ? + StateMutability::Payable : + StateMutability::NonPayable + ); + templ("body", "cleaned := " + cleanupFunction(addressType) + "(value)"); + break; + } + case Type::Category::Enum: + { + // Out of range enums cannot be truncated unambigiously and therefore it should be an error. + templ("body", "cleaned := value " + validatorFunction(_type) + "(value)"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("body", "cleaned := 0"); + break; + default: + solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure) +{ + string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function (value) { + if iszero() { } + } + )"); + templ("functionName", functionName); + if (_revertOnFailure) + templ("failure", "revert(0, 0)"); + else + templ("failure", "invalid()"); + + switch (_type.category()) + { + case Type::Category::Address: + case Type::Category::Integer: + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::FixedPoint: + case Type::Category::Function: + case Type::Category::Array: + case Type::Category::Struct: + case Type::Category::Mapping: + case Type::Category::FixedBytes: + case Type::Category::Contract: + { + templ("condition", "eq(value, " + cleanupFunction(_type) + "(value))"); + break; + } + case Type::Category::Enum: + { + size_t members = dynamic_cast(_type).numberOfMembers(); + solAssert(members > 0, "empty enum should have caused a parser error."); + templ("condition", "lt(value, " + to_string(members) + ")"); + break; + } + case Type::Category::InaccessibleDynamic: + templ("condition", "1"); + break; + default: + solAssert(false, "Validation of type " + _type.identifier() + " requested."); + } + + return templ.render(); + }); +} + +string YulUtilFunctions::packedHashFunction( + vector const& _givenTypes, + vector const& _targetTypes +) +{ + string functionName = string("packed_hashed_"); + for (auto const& t: _givenTypes) + functionName += t->identifier() + "_"; + functionName += "_to_"; + for (auto const& t: _targetTypes) + functionName += t->identifier() + "_"; + size_t sizeOnStack = 0; + for (Type const* t: _givenTypes) + sizeOnStack += t->sizeOnStack(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function () -> hash { + let pos := mload() + let end := (pos ) + hash := keccak256(pos, sub(end, pos)) + } + )"); + templ("functionName", functionName); + templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack)); + templ("comma", sizeOnStack > 0 ? "," : ""); + templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); + templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes)); + return templ.render(); + }); +} + +string YulUtilFunctions::forwardingRevertFunction() +{ + bool forward = m_evmVersion.supportsReturndata(); + string functionName = "revert_forward_" + to_string(forward); + return m_functionCollector->createFunction(functionName, [&]() { + if (forward) + return Whiskers(R"( + function () { + returndatacopy(0, 0, returndatasize()) + revert(0, returndatasize()) + } + )") + ("functionName", functionName) + .render(); + else + return Whiskers(R"( + function () { + revert(0, 0) + } + )") + ("functionName", functionName) + .render(); + }); +} + +std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type) +{ + IntegerType const& type = dynamic_cast(_type); + + string const functionName = "decrement_" + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&]() { + u256 minintval; + + // Smallest admissible value to decrement + if (type.isSigned()) + minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; + else + minintval = 1; + + return Whiskers(R"( + function (value) -> ret { + if (value, ) { revert(0,0) } + ret := sub(value, 1) + } + )") + ("functionName", functionName) + ("minval", toCompactHexWithPrefix(minintval)) + ("lt", type.isSigned() ? "slt" : "lt") + .render(); + }); +} + +std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type) +{ + IntegerType const& type = dynamic_cast(_type); + + string const functionName = "increment_" + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&]() { + u256 maxintval; + + // Biggest admissible value to increment + if (type.isSigned()) + maxintval = (u256(1) << (type.numBits() - 1)) - 2; + else + maxintval = (u256(1) << type.numBits()) - 2; + + return Whiskers(R"( + function (value) -> ret { + if (value, ) { revert(0,0) } + ret := add(value, 1) + } + )") + ("functionName", functionName) + ("maxval", toCompactHexWithPrefix(maxintval)) + ("gt", type.isSigned() ? "sgt" : "gt") + .render(); + }); +} + +string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type) +{ + IntegerType const& type = dynamic_cast(_type); + solAssert(type.isSigned(), "Expected signed type!"); + + string const functionName = "negate_" + _type.identifier(); + + u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1; + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function (_value) -> ret { + if slt(_value, ) { revert(0,0) } + ret := sub(0, _value) + } + )") + ("functionName", functionName) + ("minval", toCompactHexWithPrefix(minintval)) + .render(); + }); +} + +string YulUtilFunctions::zeroValueFunction(Type const& _type) +{ + solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!"); + solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented"); + + string const functionName = "zero_value_for_" + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&]() { + return Whiskers(R"( + function () -> ret { + + } + )") + ("functionName", functionName) + ("body", "ret := 0x0") + .render(); + }); +} + +string YulUtilFunctions::storageSetToZeroFunction(Type const& _type) +{ + string const functionName = "storage_set_to_zero_" + _type.identifier(); + + return m_functionCollector->createFunction(functionName, [&]() { + if (_type.isValueType()) + return Whiskers(R"( + function (slot, offset) { + (slot, offset, ()) + } + )") + ("functionName", functionName) + ("store", updateStorageValueFunction(_type)) + ("zeroValue", zeroValueFunction(_type)) + .render(); + else if (_type.category() == Type::Category::Array) + return Whiskers(R"( + function (slot, offset) { + (slot) + } + )") + ("functionName", functionName) + ("clearArray", clearStorageArrayFunction(dynamic_cast(_type))) + .render(); + else + solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!"); + }); +} + +string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to) +{ + string functionName = + "convert_" + + _from.identifier() + + "_to_" + + _to.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + solUnimplementedAssert( + _from.category() == Type::Category::StringLiteral, + "Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented." + ); + string const& data = dynamic_cast(_from).value(); + if (_to.category() == Type::Category::FixedBytes) + { + unsigned const numBytes = dynamic_cast(_to).numBytes(); + solAssert(data.size() <= 32, ""); + Whiskers templ(R"( + function () -> converted { + converted := + } + )"); + templ("functionName", functionName); + templ("data", formatNumber( + h256::Arith(h256(data, h256::AlignLeft)) & + (~(u256(-1) >> (8 * numBytes))) + )); + return templ.render(); + } + else if (_to.category() == Type::Category::Array) + { + auto const& arrayType = dynamic_cast(_to); + solAssert(arrayType.isByteArray(), ""); + size_t words = (data.size() + 31) / 32; + size_t storageSize = 32 + words * 32; + + Whiskers templ(R"( + function () -> converted { + converted := () + mstore(converted, ) + <#word> + mstore(add(converted, ), ) + + } + )"); + templ("functionName", functionName); + templ("allocate", allocationFunction()); + templ("storageSize", to_string(storageSize)); + templ("size", to_string(data.size())); + vector> wordParams(words); + for (size_t i = 0; i < words; ++i) + { + wordParams[i]["offset"] = to_string(32 + i * 32); + wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex(); + } + templ("word", wordParams); + return templ.render(); + } + else + solAssert( + false, + "Invalid conversion from string literal to " + _to.toString() + " requested." + ); + }); +} + +string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata) +{ + string functionName = + string("read_from_") + + (_fromCalldata ? "calldata" : "memory") + + _type.identifier(); + + // TODO use ABI functions for handling calldata + if (_fromCalldata) + solAssert(!_type.isDynamicallyEncoded(), ""); + + return m_functionCollector->createFunction(functionName, [&] { + if (auto refType = dynamic_cast(&_type)) + { + solAssert(refType->sizeOnStack() == 1, ""); + solAssert(!_fromCalldata, ""); + + return Whiskers(R"( + function (memPtr) -> value { + value := mload(memPtr) + } + )") + ("functionName", functionName) + .render(); + } + + solAssert(_type.isValueType(), ""); + + if (auto const* funType = dynamic_cast(&_type)) + if (funType->kind() == FunctionType::Kind::External) + return Whiskers(R"( + function (memPtr) -> addr, selector { + let combined := (memPtr) + addr, selector := (combined) + } + )") + ("functionName", functionName) + ("load", _fromCalldata ? "calldataload" : "mload") + ("splitFunction", splitExternalFunctionIdFunction()) + .render(); + + return Whiskers(R"( + function (memPtr) -> value { + value := (memPtr) + + value := (value) + + } + )") + ("functionName", functionName) + ("load", _fromCalldata ? "calldataload" : "mload") + ("needsValidation", _fromCalldata) + ("validate", _fromCalldata ? validatorFunction(_type) : "") + .render(); + }); +} diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h new file mode 100644 index 000000000..3b9617c71 --- /dev/null +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -0,0 +1,286 @@ +/* + 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 . +*/ +/** + * Component that can generate various useful Yul functions. + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class Type; +class ArrayType; +class MappingType; +class IntegerType; + +/** + * Component that can generate various useful Yul functions. + */ +class YulUtilFunctions +{ +public: + explicit YulUtilFunctions( + langutil::EVMVersion _evmVersion, + std::shared_ptr _functionCollector + ): + m_evmVersion(_evmVersion), + m_functionCollector(std::move(_functionCollector)) + {} + + /// @returns a function that combines the address and selector to a single value + /// for use in the ABI. + std::string combineExternalFunctionIdFunction(); + + /// @returns a function that splits the address and selector from a single value + /// for use in the ABI. + std::string splitExternalFunctionIdFunction(); + + /// @returns a function that copies raw bytes of dynamic length from calldata + /// or memory to memory. + /// Pads with zeros and might write more than exactly length. + std::string copyToMemoryFunction(bool _fromCalldata); + + // @returns the name of a function that has the equivalent logic of an + // `assert` or `require` call. + std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr); + + /// @returns the name of a function that takes a (cleaned) value of the given value type and + /// left-aligns it, usually for use in non-padded encoding. + 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 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(); + + /// signature: (x, y) -> sum + std::string overflowCheckedIntAddFunction(IntegerType const& _type); + + /// 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. + /// 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). + /// signature: (start, end) + std::string clearStorageRangeFunction(Type const& _type); + + /// @returns the name of a function that will clear the given storage array + /// signature: (slot) -> + std::string clearStorageArrayFunction(ArrayType 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. + std::string arrayAllocationSizeFunction(ArrayType const& _type); + /// @returns the name of a function that converts a storage slot number + /// 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 returns the memory address for the + /// given array base ref and index. + /// Causes invalid opcode on out of range access. + /// signature: (baseRef, index) -> address + std::string memoryArrayIndexAccessFunction(ArrayType const& _type); + + /// @returns the name of a function that returns the calldata address for the + /// given array base ref and index. + /// signature: (baseRef, index) -> address + std::string calldataArrayIndexAccessFunction(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 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. + /// @param _mappingType the type of the mapping + /// @param _keyType the type of the value provided + std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType); + + /// @returns a function that reads a value type from storage. + /// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation. + /// @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 reads a value type from memory. + /// signature: (addr) -> value + std::string readFromMemory(Type const& _type); + /// @returns a function that reads a value type from calldata. + /// Reverts on invalid input. + /// signature: (addr) -> value + std::string readFromCalldata(Type const& _type); + + /// @returns a function that extracts a value type from storage slot that has been + /// retrieved already. + /// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation. + /// @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()); + + /// Returns the name of a function that will write the given value to + /// the specified address. + /// Performs a cleanup before writing for value types. + /// signature: (memPtr, value) -> + std::string writeToMemoryFunction(Type const& _type); + + /// Performs cleanup after reading from a potentially compressed storage slot. + /// The function does not perform any validation, it just masks or sign-extends + /// higher order bytes or left-aligns (in case of bytesNN). + /// The storage cleanup expects the value to be right-aligned with potentially + /// dirty higher order bytes. + /// @param _splitFunctionTypes if false, returns the address and function signature in a + /// single variable. + std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); + + /// @returns the name of a function that prepares a value of the given type + /// for being stored in storage. This usually includes cleanup and right-alignment + /// to fit the number of bytes in storage. + /// The resulting value might still have dirty higher order bits. + std::string prepareStoreFunction(Type const& _type); + + /// @returns the name of a function that allocates memory. + /// Modifies the "free memory pointer" + /// Arguments: size + /// Return value: pointer + std::string allocationFunction(); + + /// @returns the name of a function that allocates a memory array. + /// For dynamic arrays it adds space for length and stores it. + /// signature: (length) -> memPtr + std::string allocateMemoryArrayFunction(ArrayType const& _type); + + /// @returns the name of the function that converts a value of type @a _from + /// to a value of type @a _to. The resulting vale is guaranteed to be in range + /// (i.e. "clean"). Asserts on failure. + /// + /// This is used for data being encoded or general type conversions in the code. + std::string conversionFunction(Type const& _from, Type const& _to); + + /// @returns the name of the cleanup function for the given type and + /// adds its implementation to the requested functions. + /// The cleanup function defers to the validator function with "assert" + /// if there is no reasonable way to clean a value. + std::string cleanupFunction(Type const& _type); + + /// @returns the name of the validator function for the given type and + /// adds its implementation to the requested functions. + /// @param _revertOnFailure if true, causes revert on invalid data, + /// otherwise an assertion failure. + /// + /// This is used for data decoded from external sources. + std::string validatorFunction(Type const& _type, bool _revertOnFailure = false); + + std::string packedHashFunction(std::vector const& _givenTypes, std::vector const& _targetTypes); + + /// @returns the name of a function that reverts and uses returndata (if available) + /// as reason string. + std::string forwardingRevertFunction(); + + std::string incrementCheckedFunction(Type const& _type); + std::string decrementCheckedFunction(Type const& _type); + + std::string negateNumberCheckedFunction(Type const& _type); + + /// @returns the name of a function that returns the zero value for the + /// provided type + std::string zeroValueFunction(Type const& _type); + + /// @returns the name of a function that will set the given storage item to + /// zero + /// signature: (slot, offset) -> + std::string storageSetToZeroFunction(Type const& _type); +private: + /// Special case of conversionFunction - handles everything that does not + /// use exactly one variable to hold the value. + std::string conversionFunctionSpecial(Type const& _from, Type const& _to); + + std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata); + + langutil::EVMVersion m_evmVersion; + std::shared_ptr m_functionCollector; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp new file mode 100644 index 000000000..54e997e4e --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -0,0 +1,165 @@ +/* + 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 contains contextual information during IR generation. + */ + +#include + +#include +#include + +#include +#include + +using namespace dev; +using namespace dev::solidity; +using namespace std; + +string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl) +{ + solUnimplementedAssert( + _varDecl.annotation().type->sizeOnStack() == 1, + "Multi-slot types not yet implemented." + ); + + return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} + +string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl) +{ + solAssert( + m_localVariables.count(&_varDecl), + "Unknown variable: " + _varDecl.name() + ); + return m_localVariables[&_varDecl]; +} + +void IRGenerationContext::addStateVariable( + VariableDeclaration const& _declaration, + u256 _storageOffset, + unsigned _byteOffset +) +{ + m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset); +} + +string IRGenerationContext::functionName(FunctionDefinition const& _function) +{ + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? + return "fun_" + _function.name() + "_" + to_string(_function.id()); +} + +string IRGenerationContext::functionName(VariableDeclaration const& _varDecl) +{ + return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id()); +} + +FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function) +{ + // @TODO previously, we had to distinguish creation context and runtime context, + // but since we do not work with jump positions anymore, this should not be a problem, right? + string name = _function.name(); + FunctionType functionType(_function); + for (auto const& contract: m_inheritanceHierarchy) + for (FunctionDefinition const* function: contract->definedFunctions()) + if ( + function->name() == name && + !function->isConstructor() && + FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType) + ) + return *function; + solAssert(false, "Super function " + name + " not found."); +} + +string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration) +{ + return functionName(virtualFunction(_functionDeclaration)); +} + +string IRGenerationContext::newYulVariable() +{ + return "_" + to_string(++m_varCounter); +} + +string IRGenerationContext::variable(Expression const& _expression) +{ + unsigned size = _expression.annotation().type->sizeOnStack(); + string var = "expr_" + to_string(_expression.id()); + if (size == 1) + return var; + else + return suffixedVariableNameList(move(var) + "_", 1, 1 + size); +} + +string IRGenerationContext::variablePart(Expression const& _expression, size_t _part) +{ + size_t numVars = _expression.annotation().type->sizeOnStack(); + solAssert(numVars > 1, ""); + solAssert(1 <= _part && _part <= numVars, ""); + return "expr_" + to_string(_expression.id()) + "_" + to_string(_part); +} + +string IRGenerationContext::internalDispatch(size_t _in, size_t _out) +{ + string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out); + return m_functions->createFunction(funName, [&]() { + Whiskers templ(R"( + function (fun ) { + switch fun + <#cases> + case + { + := () + } + + default { invalid() } + } + )"); + templ("functionName", funName); + templ("comma", _in > 0 ? "," : ""); + YulUtilFunctions utils(m_evmVersion, m_functions); + templ("in", suffixedVariableNameList("in_", 0, _in)); + templ("arrow", _out > 0 ? "->" : ""); + templ("out", suffixedVariableNameList("out_", 0, _out)); + vector> functions; + for (auto const& contract: m_inheritanceHierarchy) + for (FunctionDefinition const* function: contract->definedFunctions()) + if ( + !function->isConstructor() && + function->parameters().size() == _in && + function->returnParameters().size() == _out + ) + { + // 0 is reserved for uninitialized function pointers + solAssert(function->id() != 0, "Unexpected function ID: 0"); + + functions.emplace_back(map { + { "funID", to_string(function->id()) }, + { "name", functionName(*function)} + }); + } + templ("cases", move(functions)); + return templ.render(); + }); +} + +YulUtilFunctions IRGenerationContext::utils() +{ + return YulUtilFunctions(m_evmVersion, m_functions); +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h new file mode 100644 index 000000000..ed9fc5bc5 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -0,0 +1,110 @@ +/* + 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 contains contextual information during IR generation. + */ + +#pragma once + +#include + +#include + +#include + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class ContractDefinition; +class VariableDeclaration; +class FunctionDefinition; +class Expression; +class YulUtilFunctions; + +/** + * Class that contains contextual information during IR generation. + */ +class IRGenerationContext +{ +public: + IRGenerationContext(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(std::move(_optimiserSettings)), + m_functions(std::make_shared()) + {} + + std::shared_ptr functionCollector() const { return m_functions; } + + /// Sets the current inheritance hierarchy from derived to base. + void setInheritanceHierarchy(std::vector _hierarchy) + { + m_inheritanceHierarchy = std::move(_hierarchy); + } + + + std::string addLocalVariable(VariableDeclaration const& _varDecl); + bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } + std::string localVariableName(VariableDeclaration const& _varDecl); + + void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); + bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } + std::pair storageLocationOfVariable(VariableDeclaration const& _varDecl) const + { + return m_stateVariables.at(&_varDecl); + } + + std::string functionName(FunctionDefinition const& _function); + std::string functionName(VariableDeclaration const& _varDecl); + FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration); + std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration); + + std::string newYulVariable(); + /// @returns the variable (or comma-separated list of variables) that contain + /// the value of the given expression. + std::string variable(Expression const& _expression); + /// @returns the variable of a multi-variable expression. Variables are numbered + /// starting from 1. + std::string variablePart(Expression const& _expression, size_t _part); + + std::string internalDispatch(size_t _in, size_t _out); + + /// @returns a new copy of the utility function generator (but using the same function set). + YulUtilFunctions utils(); + + langutil::EVMVersion evmVersion() const { return m_evmVersion; }; + +private: + langutil::EVMVersion m_evmVersion; + OptimiserSettings m_optimiserSettings; + std::vector m_inheritanceHierarchy; + std::map m_localVariables; + /// Storage offsets of state variables + std::map> m_stateVariables; + std::shared_ptr m_functions; + size_t m_varCounter = 0; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp new file mode 100644 index 000000000..9c7834ff7 --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -0,0 +1,343 @@ +/* + 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 Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +pair IRGenerator::run(ContractDefinition const& _contract) +{ + string const ir = yul::reindent(generate(_contract)); + + yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); + if (!asmStack.parseAndAnalyze("", ir)) + { + string errorMessage; + for (auto const& error: asmStack.errors()) + errorMessage += langutil::SourceReferenceFormatter::formatErrorInformation(*error); + solAssert(false, ir + "\n\nInvalid IR generated:\n" + errorMessage + "\n"); + } + asmStack.optimize(); + + string warning = + "/*******************************************************\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"; + + return {warning + ir, warning + asmStack.print()}; +} + +string IRGenerator::generate(ContractDefinition const& _contract) +{ + solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented."); + + Whiskers t(R"( + object "" { + code { + + + + + } + object "" { + code { + + + + } + } + } + )"); + + resetContext(_contract); + + t("CreationObject", creationObjectName(_contract)); + t("memoryInit", memoryInit()); + t("constructor", constructorCode(_contract)); + t("deploy", deployCode(_contract)); + // We generate code for all functions and rely on the optimizer to remove them again + // TODO it would probably be better to only generate functions when internalDispatch or + // virtualFunctionName is called - same below. + for (auto const* contract: _contract.annotation().linearizedBaseContracts) + for (auto const* fun: contract->definedFunctions()) + generateFunction(*fun); + t("functions", m_context.functionCollector()->requestedFunctions()); + + resetContext(_contract); + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + t("RuntimeObject", runtimeObjectName(_contract)); + t("dispatch", dispatchRoutine(_contract)); + for (auto const* contract: _contract.annotation().linearizedBaseContracts) + for (auto const* fun: contract->definedFunctions()) + generateFunction(*fun); + t("runtimeFunctions", m_context.functionCollector()->requestedFunctions()); + return t.render(); +} + +string IRGenerator::generate(Block const& _block) +{ + IRGeneratorForStatements generator(m_context, m_utils); + _block.accept(generator); + return generator.code(); +} + +string IRGenerator::generateFunction(FunctionDefinition const& _function) +{ + string functionName = m_context.functionName(_function); + return m_context.functionCollector()->createFunction(functionName, [&]() { + Whiskers t(R"( + function () { + for { let return_flag := 1 } return_flag {} { + + break + } + } + )"); + t("functionName", functionName); + string params; + for (auto const& varDecl: _function.parameters()) + params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("params", params); + string retParams; + for (auto const& varDecl: _function.returnParameters()) + retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl); + t("returns", retParams.empty() ? "" : " -> " + retParams); + t("body", generate(_function.body())); + return t.render(); + }); +} + +string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) +{ + string functionName = m_context.functionName(_varDecl); + + Type const* type = _varDecl.annotation().type; + + solAssert(!_varDecl.isConstant(), ""); + solAssert(_varDecl.isStateVariable(), ""); + + solUnimplementedAssert(type->isValueType(), ""); + + return m_context.functionCollector()->createFunction(functionName, [&]() { + pair slot_offset = m_context.storageLocationOfVariable(_varDecl); + + return Whiskers(R"( + function () -> rval { + rval := () + } + )") + ("functionName", functionName) + ("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) + ("slot", slot_offset.first.str()) + .render(); + }); +} + +string IRGenerator::constructorCode(ContractDefinition const& _contract) +{ + // Initialization of state variables in base-to-derived order. + solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); + + using boost::adaptors::reverse; + + ostringstream out; + + FunctionDefinition const* constructor = _contract.constructor(); + if (constructor && !constructor->isPayable()) + out << callValueCheck(); + + for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts)) + { + out << + "\n// Begin state variable initialization for contract \"" << + contract->name() << + "\" (" << + contract->stateVariables().size() << + " variables)\n"; + + IRGeneratorForStatements generator{m_context, m_utils}; + for (VariableDeclaration const* variable: contract->stateVariables()) + if (!variable->isConstant()) + generator.initializeStateVar(*variable); + out << generator.code(); + + out << "// End state variable initialization for contract \"" << contract->name() << "\".\n"; + } + + if (constructor) + { + solUnimplementedAssert(constructor->parameters().empty(), ""); + + // TODO base constructors + + out << m_context.functionName(*constructor) + "()\n"; + } + + return out.str(); +} + +string IRGenerator::deployCode(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + )X"); + t("object", runtimeObjectName(_contract)); + return t.render(); +} + +string IRGenerator::callValueCheck() +{ + return "if callvalue() { revert(0, 0) }"; +} + +string IRGenerator::creationObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()); +} + +string IRGenerator::runtimeObjectName(ContractDefinition const& _contract) +{ + return _contract.name() + "_" + to_string(_contract.id()) + "_deployed"; +} + +string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) +{ + Whiskers t(R"X( + if iszero(lt(calldatasize(), 4)) + { + let selector := (calldataload(0)) + switch selector + <#cases> + case + { + // + + (4, calldatasize()) + () + let memPos := (0) + let memEnd := (memPos ) + return(memPos, sub(memEnd, memPos)) + } + + default {} + } + + )X"); + t("shr224", m_utils.shiftRightFunction(224)); + vector> functions; + for (auto const& function: _contract.interfaceFunctions()) + { + functions.push_back({}); + map& templ = functions.back(); + templ["functionSelector"] = "0x" + function.first.hex(); + FunctionTypePointer const& type = function.second; + templ["functionName"] = type->externalSignature(); + templ["callValueCheck"] = type->isPayable() ? "" : callValueCheck(); + + unsigned paramVars = make_shared(type->parameterTypes())->sizeOnStack(); + unsigned retVars = make_shared(type->returnParameterTypes())->sizeOnStack(); + templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := "; + templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := "; + + ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector()); + templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes()); + templ["params"] = suffixedVariableNameList("param_", 0, paramVars); + templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0); + + if (FunctionDefinition const* funDef = dynamic_cast(&type->declaration())) + templ["function"] = generateFunction(*funDef); + else if (VariableDeclaration const* varDecl = dynamic_cast(&type->declaration())) + templ["function"] = generateGetter(*varDecl); + else + solAssert(false, "Unexpected declaration for function!"); + + templ["allocate"] = m_utils.allocationFunction(); + templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false); + templ["comma"] = retVars == 0 ? "" : ", "; + } + t("cases", functions); + if (FunctionDefinition const* fallback = _contract.fallbackFunction()) + { + string fallbackCode; + if (!fallback->isPayable()) + fallbackCode += callValueCheck(); + fallbackCode += generateFunction(*fallback) + "() stop()"; + + t("fallback", fallbackCode); + } + else + t("fallback", "revert(0, 0)"); + return t.render(); +} + +string IRGenerator::memoryInit() +{ + // This function should be called at the beginning of the EVM call frame + // and thus can assume all memory to be zero, including the contents of + // the "zero memory area" (the position CompilerUtils::zeroPointer points to). + return + Whiskers{"mstore(, )"} + ("memPtr", to_string(CompilerUtils::freeMemoryPointer)) + ("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) + .render(); +} + +void IRGenerator::resetContext(ContractDefinition const& _contract) +{ + solAssert( + m_context.functionCollector()->requestedFunctions().empty(), + "Reset context while it still had functions." + ); + m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); + m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); + + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + for (auto const& var: ContractType(_contract).stateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); +} diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h new file mode 100644 index 000000000..0da85018c --- /dev/null +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -0,0 +1,83 @@ +/* + 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 Alex Beregszaszi + * @date 2017 + * Component that translates Solidity code into Yul. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class SourceUnit; + +class IRGenerator +{ +public: + IRGenerator(langutil::EVMVersion _evmVersion, OptimiserSettings _optimiserSettings): + m_evmVersion(_evmVersion), + m_optimiserSettings(_optimiserSettings), + m_context(_evmVersion, std::move(_optimiserSettings)), + m_utils(_evmVersion, m_context.functionCollector()) + {} + + /// Generates and returns the IR code, in unoptimized and optimized form + /// (or just pretty-printed, depending on the optimizer settings). + std::pair run(ContractDefinition const& _contract); + +private: + std::string generate(ContractDefinition const& _contract); + std::string generate(Block const& _block); + + /// Generates code for and returns the name of the function. + std::string generateFunction(FunctionDefinition const& _function); + /// Generates a getter for the given declaration and returns its name + std::string generateGetter(VariableDeclaration const& _varDecl); + + std::string constructorCode(ContractDefinition const& _contract); + std::string deployCode(ContractDefinition const& _contract); + std::string callValueCheck(); + + std::string creationObjectName(ContractDefinition const& _contract); + std::string runtimeObjectName(ContractDefinition const& _contract); + + std::string dispatchRoutine(ContractDefinition const& _contract); + + std::string memoryInit(); + + void resetContext(ContractDefinition const& _contract); + + langutil::EVMVersion const m_evmVersion; + OptimiserSettings const m_optimiserSettings; + + IRGenerationContext m_context; + YulUtilFunctions m_utils; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp new file mode 100644 index 000000000..5dcb811cc --- /dev/null +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -0,0 +1,1375 @@ +/* + 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 . +*/ +/** + * Component that translates Solidity code into Yul at statement level and below. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +namespace +{ + +struct CopyTranslate: public yul::ASTCopier +{ + using ExternalRefsMap = std::map; + + CopyTranslate(yul::Dialect const& _dialect, IRGenerationContext& _context, ExternalRefsMap const& _references): + m_dialect(_dialect), m_context(_context), m_references(_references) {} + + 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 + // from the Yul dialect we are compiling to. So we are assuming here that the builtin + // functions are identical. This should not be a problem for now since everything + // is EVM anyway. + if (m_dialect.builtin(_name)) + return _name; + else + return yul::YulString{"usr$" + _name.str()}; + } + + yul::Identifier translate(yul::Identifier const& _identifier) override + { + if (!m_references.count(&_identifier)) + return ASTCopier::translate(_identifier); + + auto const& reference = m_references.at(&_identifier); + auto const varDecl = dynamic_cast(reference.declaration); + solUnimplementedAssert(varDecl, ""); + + solAssert( + reference.isOffset == false && reference.isSlot == false, + "Should not be called for offset/slot" + ); + + return yul::Identifier{ + _identifier.location, + yul::YulString{m_context.localVariableName(*varDecl)} + }; + } + +private: + yul::Dialect const& m_dialect; + IRGenerationContext& m_context; + ExternalRefsMap const& m_references; +}; + +} + + + +string IRGeneratorForStatements::code() const +{ + solAssert(!m_currentLValue, "LValue not reset!"); + return m_code.str(); +} + +void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl) +{ + solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable."); + solAssert(!_varDecl.isConstant(), ""); + if (_varDecl.value()) + { + _varDecl.value()->accept(*this); + string value = m_context.newYulVariable(); + Type const& varType = *_varDecl.type(); + + m_code << "let " << value << " := " << expressionAsType(*_varDecl.value(), varType) << "\n"; + m_code << IRStorageItem{m_context, _varDecl}.storeValue(value, varType); + } +} + +void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement) +{ + for (auto const& decl: _varDeclStatement.declarations()) + if (decl) + m_context.addLocalVariable(*decl); + + if (Expression const* expression = _varDeclStatement.initialValue()) + { + solUnimplementedAssert(_varDeclStatement.declarations().size() == 1, ""); + + VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front(); + m_code << + "let " << + m_context.localVariableName(varDecl) << + " := " << + expressionAsType(*expression, *varDecl.type()) << + "\n"; + } + else + for (auto const& decl: _varDeclStatement.declarations()) + if (decl) + m_code << "let " << m_context.localVariableName(*decl) << "\n"; +} + +bool IRGeneratorForStatements::visit(Assignment const& _assignment) +{ + _assignment.rightHandSide().accept(*this); + Type const* intermediateType = type(_assignment.rightHandSide()).closestTemporaryType( + &type(_assignment.leftHandSide()) + ); + string value = m_context.newYulVariable(); + m_code << "let " << value << " := " << expressionAsType(_assignment.rightHandSide(), *intermediateType) << "\n"; + + _assignment.leftHandSide().accept(*this); + solAssert(!!m_currentLValue, "LValue not retrieved."); + + if (_assignment.assignmentOperator() != Token::Assign) + { + solAssert(type(_assignment.leftHandSide()) == *intermediateType, ""); + solAssert(intermediateType->isValueType(), "Compound operators only available for value types."); + + string leftIntermediate = m_context.newYulVariable(); + m_code << "let " << leftIntermediate << " := " << m_currentLValue->retrieveValue() << "\n"; + m_code << value << " := " << binaryOperation( + TokenTraits::AssignmentToBinaryOp(_assignment.assignmentOperator()), + *intermediateType, + leftIntermediate, + value + ); + } + + m_code << m_currentLValue->storeValue(value, *intermediateType); + m_currentLValue.reset(); + defineExpression(_assignment) << value << "\n"; + + return false; +} + +bool IRGeneratorForStatements::visit(TupleExpression const& _tuple) +{ + if (_tuple.isInlineArray()) + solUnimplementedAssert(false, ""); + else + { + solUnimplementedAssert(!_tuple.annotation().lValueRequested, ""); + solUnimplementedAssert(_tuple.components().size() == 1, ""); + solAssert(_tuple.components().front(), ""); + _tuple.components().front()->accept(*this); + defineExpression(_tuple) << m_context.variable(*_tuple.components().front()) << "\n"; + } + return false; +} + +bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement) +{ + _ifStatement.condition().accept(*this); + string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean()); + + if (_ifStatement.falseStatement()) + { + m_code << "switch " << condition << "\n" "case 0 {\n"; + _ifStatement.falseStatement()->accept(*this); + m_code << "}\n" "default {\n"; + } + else + m_code << "if " << condition << " {\n"; + _ifStatement.trueStatement().accept(*this); + m_code << "}\n"; + + return false; +} + +bool IRGeneratorForStatements::visit(ForStatement const& _forStatement) +{ + generateLoop( + _forStatement.body(), + _forStatement.condition(), + _forStatement.initializationExpression(), + _forStatement.loopExpression() + ); + + return false; +} + +bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement) +{ + generateLoop( + _whileStatement.body(), + &_whileStatement.condition(), + nullptr, + nullptr, + _whileStatement.isDoWhile() + ); + + return false; +} + +bool IRGeneratorForStatements::visit(Continue const&) +{ + m_code << "continue\n"; + return false; +} + +bool IRGeneratorForStatements::visit(Break const&) +{ + m_code << "break\n"; + return false; +} + +void IRGeneratorForStatements::endVisit(Return const& _return) +{ + if (Expression const* value = _return.expression()) + { + solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); + vector> const& returnParameters = + _return.annotation().functionReturnParameters->parameters(); + TypePointers types; + for (auto const& retVariable: returnParameters) + types.push_back(retVariable->annotation().type); + + // TODO support tuples + solUnimplementedAssert(types.size() == 1, "Multi-returns not implemented."); + m_code << + m_context.localVariableName(*returnParameters.front()) << + " := " << + expressionAsType(*value, *types.front()) << + "\n"; + } + m_code << "return_flag := 0\n" << "break\n"; +} + +void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation) +{ + Type const& resultType = type(_unaryOperation); + Token const op = _unaryOperation.getOperator(); + + if (op == Token::Delete) + { + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_code << m_currentLValue->setToZero(); + m_currentLValue.reset(); + } + else if (resultType.category() == Type::Category::RationalNumber) + { + defineExpression(_unaryOperation) << + formatNumber(resultType.literalValue(nullptr)) << + "\n"; + } + else if (resultType.category() == Type::Category::Integer) + { + solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!"); + + if (op == Token::Inc || op == Token::Dec) + { + solAssert(!!m_currentLValue, "LValue not retrieved."); + string fetchValueExpr = m_currentLValue->retrieveValue(); + string modifiedValue = m_context.newYulVariable(); + string originalValue = m_context.newYulVariable(); + + m_code << "let " << originalValue << " := " << fetchValueExpr << "\n"; + m_code << + "let " << + modifiedValue << + " := " << + (op == Token::Inc ? + m_utils.incrementCheckedFunction(resultType) : + m_utils.decrementCheckedFunction(resultType) + ) << + "(" << + originalValue << + ")\n"; + m_code << m_currentLValue->storeValue(modifiedValue, resultType); + m_currentLValue.reset(); + + defineExpression(_unaryOperation) << + (_unaryOperation.isPrefixOperation() ? modifiedValue : originalValue) << + "\n"; + } + else if (op == Token::BitNot) + appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression()); + else if (op == Token::Add) + // According to SyntaxChecker... + solAssert(false, "Use of unary + is disallowed."); + else if (op == Token::Sub) + { + IntegerType const& intType = *dynamic_cast(&resultType); + + defineExpression(_unaryOperation) << + m_utils.negateNumberCheckedFunction(intType) << + "(" << + m_context.variable(_unaryOperation.subExpression()) << + ")\n"; + } + else + solUnimplementedAssert(false, "Unary operator not yet implemented"); + } + else if (resultType.category() == Type::Category::Bool) + { + solAssert( + _unaryOperation.getOperator() != Token::BitNot, + "Bitwise Negation can't be done on bool!" + ); + + appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression()); + } + else + solUnimplementedAssert(false, "Unary operator not yet implemented"); +} + +bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp) +{ + solAssert(!!_binOp.annotation().commonType, ""); + TypePointer commonType = _binOp.annotation().commonType; + langutil::Token op = _binOp.getOperator(); + + if (op == Token::And || op == Token::Or) + { + // This can short-circuit! + appendAndOrOperatorCode(_binOp); + return false; + } + + _binOp.leftExpression().accept(*this); + _binOp.rightExpression().accept(*this); + + if (commonType->category() == Type::Category::RationalNumber) + defineExpression(_binOp) << + toCompactHexWithPrefix(commonType->literalValue(nullptr)) << + "\n"; + else if (TokenTraits::isCompareOp(op)) + { + if (auto type = dynamic_cast(commonType)) + { + solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!"); + solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!"); + } + + solAssert(commonType->isValueType(), ""); + bool isSigned = false; + if (auto type = dynamic_cast(commonType)) + isSigned = type->isSigned(); + + string args = + expressionAsType(_binOp.leftExpression(), *commonType) + + ", " + + expressionAsType(_binOp.rightExpression(), *commonType); + + string expr; + if (op == Token::Equal) + expr = "eq(" + move(args) + ")"; + else if (op == Token::NotEqual) + expr = "iszero(eq(" + move(args) + "))"; + else if (op == Token::GreaterThanOrEqual) + expr = "iszero(" + string(isSigned ? "slt(" : "lt(") + move(args) + "))"; + else if (op == Token::LessThanOrEqual) + expr = "iszero(" + string(isSigned ? "sgt(" : "gt(") + move(args) + "))"; + else if (op == Token::GreaterThan) + expr = (isSigned ? "sgt(" : "gt(") + move(args) + ")"; + else if (op == Token::LessThan) + expr = (isSigned ? "slt(" : "lt(") + move(args) + ")"; + else + solAssert(false, "Unknown comparison operator."); + defineExpression(_binOp) << expr << "\n"; + } + else + { + string left = expressionAsType(_binOp.leftExpression(), *commonType); + string right = expressionAsType(_binOp.rightExpression(), *commonType); + defineExpression(_binOp) << binaryOperation(_binOp.getOperator(), *commonType, left, right); + } + return false; +} + +void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) +{ + solUnimplementedAssert( + _functionCall.annotation().kind == FunctionCallKind::FunctionCall || + _functionCall.annotation().kind == FunctionCallKind::TypeConversion, + "This type of function call is not yet implemented" + ); + + Type const& funcType = type(_functionCall.expression()); + + if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) + { + solAssert(funcType.category() == Type::Category::TypeType, "Expected category to be TypeType"); + solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion"); + + defineExpression(_functionCall) << + expressionAsType(*_functionCall.arguments().front(), type(_functionCall)) << + "\n"; + + return; + } + + FunctionTypePointer functionType = dynamic_cast(&funcType); + + TypePointers parameterTypes = functionType->parameterTypes(); + vector> const& callArguments = _functionCall.arguments(); + vector> const& callArgumentNames = _functionCall.names(); + if (!functionType->takesArbitraryParameters()) + solAssert(callArguments.size() == parameterTypes.size(), ""); + + vector> arguments; + if (callArgumentNames.empty()) + // normal arguments + arguments = callArguments; + else + // named arguments + for (auto const& parameterName: functionType->parameterNames()) + { + auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer const& _argName) { + return *_argName == parameterName; + }); + + solAssert(it != callArgumentNames.cend(), ""); + arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]); + } + + solUnimplementedAssert(!functionType->bound(), ""); + switch (functionType->kind()) + { + case FunctionType::Kind::Internal: + { + vector args; + for (unsigned i = 0; i < arguments.size(); ++i) + if (functionType->takesArbitraryParameters()) + args.emplace_back(m_context.variable(*arguments[i])); + else + args.emplace_back(expressionAsType(*arguments[i], *parameterTypes[i])); + + if (auto identifier = dynamic_cast(&_functionCall.expression())) + { + solAssert(!functionType->bound(), ""); + if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) + { + defineExpression(_functionCall) << + m_context.virtualFunctionName(*functionDef) << + "(" << + joinHumanReadable(args) << + ")\n"; + return; + } + } + + args = vector{m_context.variable(_functionCall.expression())} + args; + defineExpression(_functionCall) << + m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) << + "(" << + joinHumanReadable(args) << + ")\n"; + break; + } + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + appendExternalFunctionCall(_functionCall, arguments); + break; + case FunctionType::Kind::BareCallCode: + solAssert(false, "Callcode has been removed."); + case FunctionType::Kind::Event: + { + auto const& event = dynamic_cast(functionType->declaration()); + TypePointers paramTypes = functionType->parameterTypes(); + ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + + vector indexedArgs; + string nonIndexedArgs; + TypePointers nonIndexedArgTypes; + TypePointers nonIndexedParamTypes; + if (!event.isAnonymous()) + { + indexedArgs.emplace_back(m_context.newYulVariable()); + string signature = formatNumber(u256(h256::Arith(dev::keccak256(functionType->externalSignature())))); + m_code << "let " << indexedArgs.back() << " := " << signature << "\n"; + } + for (size_t i = 0; i < event.parameters().size(); ++i) + { + Expression const& arg = *arguments[i]; + if (event.parameters()[i]->isIndexed()) + { + string value; + indexedArgs.emplace_back(m_context.newYulVariable()); + if (auto const& referenceType = dynamic_cast(paramTypes[i])) + value = + m_utils.packedHashFunction({arg.annotation().type}, {referenceType}) + + "(" + + m_context.variable(arg) + + ")"; + else + value = expressionAsType(arg, *paramTypes[i]); + m_code << "let " << indexedArgs.back() << " := " << value << "\n"; + } + else + { + string vars = m_context.variable(arg); + if (!vars.empty()) + // In reverse because abi_encode expects it like that. + nonIndexedArgs = ", " + move(vars) + nonIndexedArgs; + nonIndexedArgTypes.push_back(arg.annotation().type); + nonIndexedParamTypes.push_back(paramTypes[i]); + } + } + solAssert(indexedArgs.size() <= 4, "Too many indexed arguments."); + Whiskers templ(R"({ + let := mload() + let := ( ) + (, sub(, ) ) + })"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer)); + templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes)); + templ("nonIndexedArgs", nonIndexedArgs); + templ("log", "log" + to_string(indexedArgs.size())); + templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs)); + m_code << templ.render(); + break; + } + case FunctionType::Kind::Assert: + case FunctionType::Kind::Require: + { + 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, + messageArgumentType + ); + + m_code << move(requireOrAssertFunction) << "(" << m_context.variable(*arguments[0]); + if (messageArgumentType && messageArgumentType->sizeOnStack() > 0) + m_code << ", " << m_context.variable(*arguments[1]); + m_code << ")\n"; + + break; + } + // Array creation using new + case FunctionType::Kind::ObjectCreation: + { + ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type); + solAssert(arguments.size() == 1, ""); + + defineExpression(_functionCall) << + m_utils.allocateMemoryArrayFunction(arrayType) << + "(" << + expressionAsType(*arguments[0], *TypeProvider::uint256()) << + ")\n"; + + break; + } + case FunctionType::Kind::KECCAK256: + { + solAssert(arguments.size() == 1, ""); + + ArrayType const* arrayType = TypeProvider::bytesMemory(); + string const& array = m_context.newYulVariable(); + m_code << "let " << array << " := " << expressionAsType(*arguments[0], *arrayType) << "\n"; + + defineExpression(_functionCall) << + "keccak256(" << + m_utils.arrayDataAreaFunction(*arrayType) << "(" << + array << + "), " << + m_utils.arrayLengthFunction(*arrayType) << + "(" << + array << + "))\n"; + + break; + } + default: + solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented"); + } +} + +void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess) +{ + ASTString const& member = _memberAccess.memberName(); + if (auto funType = dynamic_cast(_memberAccess.annotation().type)) + if (funType->bound()) + { + solUnimplementedAssert(false, ""); + } + + switch (_memberAccess.expression().annotation().type->category()) + { + case Type::Category::Contract: + { + ContractType const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + if (type.isSuper()) + { + solUnimplementedAssert(false, ""); + } + // ordinary contract type + else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration) + { + u256 identifier; + if (auto const* variable = dynamic_cast(declaration)) + identifier = FunctionType(*variable).externalIdentifier(); + else if (auto const* function = dynamic_cast(declaration)) + identifier = FunctionType(*function).externalIdentifier(); + else + solAssert(false, "Contract member is neither variable nor function."); + + defineExpressionPart(_memberAccess, 1) << expressionAsType( + _memberAccess.expression(), + type.isPayable() ? *TypeProvider::payableAddress() : *TypeProvider::address() + ) << "\n"; + defineExpressionPart(_memberAccess, 2) << formatNumber(identifier) << "\n"; + } + else + solAssert(false, "Invalid member access in contract"); + break; + } + case Type::Category::Integer: + { + solAssert(false, "Invalid member access to integer"); + break; + } + case Type::Category::Address: + { + if (member == "balance") + defineExpression(_memberAccess) << + "balance(" << + expressionAsType(_memberAccess.expression(), *TypeProvider::address()) << + ")\n"; + else if (set{"send", "transfer"}.count(member)) + { + solAssert(dynamic_cast(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, ""); + defineExpression(_memberAccess) << + expressionAsType(_memberAccess.expression(), *TypeProvider::payableAddress()) << + "\n"; + } + else if (set{"call", "callcode", "delegatecall", "staticcall"}.count(member)) + defineExpression(_memberAccess) << + expressionAsType(_memberAccess.expression(), *TypeProvider::address()) << + "\n"; + else + solAssert(false, "Invalid member access to address"); + break; + } + case Type::Category::Function: + if (member == "selector") + { + solUnimplementedAssert(false, ""); + } + else + solAssert( + !!_memberAccess.expression().annotation().type->memberType(member), + "Invalid member access to function." + ); + break; + case Type::Category::Magic: + // we can ignore the kind of magic and only look at the name of the member + if (member == "coinbase") + defineExpression(_memberAccess) << "coinbase()\n"; + else if (member == "timestamp") + defineExpression(_memberAccess) << "timestamp()\n"; + else if (member == "difficulty") + defineExpression(_memberAccess) << "difficulty()\n"; + else if (member == "number") + defineExpression(_memberAccess) << "number()\n"; + else if (member == "gaslimit") + defineExpression(_memberAccess) << "gaslimit()\n"; + else if (member == "sender") + defineExpression(_memberAccess) << "caller()\n"; + else if (member == "value") + defineExpression(_memberAccess) << "callvalue()\n"; + else if (member == "origin") + defineExpression(_memberAccess) << "origin()\n"; + else if (member == "gasprice") + defineExpression(_memberAccess) << "gasprice()\n"; + else if (member == "data") + solUnimplementedAssert(false, ""); + else if (member == "sig") + defineExpression(_memberAccess) << + "and(calldataload(0), " << + formatNumber(u256(0xffffffff) << (256 - 32)) << + ")\n"; + else if (member == "gas") + solAssert(false, "Gas has been removed."); + else if (member == "blockhash") + solAssert(false, "Blockhash has been removed."); + else if (member == "creationCode" || member == "runtimeCode") + { + solUnimplementedAssert(false, ""); + } + else if (member == "name") + { + solUnimplementedAssert(false, ""); + } + else if (set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member)) + { + // no-op + } + else + solAssert(false, "Unknown magic member."); + break; + case Type::Category::Struct: + { + solUnimplementedAssert(false, ""); + } + case Type::Category::Enum: + { + EnumType const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + defineExpression(_memberAccess) << to_string(type.memberValue(_memberAccess.memberName())) << "\n"; + break; + } + case Type::Category::Array: + { + 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.utils(), + m_context.variable(_memberAccess.expression()), + *_memberAccess.annotation().type, + type + )); + + break; + case DataLocation::Memory: + defineExpression(_memberAccess) << + "mload(" << + m_context.variable(_memberAccess.expression()) << + ")\n"; + break; + } + break; + } + case Type::Category::FixedBytes: + { + auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type); + if (member == "length") + defineExpression(_memberAccess) << to_string(type.numBytes()); + else + solAssert(false, "Illegal fixed bytes member."); + break; + } + default: + solAssert(false, "Member access to unknown type."); + } +} + +bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) +{ + CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences}; + + yul::Statement modified = bodyCopier(_inlineAsm.operations()); + + solAssert(modified.type() == typeid(yul::Block), ""); + + m_code << yul::AsmPrinter()(boost::get(std::move(modified))) << "\n"; + return false; +} + + +void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess) +{ + Type const& baseType = *_indexAccess.baseExpression().annotation().type; + + if (baseType.category() == Type::Category::Mapping) + { + solAssert(_indexAccess.indexExpression(), "Index expression expected."); + + MappingType const& mappingType = dynamic_cast(baseType); + Type const& keyType = *_indexAccess.indexExpression()->annotation().type; + solAssert(keyType.sizeOnStack() <= 1, ""); + + string slot = m_context.newYulVariable(); + Whiskers templ("let := ( )\n"); + templ("slot", slot); + templ("indexAccess", m_utils.mappingIndexAccessFunction(mappingType, keyType)); + templ("base", m_context.variable(_indexAccess.baseExpression())); + if (keyType.sizeOnStack() == 0) + templ("key", ""); + else + templ("key", ", " + m_context.variable(*_indexAccess.indexExpression())); + m_code << templ.render(); + setLValue(_indexAccess, make_unique( + m_context.utils(), + slot, + 0, + *_indexAccess.annotation().type + )); + } + else if (baseType.category() == Type::Category::Array) + { + 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.utils(), + slot, + offset, + *_indexAccess.annotation().type + )); + + break; + } + case DataLocation::Memory: + { + string const memAddress = + m_utils.memoryArrayIndexAccessFunction(arrayType) + + "(" + + m_context.variable(_indexAccess.baseExpression()) + + ", " + + expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) + + ")"; + + setLValue(_indexAccess, make_unique( + m_context.utils(), + memAddress, + false, + *arrayType.baseType() + )); + break; + } + case DataLocation::CallData: + { + solUnimplemented("calldata not yet implemented!"); + + } + } + } + else if (baseType.category() == Type::Category::FixedBytes) + solUnimplementedAssert(false, ""); + else if (baseType.category() == Type::Category::TypeType) + { + solAssert(baseType.sizeOnStack() == 0, ""); + solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, ""); + // no-op - this seems to be a lone array type (`structType[];`) + } + else + solAssert(false, "Index access only allowed for mappings or arrays."); +} + +void IRGeneratorForStatements::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) + { + switch (magicVar->type()->category()) + { + case Type::Category::Contract: + if (dynamic_cast(*magicVar->type()).isSuper()) + solAssert(_identifier.name() == "super", ""); + else + { + solAssert(_identifier.name() == "this", ""); + defineExpression(_identifier) << "address()\n"; + } + break; + case Type::Category::Integer: + solAssert(_identifier.name() == "now", ""); + defineExpression(_identifier) << "timestamp()\n"; + break; + default: + break; + } + return; + } + else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) + defineExpression(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n"; + else if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + { + // TODO for the constant case, we have to be careful: + // If the value is visited twice, `defineExpression` is called twice on + // the same expression. + solUnimplementedAssert(!varDecl->isConstant(), ""); + unique_ptr lvalue; + if (m_context.isLocalVariable(*varDecl)) + lvalue = make_unique(m_context, *varDecl); + else if (m_context.isStateVariable(*varDecl)) + lvalue = make_unique(m_context, *varDecl); + else + solAssert(false, "Invalid variable kind."); + + setLValue(_identifier, move(lvalue)); + } + else if (auto contract = dynamic_cast(declaration)) + { + solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported."); + } + else if (dynamic_cast(declaration)) + { + // no-op + } + else if (dynamic_cast(declaration)) + { + // no-op + } + else if (dynamic_cast(declaration)) + { + // no-op + } + else + { + solAssert(false, "Identifier type not expected in expression context."); + } +} + +bool IRGeneratorForStatements::visit(Literal const& _literal) +{ + Type const& literalType = type(_literal); + + switch (literalType.category()) + { + case Type::Category::RationalNumber: + case Type::Category::Bool: + case Type::Category::Address: + defineExpression(_literal) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n"; + break; + case Type::Category::StringLiteral: + break; // will be done during conversion + default: + solUnimplemented("Only integer, boolean and string literals implemented for now."); + } + return false; +} + +void IRGeneratorForStatements::appendExternalFunctionCall( + FunctionCall const& _functionCall, + vector> const& _arguments +) +{ + FunctionType const& funType = dynamic_cast(type(_functionCall.expression())); + solAssert( + funType.takesArbitraryParameters() || + _arguments.size() == funType.parameterTypes().size(), "" + ); + solUnimplementedAssert(!funType.bound(), ""); + FunctionType::Kind funKind = funType.kind(); + + solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); + solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); + + bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; + bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; + bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); + + bool haveReturndatacopy = m_context.evmVersion().supportsReturndata(); + unsigned retSize = 0; + bool dynamicReturnSize = false; + TypePointers returnTypes; + if (!returnSuccessConditionAndReturndata) + { + if (haveReturndatacopy) + returnTypes = funType.returnParameterTypes(); + else + returnTypes = funType.returnParameterTypesWithoutDynamicTypes(); + + for (auto const& retType: returnTypes) + if (retType->isDynamicallyEncoded()) + { + solAssert(haveReturndatacopy, ""); + dynamicReturnSize = true; + retSize = 0; + break; + } + else if (retType->decodingType()) + retSize += retType->decodingType()->calldataEncodedSize(); + else + retSize += retType->calldataEncodedSize(); + } + + TypePointers argumentTypes; + string argumentString; + for (auto const& arg: _arguments) + { + argumentTypes.emplace_back(&type(*arg)); + string var = m_context.variable(*arg); + if (!var.empty()) + argumentString += ", " + move(var); + } + + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + if (!m_context.evmVersion().canOverchargeGasForCall()) + { + // Touch the end of the output area so that we do not pay for memory resize during the call + // (which we would have to subtract from the gas left) + // We could also just use MLOAD; POP right before the gas calculation, but the optimizer + // would remove that, so we use MSTORE here. + if (!funType.gasSet() && retSize > 0) + m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n"; + } + + ABIFunctions abi(m_context.evmVersion(), m_context.functionCollector()); + + solUnimplementedAssert(!funType.isBareCall(), ""); + Whiskers templ(R"( + + if iszero(extcodesize(
)) { revert(0, 0) } + + + let := + mstore(, ()) + let := (add(, 4) ) + + let := (,
, , , sub(, ), , ) + if iszero() { } + + + returndatacopy(, 0, returndatasize()) + + + mstore(, add(, and(add(, 0x1f), not(0x1f)))) + let := (, ) + )"); + templ("pos", m_context.newYulVariable()); + templ("end", m_context.newYulVariable()); + templ("result", m_context.newYulVariable()); + templ("freeMem", fetchFreeMem()); + templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4))); + templ("funId", m_context.variablePart(_functionCall.expression(), 2)); + + // If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place. + // Move arguments to memory, will not update the free memory pointer (but will update the memory + // pointer on the stack). + bool encodeInPlace = funType.takesArbitraryParameters() || funType.isBareCall(); + if (funType.kind() == FunctionType::Kind::ECRecover) + // This would be the only combination of padding and in-place encoding, + // but all parameters of ecrecover are value types anyway. + encodeInPlace = false; + bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall; + solUnimplementedAssert(!encodeInPlace, ""); + solUnimplementedAssert(!funType.padArguments(), ""); + templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall)); + templ("argumentString", argumentString); + + // Output data will replace input data, unless we have ECRecover (then, output + // area will be 32 bytes just before input area). + templ("retSize", to_string(retSize)); + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + if (isDelegateCall) + solAssert(!funType.valueSet(), "Value set for delegatecall"); + else if (useStaticCall) + solAssert(!funType.valueSet(), "Value set for staticcall"); + else if (funType.valueSet()) + templ("value", m_context.variablePart(_functionCall.expression(), 4)); + else + templ("value", "0"); + + // Check that the target contract exists (has code) for non-low-level calls. + bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall); + templ("checkExistence", checkExistence); + + if (funType.gasSet()) + templ("gas", m_context.variablePart(_functionCall.expression(), 3)); + else if (m_context.evmVersion().canOverchargeGasForCall()) + // Send all gas (requires tangerine whistle EVM) + templ("gas", "gas()"); + else + { + // send all gas except the amount needed to execute "SUB" and "CALL" + // @todo this retains too much gas for now, needs to be fine-tuned. + u256 gasNeededByCaller = eth::GasCosts::callGas(m_context.evmVersion()) + 10; + if (funType.valueSet()) + gasNeededByCaller += eth::GasCosts::callValueTransferGas; + if (!checkExistence) + gasNeededByCaller += eth::GasCosts::callNewAccountGas; // we never know + templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")"); + } + // Order is important here, STATICCALL might overlap with DELEGATECALL. + if (isDelegateCall) + templ("call", "delegatecall"); + else if (useStaticCall) + templ("call", "staticcall"); + else + templ("call", "call"); + + templ("forwardingRevert", m_utils.forwardingRevertFunction()); + + solUnimplementedAssert(!returnSuccessConditionAndReturndata, ""); + solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, ""); + solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, ""); + + templ("dynamicReturnSize", dynamicReturnSize); + // Always use the actual return length, and not our calculated expected length, if returndatacopy is supported. + // This ensures it can catch badly formatted input from external calls. + if (haveReturndatacopy) + templ("returnSize", "returndatasize()"); + else + templ("returnSize", to_string(retSize)); + templ("abiDecode", abi.tupleDecoder(returnTypes, true)); + templ("returns", !returnTypes.empty()); + templ("retVars", m_context.variable(_functionCall)); +} + +string IRGeneratorForStatements::fetchFreeMem() const +{ + return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")"; +} + +string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to) +{ + Type const& from = type(_expression); + if (from.sizeOnStack() == 0) + { + solAssert(from != _to, ""); + return m_utils.conversionFunction(from, _to) + "()"; + } + else + { + string varName = m_context.variable(_expression); + + if (from == _to) + return varName; + else + return m_utils.conversionFunction(from, _to) + "(" + std::move(varName) + ")"; + } +} + +ostream& IRGeneratorForStatements::defineExpression(Expression const& _expression) +{ + string vars = m_context.variable(_expression); + if (!vars.empty()) + m_code << "let " << move(vars) << " := "; + return m_code; +} + +ostream& IRGeneratorForStatements::defineExpressionPart(Expression const& _expression, size_t _part) +{ + return m_code << "let " << m_context.variablePart(_expression, _part) << " := "; +} + + +void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr) +{ + string func; + + if (_operation.getOperator() == Token::Not) + func = "iszero"; + else if (_operation.getOperator() == Token::BitNot) + func = "not"; + else + solAssert(false, "Invalid Token!"); + + defineExpression(_operation) << + m_utils.cleanupFunction(type(_expr)) << + "(" << + func << + "(" << + m_context.variable(_expr) << + ")" << + ")\n"; +} + +string IRGeneratorForStatements::binaryOperation( + langutil::Token _operator, + Type const& _type, + string const& _left, + string const& _right +) +{ + if (IntegerType const* type = dynamic_cast(&_type)) + { + string fun; + // TODO: Implement all operations for signed and unsigned types. + switch (_operator) + { + 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; + } + + solUnimplementedAssert(!fun.empty(), ""); + return fun + "(" + _left + ", " + _right + ")\n"; + } + else + solUnimplementedAssert(false, ""); + + return {}; +} + +void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp) +{ + langutil::Token const op = _binOp.getOperator(); + solAssert(op == Token::Or || op == Token::And, ""); + + _binOp.leftExpression().accept(*this); + + string value = m_context.variable(_binOp); + m_code << "let " << value << " := " << m_context.variable(_binOp.leftExpression()) << "\n"; + if (op == Token::Or) + m_code << "if iszero(" << value << ") {\n"; + else + m_code << "if " << value << " {\n"; + _binOp.rightExpression().accept(*this); + m_code << value << " := " + m_context.variable(_binOp.rightExpression()) << "\n"; + m_code << "}\n"; +} + +void IRGeneratorForStatements::setLValue(Expression const& _expression, unique_ptr _lvalue) +{ + solAssert(!m_currentLValue, ""); + + if (_expression.annotation().lValueRequested) + // Do not define the expression, so it cannot be used as value. + m_currentLValue = std::move(_lvalue); + else + defineExpression(_expression) << _lvalue->retrieveValue() << "\n"; +} + +void IRGeneratorForStatements::generateLoop( + Statement const& _body, + Expression const* _conditionExpression, + Statement const* _initExpression, + ExpressionStatement const* _loopExpression, + bool _isDoWhile +) +{ + string firstRun; + + if (_isDoWhile) + { + solAssert(_conditionExpression, "Expected condition for doWhile"); + firstRun = m_context.newYulVariable(); + m_code << "let " << firstRun << " := 1\n"; + } + + m_code << "for {\n"; + if (_initExpression) + _initExpression->accept(*this); + m_code << "} return_flag {\n"; + if (_loopExpression) + _loopExpression->accept(*this); + m_code << "}\n"; + m_code << "{\n"; + + if (_conditionExpression) + { + if (_isDoWhile) + m_code << "if iszero(" << firstRun << ") {\n"; + + _conditionExpression->accept(*this); + m_code << + "if iszero(" << + expressionAsType(*_conditionExpression, *TypeProvider::boolean()) << + ") { break }\n"; + + if (_isDoWhile) + m_code << "}\n" << firstRun << " := 0\n"; + } + + _body.accept(*this); + + m_code << "}\n"; + // Bubble up the return condition. + m_code << "if iszero(return_flag) { break }\n"; +} + +Type const& IRGeneratorForStatements::type(Expression const& _expression) +{ + solAssert(_expression.annotation().type, "Type of expression not set."); + return *_expression.annotation().type; +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h new file mode 100644 index 000000000..d64ab9e0a --- /dev/null +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -0,0 +1,116 @@ +/* + 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 . +*/ +/** + * Component that translates Solidity code into Yul at statement level and below. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ + +class IRGenerationContext; +class YulUtilFunctions; + +/** + * Component that translates Solidity's AST into Yul at statement level and below. + * It is an AST visitor that appends to an internal string buffer. + */ +class IRGeneratorForStatements: public ASTConstVisitor +{ +public: + IRGeneratorForStatements(IRGenerationContext& _context, YulUtilFunctions& _utils): + m_context(_context), + m_utils(_utils) + {} + + std::string code() const; + + /// Generates code to initialize the given state variable. + void initializeStateVar(VariableDeclaration const& _varDecl); + + void endVisit(VariableDeclarationStatement const& _variableDeclaration) override; + bool visit(Assignment const& _assignment) override; + bool visit(TupleExpression const& _tuple) override; + bool visit(IfStatement const& _ifStatement) override; + bool visit(ForStatement const& _forStatement) override; + bool visit(WhileStatement const& _whileStatement) override; + bool visit(Continue const& _continueStatement) override; + bool visit(Break const& _breakStatement) override; + void endVisit(Return const& _return) override; + void endVisit(UnaryOperation const& _unaryOperation) override; + bool visit(BinaryOperation const& _binOp) override; + void endVisit(FunctionCall const& _funCall) override; + void endVisit(MemberAccess const& _memberAccess) override; + bool visit(InlineAssembly const& _inlineAsm) override; + void endVisit(IndexAccess const& _indexAccess) override; + void endVisit(Identifier const& _identifier) override; + bool visit(Literal const& _literal) override; + +private: + /// Appends code to call an external function with the given arguments. + /// All involved expressions have already been visited. + void appendExternalFunctionCall( + FunctionCall const& _functionCall, + std::vector> const& _arguments + ); + + std::string fetchFreeMem() const; + + /// @returns a Yul expression representing the current value of @a _expression, + /// converted to type @a _to if it does not yet have that type. + std::string expressionAsType(Expression const& _expression, Type const& _to); + std::ostream& defineExpression(Expression const& _expression); + /// Defines only one of many variables corresponding to an expression. + /// We start counting at 1 instead of 0. + std::ostream& defineExpressionPart(Expression const& _expression, size_t _part); + + void appendAndOrOperatorCode(BinaryOperation const& _binOp); + void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr); + + /// @returns code to perform the given binary operation in the given type on the two values. + std::string binaryOperation( + langutil::Token _op, + Type const& _type, + std::string const& _left, + std::string const& _right + ); + + void setLValue(Expression const& _expression, std::unique_ptr _lvalue); + void generateLoop( + Statement const& _body, + Expression const* _conditionExpression, + Statement const* _initExpression = nullptr, + ExpressionStatement const* _loopExpression = nullptr, + bool _isDoWhile = false + ); + + static Type const& type(Expression const& _expression); + + std::ostringstream m_code; + IRGenerationContext& m_context; + YulUtilFunctions& m_utils; + std::unique_ptr m_currentLValue; +}; + +} +} diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp new file mode 100644 index 000000000..fb1bcdcbe --- /dev/null +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -0,0 +1,261 @@ +/* + 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 . +*/ +/** + * Generator for code that handles LValues. + */ + +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +IRLocalVariable::IRLocalVariable( + IRGenerationContext& _context, + VariableDeclaration const& _varDecl +): + IRLValue(_context.utils(), _varDecl.annotation().type), + m_variableName(_context.localVariableName(_varDecl)) +{ +} + +string IRLocalVariable::storeValue(string const& _value, Type const& _type) const +{ + solAssert(_type == *m_type, "Storing different types - not necessarily a problem."); + return m_variableName + " := " + _value + "\n"; +} + +string IRLocalVariable::setToZero() const +{ + return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type); +} + +IRStorageItem::IRStorageItem( + IRGenerationContext& _context, + VariableDeclaration const& _varDecl +): + IRStorageItem( + _context.utils(), + *_varDecl.annotation().type, + _context.storageLocationOfVariable(_varDecl) + ) +{ } + +IRStorageItem::IRStorageItem( + YulUtilFunctions _utils, + Type const& _type, + std::pair slot_offset +): + IRLValue(std::move(_utils), &_type), + m_slot(toCompactHexWithPrefix(slot_offset.first)), + m_offset(slot_offset.second) +{ +} + +IRStorageItem::IRStorageItem( + YulUtilFunctions _utils, + string _slot, + boost::variant _offset, + Type const& _type +): + IRLValue(std::move(_utils), &_type), + m_slot(std::move(_slot)), + m_offset(std::move(_offset)) +{ + solAssert(!m_offset.empty(), ""); + solAssert(!m_slot.empty(), ""); +} + +string IRStorageItem::retrieveValue() const +{ + if (!m_type->isValueType()) + return m_slot; + solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); + if (m_offset.type() == typeid(string)) + return + m_utils.readFromStorageDynamic(*m_type, false) + + "(" + + m_slot + + ", " + + boost::get(m_offset) + + ")"; + else if (m_offset.type() == typeid(unsigned)) + return + m_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(_sourceType == *m_type, "Different type, but might not be an error."); + + boost::optional offset; + + if (m_offset.type() == typeid(unsigned)) + offset = get(m_offset); + + return + m_utils.updateStorageValueFunction(*m_type, offset) + + "(" + + m_slot + + (m_offset.type() == typeid(string) ? + (", " + get(m_offset)) : + "" + ) + + ", " + + _value + + ")\n"; +} + +string IRStorageItem::setToZero() const +{ + return + m_utils.storageSetToZeroFunction(*m_type) + + "(" + + m_slot + + ", " + + ( + m_offset.type() == typeid(unsigned) ? + to_string(get(m_offset)) : + get(m_offset) + ) + + ")\n"; +} + +IRStorageArrayLength::IRStorageArrayLength( + YulUtilFunctions _utils, + string _slot, + Type const& _type, + ArrayType const& _arrayType +): + IRLValue(std::move(_utils), &_type), m_arrayType(_arrayType), m_slot(move(_slot)) +{ + solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!"); +} + +string IRStorageArrayLength::retrieveValue() const +{ + return m_utils.arrayLengthFunction(m_arrayType) + "(" + m_slot + ")"; +} + +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_utils.resizeDynamicArrayFunction(m_arrayType) + + "(" + + m_slot + + ", " + + _value + + ")\n"; +} + +string IRStorageArrayLength::setToZero() const +{ + return storeValue("0", *TypeProvider::uint256()); +} + +IRMemoryItem::IRMemoryItem( + YulUtilFunctions _utils, + std::string _address, + bool _byteArrayElement, + Type const& _type +): + IRLValue(std::move(_utils), &_type), + m_address(move(_address)), + m_byteArrayElement(_byteArrayElement) +{ } + +string IRMemoryItem::retrieveValue() const +{ + if (m_byteArrayElement) + return m_utils.cleanupFunction(*m_type) + + "(mload(" + + m_address + + "))"; + + if (m_type->isValueType()) + return m_utils.readFromMemory(*m_type) + + "(" + + m_address + + ")"; + else + return "mload(" + m_address + ")"; +} + +string IRMemoryItem::storeValue(string const& _value, Type const& _type) const +{ + if (!m_type->isValueType()) + { + solUnimplementedAssert(_type == *m_type, "Conversion not implemented for assignment to memory."); + + solAssert(m_type->sizeOnStack() == 1, ""); + solAssert(dynamic_cast(m_type), ""); + + return "mstore(" + m_address + ", " + _value + ")\n"; + } + + solAssert(_type.isValueType(), ""); + + string prepared = _value; + + // Exists to see if this case ever happens + solAssert(_type == *m_type, ""); + + if (_type != *m_type) + prepared = + m_utils.conversionFunction(_type, *m_type) + + "(" + + _value + + ")"; + else + prepared = + m_utils.cleanupFunction(*m_type) + + "(" + + _value + + ")"; + + if (m_byteArrayElement) + { + solAssert(*m_type == *TypeProvider::byte(), ""); + return "mstore8(" + m_address + ", byte(0, " + prepared + "))\n"; + } + else + return m_utils.writeToMemoryFunction(*m_type) + + "(" + + m_address + + ", " + + prepared + + ")\n"; +} + +string IRMemoryItem::setToZero() const +{ + return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type); +} diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h new file mode 100644 index 000000000..eeb5a890a --- /dev/null +++ b/libsolidity/codegen/ir/IRLValue.h @@ -0,0 +1,156 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Generator for code that handles LValues. + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class VariableDeclaration; +class IRGenerationContext; +class Type; +class ArrayType; + +/** + * Abstract class used to retrieve, delete and store data in LValues. + */ +class IRLValue +{ +protected: + explicit IRLValue(YulUtilFunctions _utils, Type const* _type = nullptr): + m_utils(std::move(_utils)), + m_type(_type) + {} + +public: + virtual ~IRLValue() = default; + /// @returns an expression to retrieve the value of the lvalue. + virtual std::string retrieveValue() const = 0; + /// Returns code that stores the value of @a _value (should be an identifier) + /// of type @a _type in the lvalue. Might perform type conversion. + virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0; + + /// Returns code that will reset the stored value to zero + virtual std::string setToZero() const = 0; +protected: + YulUtilFunctions mutable m_utils; + Type const* m_type; +}; + +class IRLocalVariable: public IRLValue +{ +public: + IRLocalVariable( + IRGenerationContext& _context, + VariableDeclaration const& _varDecl + ); + std::string retrieveValue() const override { return m_variableName; } + std::string storeValue(std::string const& _value, Type const& _type) const override; + + std::string setToZero() const override; +private: + std::string m_variableName; +}; + +class IRStorageItem: public IRLValue +{ +public: + IRStorageItem( + IRGenerationContext& _context, + VariableDeclaration const& _varDecl + ); + IRStorageItem( + YulUtilFunctions _utils, + std::string _slot, + boost::variant _offset, + Type const& _type + ); + std::string retrieveValue() const override; + std::string storeValue(std::string const& _value, Type const& _type) const override; + + std::string setToZero() const override; +private: + IRStorageItem( + YulUtilFunctions _utils, + 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( + YulUtilFunctions _utils, + 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; +}; + +class IRMemoryItem: public IRLValue +{ +public: + IRMemoryItem( + YulUtilFunctions _utils, + std::string _address, + bool _byteArrayElement, + Type const& _type + ); + std::string retrieveValue() const override; + std::string storeValue(std::string const& _value, Type const& _type) const override; + + std::string setToZero() const override; +private: + std::string const m_address; + bool m_byteArrayElement; +}; + +} +} diff --git a/libsolidity/codegen/ir/README.md b/libsolidity/codegen/ir/README.md new file mode 100644 index 000000000..468ecd269 --- /dev/null +++ b/libsolidity/codegen/ir/README.md @@ -0,0 +1,10 @@ +# The Solidity to Yul Code Generator + +This directory contains the new experimental code generator that +compiles Solidity to an intermediate representation in Yul +with EVM dialect. + +The main semantic differences to the legacy code generator are the following: + + - Arithmetic operations cause a failing assertion if the result is not in range. + - Resizing a storage array to a length larger than 2**64 causes a failing assertion. \ No newline at end of file diff --git a/libsolidity/formal/BMC.cpp b/libsolidity/formal/BMC.cpp new file mode 100644 index 000000000..0fcccdd75 --- /dev/null +++ b/libsolidity/formal/BMC.cpp @@ -0,0 +1,884 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +BMC::BMC(smt::EncodingContext& _context, ErrorReporter& _errorReporter, map const& _smtlib2Responses): + SMTEncoder(_context), + m_outerErrorReporter(_errorReporter), + m_interface(make_shared(_smtlib2Responses)) +{ +#if defined (HAVE_Z3) || defined (HAVE_CVC4) + if (!_smtlib2Responses.empty()) + m_errorReporter.warning( + "SMT-LIB2 query responses were given in the auxiliary input, " + "but this Solidity binary uses an SMT solver (Z3/CVC4) directly." + "These responses will be ignored." + "Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses." + ); +#endif +} + +void BMC::analyze(SourceUnit const& _source, set _safeAssertions) +{ + solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), ""); + + m_safeAssertions += move(_safeAssertions); + m_context.setSolver(m_interface); + m_context.clear(); + m_context.setAssertionAccumulation(true); + m_variableUsage.setFunctionInlining(true); + + _source.accept(*this); + + solAssert(m_interface->solvers() > 0, ""); + // If this check is true, Z3 and CVC4 are not available + // and the query answers were not provided, since SMTPortfolio + // guarantees that SmtLib2Interface is the first solver. + if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1) + { + if (!m_noSolverWarning) + { + m_noSolverWarning = true; + m_outerErrorReporter.warning( + SourceLocation(), + "BMC analysis was not possible since no integrated SMT solver (Z3 or CVC4) was found." + ); + } + } + else + m_outerErrorReporter.append(m_errorReporter.errors()); + + m_errorReporter.clear(); +} + +bool BMC::shouldInlineFunctionCall(FunctionCall const& _funCall) +{ + FunctionDefinition const* funDef = functionCallToDefinition(_funCall); + if (!funDef || !funDef->isImplemented()) + return false; + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (funType.kind() == FunctionType::Kind::External) + { + auto memberAccess = dynamic_cast(&_funCall.expression()); + if (!memberAccess) + return false; + + auto identifier = dynamic_cast(&memberAccess->expression()); + if (!( + identifier && + identifier->name() == "this" && + identifier->annotation().referencedDeclaration && + dynamic_cast(identifier->annotation().referencedDeclaration) + )) + return false; + } + else if (funType.kind() != FunctionType::Kind::Internal) + return false; + + return true; +} + +/// AST visitors. + +bool BMC::visit(ContractDefinition const& _contract) +{ + SMTEncoder::visit(_contract); + + /// Check targets created by state variable initialization. + smt::Expression constraints = m_context.assertions(); + checkVerificationTargets(constraints); + m_verificationTargets.clear(); + + return true; +} + +void BMC::endVisit(ContractDefinition const& _contract) +{ + SMTEncoder::endVisit(_contract); +} + +bool BMC::visit(FunctionDefinition const& _function) +{ + auto contract = dynamic_cast(_function.scope()); + solAssert(contract, ""); + solAssert(m_currentContract, ""); + auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts; + if (find(hierarchy.begin(), hierarchy.end(), contract) == hierarchy.end()) + initializeStateVariables(*contract); + + if (m_callStack.empty()) + reset(); + + /// Already visits the children. + SMTEncoder::visit(_function); + + return false; +} + +void BMC::endVisit(FunctionDefinition const& _function) +{ + if (isRootFunction()) + { + smt::Expression constraints = m_context.assertions(); + checkVerificationTargets(constraints); + m_verificationTargets.clear(); + } + + SMTEncoder::endVisit(_function); +} + +bool BMC::visit(IfStatement const& _node) +{ + // This check needs to be done in its own context otherwise + // constraints from the If body might influence it. + m_context.pushSolver(); + _node.condition().accept(*this); + + // We ignore called functions here because they have + // specific input values. + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + m_context.popSolver(); + + SMTEncoder::visit(_node); + + return false; +} + +// Here we consider the execution of two branches: +// Branch 1 assumes the loop condition to be true and executes the loop once, +// after resetting touched variables. +// Branch 2 assumes the loop condition to be false and skips the loop after +// visiting the condition (it might contain side-effects, they need to be considered) +// and does not erase knowledge. +// If the loop is a do-while, condition side-effects are lost since the body, +// executed once before the condition, might reassign variables. +// Variables touched by the loop are merged with Branch 2. +bool BMC::visit(WhileStatement const& _node) +{ + auto indicesBeforeLoop = copyVariableIndices(); + auto touchedVars = touchedVariables(_node); + m_context.resetVariables(touchedVars); + decltype(indicesBeforeLoop) indicesAfterLoop; + if (_node.isDoWhile()) + { + indicesAfterLoop = visitBranch(&_node.body()); + // TODO the assertions generated in the body should still be active in the condition + _node.condition().accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + } + else + { + _node.condition().accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(_node.condition()), + &_node.condition() + ); + + indicesAfterLoop = visitBranch(&_node.body(), expr(_node.condition())); + } + + // We reset the execution to before the loop + // and visit the condition in case it's not a do-while. + // A do-while's body might have non-precise information + // in its first run about variables that are touched. + resetVariableIndices(indicesBeforeLoop); + if (!_node.isDoWhile()) + _node.condition().accept(*this); + + mergeVariables(touchedVars, expr(_node.condition()), indicesAfterLoop, copyVariableIndices()); + + m_loopExecutionHappened = true; + return false; +} + +// Here we consider the execution of two branches similar to WhileStatement. +bool BMC::visit(ForStatement const& _node) +{ + if (_node.initializationExpression()) + _node.initializationExpression()->accept(*this); + + auto indicesBeforeLoop = copyVariableIndices(); + + // Do not reset the init expression part. + auto touchedVars = touchedVariables(_node.body()); + if (_node.condition()) + touchedVars += touchedVariables(*_node.condition()); + if (_node.loopExpression()) + touchedVars += touchedVariables(*_node.loopExpression()); + + m_context.resetVariables(touchedVars); + + if (_node.condition()) + { + _node.condition()->accept(*this); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(*_node.condition()), + _node.condition() + ); + } + + m_context.pushSolver(); + if (_node.condition()) + m_context.addAssertion(expr(*_node.condition())); + _node.body().accept(*this); + if (_node.loopExpression()) + _node.loopExpression()->accept(*this); + m_context.popSolver(); + + auto indicesAfterLoop = copyVariableIndices(); + // We reset the execution to before the loop + // and visit the condition. + resetVariableIndices(indicesBeforeLoop); + if (_node.condition()) + _node.condition()->accept(*this); + + auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true); + mergeVariables(touchedVars, forCondition, indicesAfterLoop, copyVariableIndices()); + + m_loopExecutionHappened = true; + return false; +} + +void BMC::endVisit(UnaryOperation const& _op) +{ + SMTEncoder::endVisit(_op); + + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + + switch (_op.getOperator()) + { + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + expr(_op), + &_op + ); + break; + case Token::Sub: // - + if (_op.annotation().type->category() == Type::Category::Integer) + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + expr(_op), + &_op + ); + break; + default: + break; + } +} + +void BMC::endVisit(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + { + SMTEncoder::endVisit(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Require: + visitRequire(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + SMTEncoder::endVisit(_funCall); + internalOrExternalFunctionCall(_funCall); + break; + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + SMTEncoder::endVisit(_funCall); + abstractFunctionCall(_funCall); + break; + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: + { + SMTEncoder::endVisit(_funCall); + auto value = _funCall.arguments().front(); + solAssert(value, ""); + smt::Expression thisBalance = m_context.balance(); + + addVerificationTarget( + VerificationTarget::Type::Balance, + thisBalance < expr(*value), + &_funCall + ); + break; + } + default: + SMTEncoder::endVisit(_funCall); + break; + } +} + +/// Visitor helpers. + +void BMC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + addVerificationTarget( + VerificationTarget::Type::Assert, + expr(*args.front()), + &_funCall + ); +} + +void BMC::visitRequire(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() >= 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + if (isRootFunction()) + addVerificationTarget( + VerificationTarget::Type::ConstantCondition, + expr(*args.front()), + args.front().get() + ); +} + +void BMC::inlineFunctionCall(FunctionCall const& _funCall) +{ + solAssert(shouldInlineFunctionCall(_funCall), ""); + FunctionDefinition const* funDef = functionCallToDefinition(_funCall); + solAssert(funDef, ""); + + if (visitedFunction(funDef)) + { + auto const& returnParams = funDef->returnParameters(); + for (auto param: returnParams) + { + m_context.newValue(*param); + m_context.setUnknownValue(*param); + } + + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not support recursive function calls.", + SecondarySourceLocation().append("Starting from function:", funDef->location()) + ); + } + else + { + vector funArgs; + Expression const* calledExpr = &_funCall.expression(); + auto const& funType = dynamic_cast(calledExpr->annotation().type); + solAssert(funType, ""); + + if (funType->bound()) + { + auto const& boundFunction = dynamic_cast(calledExpr); + solAssert(boundFunction, ""); + funArgs.push_back(expr(boundFunction->expression())); + } + + for (auto arg: _funCall.arguments()) + funArgs.push_back(expr(*arg)); + initializeFunctionCallParameters(*funDef, funArgs); + + // The reason why we need to pushCallStack here instead of visit(FunctionDefinition) + // is that there we don't have `_funCall`. + pushCallStack({funDef, &_funCall}); + // If an internal function is called to initialize + // a state variable. + if (m_callStack.empty()) + initFunction(*funDef); + funDef->accept(*this); + } + + createReturnedExpressions(_funCall); +} + +void BMC::abstractFunctionCall(FunctionCall const& _funCall) +{ + vector smtArguments; + for (auto const& arg: _funCall.arguments()) + smtArguments.push_back(expr(*arg)); + defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments)); + m_uninterpretedTerms.insert(&_funCall); + setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, m_context); +} + +void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall) +{ + auto const& funType = dynamic_cast(*_funCall.expression().annotation().type); + if (shouldInlineFunctionCall(_funCall)) + inlineFunctionCall(_funCall); + else if (funType.kind() == FunctionType::Kind::Internal) + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not yet implement this type of function call." + ); + else + { + m_externalFunctionCallHappened = true; + resetStateVariables(); + resetStorageReferences(); + } +} + +pair BMC::arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& _expression +) +{ + if (_op == Token::Div || _op == Token::Mod) + addVerificationTarget( + VerificationTarget::Type::DivByZero, + _right, + &_expression + ); + + auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression); + + addVerificationTarget( + VerificationTarget::Type::UnderOverflow, + values.second, + &_expression + ); + return values; +} + +void BMC::resetStorageReferences() +{ + m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); +} + +void BMC::reset() +{ + m_externalFunctionCallHappened = false; + m_loopExecutionHappened = false; +} + +pair, vector> BMC::modelExpressions() +{ + vector expressionsToEvaluate; + vector expressionNames; + for (auto const& var: m_context.variables()) + if (var.first->type()->isValueType()) + { + expressionsToEvaluate.emplace_back(currentValue(*var.first)); + expressionNames.push_back(var.first->name()); + } + for (auto const& var: m_context.globalSymbols()) + { + auto const& type = var.second->type(); + if ( + type->isValueType() && + smt::smtKind(type->category()) != smt::Kind::Function + ) + { + expressionsToEvaluate.emplace_back(var.second->currentValue()); + expressionNames.push_back(var.first); + } + } + for (auto const& uf: m_uninterpretedTerms) + if (uf->annotation().type->isValueType()) + { + expressionsToEvaluate.emplace_back(expr(*uf)); + expressionNames.push_back(uf->location().text()); + } + + return {expressionsToEvaluate, expressionNames}; +} + +/// Verification targets. + +void BMC::checkVerificationTargets(smt::Expression const& _constraints) +{ + for (auto& target: m_verificationTargets) + checkVerificationTarget(target, _constraints); +} + +void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints) +{ + switch (_target.type) + { + case VerificationTarget::Type::ConstantCondition: + checkConstantCondition(_target); + break; + case VerificationTarget::Type::Underflow: + checkUnderflow(_target, _constraints); + break; + case VerificationTarget::Type::Overflow: + checkOverflow(_target, _constraints); + break; + case VerificationTarget::Type::UnderOverflow: + checkUnderflow(_target, _constraints); + checkOverflow(_target, _constraints); + break; + case VerificationTarget::Type::DivByZero: + checkDivByZero(_target); + break; + case VerificationTarget::Type::Balance: + checkBalance(_target); + break; + case VerificationTarget::Type::Assert: + checkAssert(_target); + break; + default: + solAssert(false, ""); + } +} + +void BMC::checkConstantCondition(VerificationTarget& _target) +{ + checkBooleanNotConstant( + *_target.expression, + _target.constraints, + _target.value, + _target.callStack, + "Condition is always $VALUE." + ); +} + +void BMC::checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints) +{ + solAssert( + _target.type == VerificationTarget::Type::Underflow || + _target.type == VerificationTarget::Type::UnderOverflow, + "" + ); + auto intType = dynamic_cast(_target.expression->annotation().type); + solAssert(intType, ""); + checkCondition( + _target.constraints && _constraints && _target.value < smt::minValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")", + "", + &_target.value + ); +} + +void BMC::checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints) +{ + solAssert( + _target.type == VerificationTarget::Type::Overflow || + _target.type == VerificationTarget::Type::UnderOverflow, + "" + ); + auto intType = dynamic_cast(_target.expression->annotation().type); + solAssert(intType, ""); + checkCondition( + _target.constraints && _constraints && _target.value > smt::maxValue(*intType), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")", + "", + &_target.value + ); +} + +void BMC::checkDivByZero(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::DivByZero, ""); + checkCondition( + _target.constraints && (_target.value == 0), + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Division by zero", + "", + &_target.value + ); +} + +void BMC::checkBalance(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::Balance, ""); + checkCondition( + _target.constraints && _target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Insufficient funds", + "address(this).balance" + ); +} + +void BMC::checkAssert(VerificationTarget& _target) +{ + solAssert(_target.type == VerificationTarget::Type::Assert, ""); + if (!m_safeAssertions.count(_target.expression)) + checkCondition( + _target.constraints && !_target.value, + _target.callStack, + _target.modelExpressions, + _target.expression->location(), + "Assertion violation" + ); +} + +void BMC::addVerificationTarget( + VerificationTarget::Type _type, + smt::Expression const& _value, + Expression const* _expression +) +{ + VerificationTarget target{ + _type, + _value, + currentPathConditions() && m_context.assertions(), + _expression, + m_callStack, + modelExpressions() + }; + if (_type == VerificationTarget::Type::ConstantCondition) + checkVerificationTarget(target); + else + m_verificationTargets.emplace_back(move(target)); +} + +/// Solving. + +void BMC::checkCondition( + smt::Expression _condition, + vector const& callStack, + pair, vector> const& _modelExpressions, + SourceLocation const& _location, + string const& _description, + string const& _additionalValueName, + smt::Expression const* _additionalValue +) +{ + m_interface->push(); + m_interface->addAssertion(_condition); + + vector expressionsToEvaluate; + vector expressionNames; + tie(expressionsToEvaluate, expressionNames) = _modelExpressions; + if (callStack.size()) + if (_additionalValue) + { + expressionsToEvaluate.emplace_back(*_additionalValue); + expressionNames.push_back(_additionalValueName); + } + smt::CheckResult result; + vector values; + tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); + + string extraComment = SMTEncoder::extraComment(); + if (m_loopExecutionHappened) + extraComment += + "\nNote that some information is erased after the execution of loops.\n" + "You can re-introduce information using require()."; + if (m_externalFunctionCallHappened) + extraComment+= + "\nNote that external function calls are not inlined," + " even if the source code of the function is available." + " This is due to the possibility that the actual called contract" + " has the same ABI but implements the function differently."; + + SecondarySourceLocation secondaryLocation{}; + secondaryLocation.append(extraComment, SourceLocation{}); + + switch (result) + { + case smt::CheckResult::SATISFIABLE: + { + std::ostringstream message; + message << _description << " happens here"; + if (callStack.size()) + { + std::ostringstream modelMessage; + modelMessage << " for:\n"; + solAssert(values.size() == expressionNames.size(), ""); + map sortedModel; + for (size_t i = 0; i < values.size(); ++i) + if (expressionsToEvaluate.at(i).name != values.at(i)) + sortedModel[expressionNames.at(i)] = values.at(i); + + for (auto const& eval: sortedModel) + modelMessage << " " << eval.first << " = " << eval.second << "\n"; + m_errorReporter.warning( + _location, + message.str(), + SecondarySourceLocation().append(modelMessage.str(), SourceLocation{}) + .append(SMTEncoder::callStackMessage(callStack)) + .append(move(secondaryLocation)) + ); + } + else + { + message << "."; + m_errorReporter.warning(_location, message.str(), secondaryLocation); + } + break; + } + case smt::CheckResult::UNSATISFIABLE: + break; + case smt::CheckResult::UNKNOWN: + m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation); + break; + case smt::CheckResult::CONFLICTING: + m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); + break; + case smt::CheckResult::ERROR: + m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); + break; + } + + m_interface->pop(); +} + +void BMC::checkBooleanNotConstant( + Expression const& _condition, + smt::Expression const& _constraints, + smt::Expression const& _value, + vector const& _callStack, + string const& _description +) +{ + // Do not check for const-ness if this is a constant. + if (dynamic_cast(&_condition)) + return; + + m_interface->push(); + m_interface->addAssertion(_constraints && _value); + auto positiveResult = checkSatisfiable(); + m_interface->pop(); + + m_interface->push(); + m_interface->addAssertion(_constraints && !_value); + auto negatedResult = checkSatisfiable(); + m_interface->pop(); + + if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) + m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); + else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING) + m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound."); + else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE) + { + // everything fine. + } + else if (positiveResult == smt::CheckResult::UNKNOWN || negatedResult == smt::CheckResult::UNKNOWN) + { + // can't do anything. + } + else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE) + m_errorReporter.warning(_condition.location(), "Condition unreachable.", SMTEncoder::callStackMessage(_callStack)); + else + { + string value; + if (positiveResult == smt::CheckResult::SATISFIABLE) + { + solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, ""); + value = "true"; + } + else + { + solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, ""); + solAssert(negatedResult == smt::CheckResult::SATISFIABLE, ""); + value = "false"; + } + m_errorReporter.warning( + _condition.location(), + boost::algorithm::replace_all_copy(_description, "$VALUE", value), + SMTEncoder::callStackMessage(_callStack) + ); + } +} + +pair> +BMC::checkSatisfiableAndGenerateModel(vector const& _expressionsToEvaluate) +{ + smt::CheckResult result; + vector values; + try + { + tie(result, values) = m_interface->check(_expressionsToEvaluate); + } + catch (smt::SolverError const& _e) + { + string description("Error querying SMT solver"); + if (_e.comment()) + description += ": " + *_e.comment(); + m_errorReporter.warning(description); + result = smt::CheckResult::ERROR; + } + + for (string& value: values) + { + try + { + // Parse and re-format nicely + value = formatNumberReadable(bigint(value)); + } + catch (...) { } + } + + return make_pair(result, values); +} + +smt::CheckResult BMC::checkSatisfiable() +{ + return checkSatisfiableAndGenerateModel({}).first; +} + diff --git a/libsolidity/formal/BMC.h b/libsolidity/formal/BMC.h new file mode 100644 index 000000000..432050e78 --- /dev/null +++ b/libsolidity/formal/BMC.h @@ -0,0 +1,185 @@ +/* + 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 implements an SMT-based Bounded Model Checker (BMC). + * Traverses the AST such that: + * - Loops are unrolled + * - Internal function calls are inlined + * Creates verification targets for: + * - Underflow/Overflow + * - Constant conditions + * - Assertions + */ + +#pragma once + + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ + +class BMC: public SMTEncoder +{ +public: + BMC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + + void analyze(SourceUnit const& _sources, std::set _safeAssertions); + + /// This is used if the SMT solver is not directly linked into this binary. + /// @returns a list of inputs to the SMT solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries() { return m_interface->unhandledQueries(); } + + /// @returns true if _funCall should be inlined, otherwise false. + static bool shouldInlineFunctionCall(FunctionCall const& _funCall); + + std::shared_ptr solver() { return m_interface; } + +private: + /// AST visitors. + /// Only nodes that lead to verification targets being built + /// or checked are visited. + //@{ + bool visit(ContractDefinition const& _node) override; + void endVisit(ContractDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const& _node) override; + bool visit(ForStatement const& _node) override; + void endVisit(UnaryOperation const& _node) override; + void endVisit(FunctionCall const& _node) override; + //@} + + /// Visitor helpers. + //@{ + void visitAssert(FunctionCall const& _funCall); + void visitRequire(FunctionCall const& _funCall); + /// Visits the FunctionDefinition of the called function + /// if available and inlines the return value. + void inlineFunctionCall(FunctionCall const& _funCall); + /// Creates an uninterpreted function call. + void abstractFunctionCall(FunctionCall const& _funCall); + /// Inlines if the function call is internal or external to `this`. + /// Erases knowledge about state variables if external. + void internalOrExternalFunctionCall(FunctionCall const& _funCall); + + /// Creates underflow/overflow verification targets. + std::pair arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& _expression + ) override; + + void resetStorageReferences(); + void reset(); + + std::pair, std::vector> modelExpressions(); + //@} + + /// Verification targets. + //@{ + struct VerificationTarget + { + enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type; + smt::Expression value; + smt::Expression constraints; + Expression const* expression; + std::vector callStack; + std::pair, std::vector> modelExpressions; + }; + + void checkVerificationTargets(smt::Expression const& _constraints); + void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true)); + void checkConstantCondition(VerificationTarget& _target); + void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints); + void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints); + void checkDivByZero(VerificationTarget& _target); + void checkBalance(VerificationTarget& _target); + void checkAssert(VerificationTarget& _target); + void addVerificationTarget( + VerificationTarget::Type _type, + smt::Expression const& _value, + Expression const* _expression + ); + //@} + + /// Solver related. + //@{ + /// Check that a condition can be satisfied. + void checkCondition( + smt::Expression _condition, + std::vector const& callStack, + std::pair, std::vector> const& _modelExpressions, + langutil::SourceLocation const& _location, + std::string const& _description, + std::string const& _additionalValueName = "", + smt::Expression const* _additionalValue = nullptr + ); + /// Checks that a boolean condition is not constant. Do not warn if the expression + /// is a literal constant. + /// @param _description the warning string, $VALUE will be replaced by the constant value. + void checkBooleanNotConstant( + Expression const& _condition, + smt::Expression const& _constraints, + smt::Expression const& _value, + std::vector const& _callStack, + std::string const& _description + ); + std::pair> + checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate); + + smt::CheckResult checkSatisfiable(); + //@} + + /// Flags used for better warning messages. + bool m_loopExecutionHappened = false; + bool m_externalFunctionCallHappened = false; + + /// ErrorReporter that comes from CompilerStack. + langutil::ErrorReporter& m_outerErrorReporter; + + std::vector m_verificationTargets; + + /// Assertions that are known to be safe. + std::set m_safeAssertions; + + std::shared_ptr m_interface; +}; + +} +} diff --git a/libsolidity/formal/CHC.cpp b/libsolidity/formal/CHC.cpp new file mode 100644 index 000000000..867be77dd --- /dev/null +++ b/libsolidity/formal/CHC.cpp @@ -0,0 +1,682 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#ifdef HAVE_Z3 +#include +#endif + +#include + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +CHC::CHC(smt::EncodingContext& _context, ErrorReporter& _errorReporter): + SMTEncoder(_context), +#ifdef HAVE_Z3 + m_interface(make_shared()), +#endif + m_outerErrorReporter(_errorReporter) +{ +} + +void CHC::analyze(SourceUnit const& _source) +{ + solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), ""); + +#ifdef HAVE_Z3 + auto z3Interface = dynamic_pointer_cast(m_interface); + solAssert(z3Interface, ""); + m_context.setSolver(z3Interface->z3Interface()); + m_context.clear(); + m_context.setAssertionAccumulation(false); + m_variableUsage.setFunctionInlining(false); + + _source.accept(*this); +#endif +} + +bool CHC::visit(ContractDefinition const& _contract) +{ + if (!shouldVisit(_contract)) + return false; + + reset(); + + if (!SMTEncoder::visit(_contract)) + return false; + + m_stateVariables = _contract.stateVariablesIncludingInherited(); + + for (auto const& var: m_stateVariables) + // SMT solvers do not support function types as arguments. + if (var->type()->category() == Type::Category::Function) + m_stateSorts.push_back(make_shared(smt::Kind::Int)); + else + m_stateSorts.push_back(smt::smtSort(*var->type())); + + clearIndices(); + + string interfaceName = "interface_" + _contract.name() + "_" + to_string(_contract.id()); + m_interfacePredicate = createSymbolicBlock(interfaceSort(), interfaceName); + + // TODO create static instances for Bool/Int sorts in SolverInterface. + auto boolSort = make_shared(smt::Kind::Bool); + auto errorFunctionSort = make_shared( + vector(), + boolSort + ); + m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error"); + + // If the contract has a constructor it is handled as a function. + // Otherwise we zero-initialize all state vars. + // TODO take into account state vars init values. + if (!_contract.constructor()) + { + string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id()); + m_constructorPredicate = createSymbolicBlock(constructorSort(), constructorName); + smt::Expression constructorPred = (*m_constructorPredicate)({}); + addRule(constructorPred, constructorName); + + for (auto const& var: m_stateVariables) + { + auto const& symbVar = m_context.variable(*var); + symbVar->increaseIndex(); + m_interface->declareVariable(symbVar->currentName(), *symbVar->sort()); + m_context.setZeroValue(*symbVar); + } + + connectBlocks(constructorPred, interface()); + } + + return true; +} + +void CHC::endVisit(ContractDefinition const& _contract) +{ + if (!shouldVisit(_contract)) + return; + + for (unsigned i = 0; i < m_verificationTargets.size(); ++i) + { + auto const& target = m_verificationTargets.at(i); + auto errorAppl = error(i + 1); + if (query(errorAppl, target->location())) + m_safeAssertions.insert(target); + } + + SMTEncoder::endVisit(_contract); +} + +bool CHC::visit(FunctionDefinition const& _function) +{ + if (!shouldVisit(_function)) + return false; + + solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented"); + m_currentFunction = &_function; + + initFunction(_function); + + // Store the constraints related to variable initialization. + smt::Expression const& initAssertions = m_context.assertions(); + m_context.pushSolver(); + + solAssert(m_functionBlocks == 0, ""); + + createBlock(m_currentFunction); + createBlock(&m_currentFunction->body(), "block_"); + + auto functionPred = predicate(m_currentFunction); + auto bodyPred = predicate(&m_currentFunction->body()); + + connectBlocks(interface(), functionPred); + connectBlocks(functionPred, bodyPred); + + m_context.popSolver(); + + pushBlock(&m_currentFunction->body()); + + // We need to re-add the constraints that were created for initialization of variables. + m_context.addAssertion(initAssertions); + + SMTEncoder::visit(*m_currentFunction); + + return false; +} + +void CHC::endVisit(FunctionDefinition const& _function) +{ + if (!shouldVisit(_function)) + return; + + solAssert(m_currentFunction == &_function, "Inlining internal function calls not yet implemented"); + + // Function Exit block. + createBlock(m_currentFunction); + connectBlocks(m_path.back(), predicate(&_function)); + + // Rule FunctionExit -> Interface, uses no constraints. + clearIndices(); + m_context.pushSolver(); + connectBlocks(predicate(&_function), interface()); + m_context.popSolver(); + + m_currentFunction = nullptr; + solAssert(m_path.size() == m_functionBlocks, ""); + while (m_functionBlocks > 0) + popBlock(); + + solAssert(m_path.empty(), ""); + + SMTEncoder::endVisit(_function); +} + +bool CHC::visit(IfStatement const& _if) +{ + solAssert(m_currentFunction, ""); + + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + SMTEncoder::visit(_if); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(WhileStatement const& _while) +{ + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + + if (_while.isDoWhile()) + _while.body().accept(*this); + + visitLoop( + _while, + &_while.condition(), + _while.body(), + nullptr + ); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +bool CHC::visit(ForStatement const& _for) +{ + bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen; + m_unknownFunctionCallSeen = false; + + solAssert(m_currentFunction, ""); + + if (auto init = _for.initializationExpression()) + init->accept(*this); + + visitLoop( + _for, + _for.condition(), + _for.body(), + _for.loopExpression() + ); + + if (m_unknownFunctionCallSeen) + eraseKnowledge(); + + m_unknownFunctionCallSeen = unknownFunctionCallWasSeen; + + return false; +} + +void CHC::endVisit(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + { + SMTEncoder::endVisit(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + SMTEncoder::endVisit(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + SMTEncoder::endVisit(_funCall); + unknownFunctionCall(_funCall); + break; + default: + SMTEncoder::endVisit(_funCall); + break; + } + + createReturnedExpressions(_funCall); +} + +void CHC::endVisit(Break const&) +{ + solAssert(m_breakDest, ""); + m_breakSeen = true; +} + +void CHC::endVisit(Continue const&) +{ + solAssert(m_continueDest, ""); + m_continueSeen = true; +} + +void CHC::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + + solAssert(!m_path.empty(), ""); + + createErrorBlock(); + + smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue()); + connectBlocks(m_path.back(), error(), currentPathConditions() && assertNeg); + + m_verificationTargets.push_back(&_funCall); +} + +void CHC::unknownFunctionCall(FunctionCall const&) +{ + /// Function calls are not handled at the moment, + /// so always erase knowledge. + /// TODO remove when function calls get predicates/blocks. + eraseKnowledge(); + + /// Used to erase outer scope knowledge in loops and ifs. + /// TODO remove when function calls get predicates/blocks. + m_unknownFunctionCallSeen = true; +} + +void CHC::visitLoop( + BreakableStatement const& _loop, + Expression const* _condition, + Statement const& _body, + ASTNode const* _postLoop +) +{ + bool breakWasSeen = m_breakSeen; + bool continueWasSeen = m_continueSeen; + m_breakSeen = false; + m_continueSeen = false; + + solAssert(m_currentFunction, ""); + auto const& functionBody = m_currentFunction->body(); + + createBlock(&_loop, "loop_header_"); + createBlock(&_body, "loop_body_"); + createBlock(&functionBody, "block_"); + + connectBlocks(m_path.back(), predicate(&_loop)); + + // We need to save the next block here because new blocks + // might be created inside the loop body. + // This will be m_path.back() in the end of this function. + pushBlock(&functionBody); + + smt::Expression loopHeader = predicate(&_loop); + pushBlock(&_loop); + + if (_condition) + _condition->accept(*this); + auto condition = _condition ? expr(*_condition) : smt::Expression(true); + + connectBlocks(loopHeader, predicate(&_body), condition); + connectBlocks(loopHeader, predicate(&functionBody), !condition); + + // Loop body visit. + pushBlock(&_body); + + m_breakDest = &functionBody; + m_continueDest = _postLoop ? _postLoop : &_loop; + + auto functionBlocks = m_functionBlocks; + _body.accept(*this); + if (_postLoop) + { + createBlock(_postLoop, "loop_post_"); + connectBlocks(m_path.back(), predicate(_postLoop)); + pushBlock(_postLoop); + _postLoop->accept(*this); + } + + // Back edge. + connectBlocks(m_path.back(), predicate(&_loop)); + + // Pop all function blocks created by nested inner loops + // to adjust the assertion context. + for (unsigned i = m_functionBlocks; i > functionBlocks; --i) + popBlock(); + m_functionBlocks = functionBlocks; + + // Loop body + popBlock(); + // Loop header + popBlock(); + + // New function block starts with indices = 0 + clearIndices(); + + if (m_breakSeen || m_continueSeen) + { + eraseKnowledge(); + m_context.resetVariables([](VariableDeclaration const&) { return true; }); + } + + m_breakSeen = breakWasSeen; + m_continueSeen = continueWasSeen; +} + +void CHC::reset() +{ + m_stateSorts.clear(); + m_stateVariables.clear(); + m_verificationTargets.clear(); + m_safeAssertions.clear(); + m_unknownFunctionCallSeen = false; + m_breakSeen = false; + m_continueSeen = false; +} + +void CHC::eraseKnowledge() +{ + resetStateVariables(); + m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); +} + +bool CHC::shouldVisit(ContractDefinition const& _contract) const +{ + if ( + _contract.isLibrary() || + _contract.isInterface() + ) + return false; + return true; +} + +bool CHC::shouldVisit(FunctionDefinition const& _function) const +{ + if ( + _function.isPublic() && + _function.isImplemented() && + !_function.isConstructor() + ) + return true; + return false; +} + +void CHC::pushBlock(ASTNode const* _node) +{ + clearIndices(); + m_context.pushSolver(); + m_path.push_back(predicate(_node)); + ++m_functionBlocks; +} + +void CHC::popBlock() +{ + m_context.popSolver(); + m_path.pop_back(); + --m_functionBlocks; +} + +smt::SortPointer CHC::constructorSort() +{ + solAssert(m_currentContract, ""); + auto boolSort = make_shared(smt::Kind::Bool); + if (!m_currentContract->constructor()) + return make_shared(vector{}, boolSort); + return sort(*m_currentContract->constructor()); +} + +smt::SortPointer CHC::interfaceSort() +{ + auto boolSort = make_shared(smt::Kind::Bool); + return make_shared( + m_stateSorts, + boolSort + ); +} + +smt::SortPointer CHC::sort(FunctionDefinition const& _function) +{ + if (m_nodeSorts.count(&_function)) + return m_nodeSorts.at(&_function); + + auto boolSort = make_shared(smt::Kind::Bool); + vector varSorts; + for (auto const& var: _function.parameters() + _function.returnParameters()) + varSorts.push_back(smt::smtSort(*var->type())); + auto sort = make_shared( + m_stateSorts + varSorts, + boolSort + ); + return m_nodeSorts[&_function] = move(sort); +} + +smt::SortPointer CHC::sort(ASTNode const* _node) +{ + if (m_nodeSorts.count(_node)) + return m_nodeSorts.at(_node); + + if (auto funDef = dynamic_cast(_node)) + return sort(*funDef); + + auto fSort = dynamic_pointer_cast(sort(*m_currentFunction)); + solAssert(fSort, ""); + + auto boolSort = make_shared(smt::Kind::Bool); + vector varSorts; + for (auto const& var: m_currentFunction->localVariables()) + varSorts.push_back(smt::smtSort(*var->type())); + auto functionBodySort = make_shared( + fSort->domain + varSorts, + boolSort + ); + return m_nodeSorts[_node] = move(functionBodySort); +} + +unique_ptr CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name) +{ + auto block = make_unique( + _sort, + _name, + m_context + ); + m_interface->registerRelation(block->currentValue()); + return block; +} + +smt::Expression CHC::constructor() +{ + solAssert(m_currentContract, ""); + + if (!m_currentContract->constructor()) + return (*m_constructorPredicate)({}); + + vector paramExprs; + for (auto const& var: m_currentContract->constructor()->parameters()) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return (*m_constructorPredicate)(paramExprs); +} + +smt::Expression CHC::interface() +{ + vector paramExprs; + for (auto const& var: m_stateVariables) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return (*m_interfacePredicate)(paramExprs); +} + +smt::Expression CHC::error() +{ + return (*m_errorPredicate)({}); +} + +smt::Expression CHC::error(unsigned _idx) +{ + return m_errorPredicate->valueAtIndex(_idx)({}); +} + +void CHC::createBlock(ASTNode const* _node, string const& _prefix) +{ + if (m_predicates.count(_node)) + { + m_predicates.at(_node)->increaseIndex(); + m_interface->registerRelation(m_predicates.at(_node)->currentValue()); + } + else + m_predicates[_node] = createSymbolicBlock(sort(_node), _prefix + predicateName(_node)); +} + +void CHC::createErrorBlock() +{ + solAssert(m_errorPredicate, ""); + m_errorPredicate->increaseIndex(); + m_interface->registerRelation(m_errorPredicate->currentValue()); +} + +void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints) +{ + smt::Expression edge = smt::Expression::implies( + _from && m_context.assertions() && _constraints, + _to + ); + addRule(edge, _from.name + "_to_" + _to.name); +} + +vector CHC::currentFunctionVariables() +{ + solAssert(m_currentFunction, ""); + vector paramExprs; + for (auto const& var: m_stateVariables) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return paramExprs; +} + +vector CHC::currentBlockVariables() +{ + solAssert(m_currentFunction, ""); + vector paramExprs; + for (auto const& var: m_currentFunction->localVariables()) + paramExprs.push_back(m_context.variable(*var)->currentValue()); + return currentFunctionVariables() + paramExprs; +} + +void CHC::clearIndices() +{ + for (auto const& var: m_stateVariables) + m_context.variable(*var)->resetIndex(); + if (m_currentFunction) + { + for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters()) + m_context.variable(*var)->resetIndex(); + for (auto const& var: m_currentFunction->localVariables()) + m_context.variable(*var)->resetIndex(); + } +} + +string CHC::predicateName(ASTNode const* _node) +{ + string prefix; + if (auto funDef = dynamic_cast(_node)) + { + prefix = funDef->isConstructor() ? + "constructor" : + funDef->isFallback() ? + "fallback" : + "function_" + funDef->name(); + prefix += "_"; + } + return prefix + to_string(_node->id()); +} + +smt::Expression CHC::predicate(ASTNode const* _node) +{ + if (dynamic_cast(_node)) + return (*m_predicates.at(_node))(currentFunctionVariables()); + return (*m_predicates.at(_node))(currentBlockVariables()); +} + +void CHC::addRule(smt::Expression const& _rule, string const& _ruleName) +{ + m_interface->addRule(_rule, _ruleName); +} + +bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location) +{ + smt::CheckResult result; + vector values; + tie(result, values) = m_interface->query(_query); + switch (result) + { + case smt::CheckResult::SATISFIABLE: + break; + case smt::CheckResult::UNSATISFIABLE: + return true; + case smt::CheckResult::UNKNOWN: + break; + case smt::CheckResult::CONFLICTING: + m_outerErrorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); + break; + case smt::CheckResult::ERROR: + m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver."); + break; + } + return false; +} diff --git a/libsolidity/formal/CHC.h b/libsolidity/formal/CHC.h new file mode 100644 index 000000000..5b40b4c8e --- /dev/null +++ b/libsolidity/formal/CHC.h @@ -0,0 +1,211 @@ +/* + 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 . +*/ + +/** + * Model checker based on Constrained Horn Clauses. + * + * A Solidity contract's CFG is encoded into a system of Horn clauses where + * each block has a predicate and edges are rules. + * + * The entry block is the constructor which has no in-edges. + * The constructor has one out-edge to an artificial block named _Interface_ + * which has in/out-edges from/to all public functions. + * + * Loop invariants for Interface -> Interface' are state invariants. + */ + +#pragma once + +#include + +#include + +#include + +namespace dev +{ +namespace solidity +{ + +class CHC: public SMTEncoder +{ +public: + CHC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter); + + void analyze(SourceUnit const& _sources); + + std::set const& safeAssertions() const { return m_safeAssertions; } + +private: + /// Visitor functions. + //@{ + bool visit(ContractDefinition const& _node) override; + void endVisit(ContractDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const&) override; + bool visit(ForStatement const&) override; + void endVisit(FunctionCall const& _node) override; + void endVisit(Break const& _node) override; + void endVisit(Continue const& _node) override; + + void visitAssert(FunctionCall const& _funCall); + void unknownFunctionCall(FunctionCall const& _funCall); + void visitLoop( + BreakableStatement const& _loop, + Expression const* _condition, + Statement const& _body, + ASTNode const* _postLoop + ); + //@} + + /// Helpers. + //@{ + void reset(); + void eraseKnowledge(); + bool shouldVisit(ContractDefinition const& _contract) const; + bool shouldVisit(FunctionDefinition const& _function) const; + void pushBlock(ASTNode const* _node); + void popBlock(); + //@} + + /// Sort helpers. + //@{ + smt::SortPointer constructorSort(); + smt::SortPointer interfaceSort(); + smt::SortPointer sort(FunctionDefinition const& _function); + smt::SortPointer sort(ASTNode const* _block); + //@} + + /// Predicate helpers. + //@{ + /// @returns a new block of given _sort and _name. + std::unique_ptr createSymbolicBlock(smt::SortPointer _sort, std::string const& _name); + + /// Constructor predicate over current variables. + smt::Expression constructor(); + /// Interface predicate over current variables. + smt::Expression interface(); + /// Error predicate over current variables. + smt::Expression error(); + smt::Expression error(unsigned _idx); + + /// Creates a block for the given _node or increases its SSA index + /// if the block already exists which in practice creates a new function. + void createBlock(ASTNode const* _node, std::string const& _prefix = ""); + + /// Creates a new error block to be used by an assertion. + /// Also registers the predicate. + void createErrorBlock(); + + void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true)); + + /// @returns the current symbolic values of the current function's + /// input and output parameters. + std::vector currentFunctionVariables(); + /// @returns the samve as currentFunctionVariables plus + /// local variables. + std::vector currentBlockVariables(); + + /// Sets the SSA indices of the variables in scope to 0. + /// Used when starting a new block. + void clearIndices(); + + /// @returns the predicate name for a given node. + std::string predicateName(ASTNode const* _node); + /// @returns a predicate application over the current scoped variables. + smt::Expression predicate(ASTNode const* _node); + //@} + + /// Solver related. + //@{ + /// Adds Horn rule to the solver. + void addRule(smt::Expression const& _rule, std::string const& _ruleName); + /// @returns true if query is unsatisfiable (safe). + bool query(smt::Expression const& _query, langutil::SourceLocation const& _location); + //@} + + /// Predicates. + //@{ + /// Constructor predicate. + /// Default constructor sets state vars to 0. + std::unique_ptr m_constructorPredicate; + + /// Artificial Interface predicate. + /// Single entry block for all functions. + std::unique_ptr m_interfacePredicate; + + /// Artificial Error predicate. + /// Single error block for all assertions. + std::unique_ptr m_errorPredicate; + + /// Maps AST nodes to their predicates. + std::unordered_map> m_predicates; + //@} + + /// Variables. + //@{ + /// State variables sorts. + /// Used by all predicates. + std::vector m_stateSorts; + /// State variables. + /// Used to create all predicates. + std::vector m_stateVariables; + + /// Input sorts for AST nodes. + std::map m_nodeSorts; + //@} + + /// Verification targets. + //@{ + std::vector m_verificationTargets; + + /// Assertions proven safe. + std::set m_safeAssertions; + //@} + + /// Control-flow. + //@{ + FunctionDefinition const* m_currentFunction = nullptr; + + /// Number of basic blocks created for the body of the current function. + unsigned m_functionBlocks = 0; + /// The current control flow path. + std::vector m_path; + /// Whether a function call was seen in the current scope. + bool m_unknownFunctionCallSeen = false; + /// Whether a break statement was seen in the current scope. + bool m_breakSeen = false; + /// Whether a continue statement was seen in the current scope. + bool m_continueSeen = false; + + /// Block where a loop break should go to. + ASTNode const* m_breakDest; + /// Block where a loop continue should go to. + ASTNode const* m_continueDest; + //@} + + /// CHC solver. + std::shared_ptr m_interface; + + /// ErrorReporter that comes from CompilerStack. + langutil::ErrorReporter& m_outerErrorReporter; +}; + +} +} diff --git a/libsolidity/formal/CHCSolverInterface.h b/libsolidity/formal/CHCSolverInterface.h new file mode 100644 index 000000000..091aa3d7c --- /dev/null +++ b/libsolidity/formal/CHCSolverInterface.h @@ -0,0 +1,56 @@ +/* + 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 . +*/ + +/** + * Interface for constrained Horn solvers. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +class CHCSolverInterface +{ +public: + virtual ~CHCSolverInterface() = default; + + virtual void declareVariable(std::string const& _name, Sort const& _sort) = 0; + + /// Takes a function declaration as a relation. + virtual void registerRelation(Expression const& _expr) = 0; + + /// Takes an implication and adds as rule. + /// Needs to bound all vars as universally quantified. + virtual void addRule(Expression const& _expr, std::string const& _name) = 0; + + /// Takes a function application and checks + /// for reachability. + virtual std::pair> query( + Expression const& _expr + ) = 0; +}; + +} +} +} diff --git a/libsolidity/formal/CVC4Interface.cpp b/libsolidity/formal/CVC4Interface.cpp index 6cb914834..dfe0cb913 100644 --- a/libsolidity/formal/CVC4Interface.cpp +++ b/libsolidity/formal/CVC4Interface.cpp @@ -17,8 +17,7 @@ #include -#include - +#include #include using namespace std; @@ -33,8 +32,7 @@ CVC4Interface::CVC4Interface(): void CVC4Interface::reset() { - m_constants.clear(); - m_functions.clear(); + m_variables.clear(); m_solver.reset(); m_solver.setOption("produce-models", true); m_solver.setTimeLimit(queryTimeout); @@ -50,25 +48,10 @@ void CVC4Interface::pop() m_solver.pop(); } -void CVC4Interface::declareFunction(string _name, Sort _domain, Sort _codomain) -{ - if (!m_functions.count(_name)) - { - CVC4::Type fType = m_context.mkFunctionType(cvc4Sort(_domain), cvc4Sort(_codomain)); - m_functions.insert({_name, m_context.mkVar(_name.c_str(), fType)}); - } -} - -void CVC4Interface::declareInteger(string _name) -{ - if (!m_constants.count(_name)) - m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.integerType())}); -} - -void CVC4Interface::declareBool(string _name) +void CVC4Interface::declareVariable(string const& _name, Sort const& _sort) { - if (!m_constants.count(_name)) - m_constants.insert({_name, m_context.mkVar(_name.c_str(), m_context.booleanType())}); + if (!m_variables.count(_name)) + m_variables.insert({_name, m_context.mkVar(_name.c_str(), cvc4Sort(_sort))}); } void CVC4Interface::addAssertion(Expression const& _expr) @@ -77,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()); } } @@ -129,71 +116,118 @@ pair> CVC4Interface::check(vector const& CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr) { - if (_expr.arguments.empty() && m_constants.count(_expr.name)) - return m_constants.at(_expr.name); + // Variable + if (_expr.arguments.empty() && m_variables.count(_expr.name)) + return m_variables.at(_expr.name); + vector arguments; for (auto const& arg: _expr.arguments) arguments.push_back(toCVC4Expr(arg)); - string const& n = _expr.name; - if (m_functions.count(n)) - return m_context.mkExpr(CVC4::kind::APPLY_UF, m_functions[n], arguments); - else if (m_constants.count(n)) + try + { + 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 if (auto sortSort = dynamic_pointer_cast(_expr.sort)) + return m_context.mkVar(n, cvc4Sort(*sortSort->inner)); + 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]); + else if (n == "const_array") + { + shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + solAssert(sortSort, ""); + return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1])); + } + + solAssert(false, ""); + } + catch (CVC4::TypeCheckingException const& _e) { - solAssert(arguments.empty(), ""); - return m_constants.at(n); + solAssert(false, _e.what()); } - else if (arguments.empty()) + catch (CVC4::Exception const& _e) { - 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)); + 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]); - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } -CVC4::Type CVC4Interface::cvc4Sort(Sort _sort) +CVC4::Type CVC4Interface::cvc4Sort(Sort const& _sort) { - switch (_sort) + switch (_sort.kind) { - case Sort::Bool: + case Kind::Bool: return m_context.booleanType(); - case Sort::Int: + case Kind::Int: return m_context.integerType(); + case Kind::Function: + { + FunctionSort const& fSort = dynamic_cast(_sort); + return m_context.mkFunctionType(cvc4Sort(fSort.domain), cvc4Sort(*fSort.codomain)); + } + case Kind::Array: + { + auto const& arraySort = dynamic_cast(_sort); + return m_context.mkArrayType(cvc4Sort(*arraySort.domain), cvc4Sort(*arraySort.range)); + } default: break; } @@ -201,3 +235,11 @@ CVC4::Type CVC4Interface::cvc4Sort(Sort _sort) // Cannot be reached. return m_context.integerType(); } + +vector CVC4Interface::cvc4Sort(vector const& _sorts) +{ + vector cvc4Sorts; + for (auto const& _sort: _sorts) + cvc4Sorts.push_back(cvc4Sort(*_sort)); + return cvc4Sorts; +} diff --git a/libsolidity/formal/CVC4Interface.h b/libsolidity/formal/CVC4Interface.h index cd6d761dd..897923647 100644 --- a/libsolidity/formal/CVC4Interface.h +++ b/libsolidity/formal/CVC4Interface.h @@ -18,7 +18,6 @@ #pragma once #include - #include #if defined(__GLIBC__) @@ -51,21 +50,19 @@ class CVC4Interface: public SolverInterface, public boost::noncopyable void push() override; void pop() override; - void declareFunction(std::string _name, Sort _domain, Sort _codomain) override; - void declareInteger(std::string _name) override; - void declareBool(std::string _name) override; + void declareVariable(std::string const&, Sort const&) override; void addAssertion(Expression const& _expr) override; std::pair> check(std::vector const& _expressionsToEvaluate) override; private: CVC4::Expr toCVC4Expr(Expression const& _expr); - CVC4::Type cvc4Sort(smt::Sort _sort); + CVC4::Type cvc4Sort(smt::Sort const& _sort); + std::vector cvc4Sort(std::vector const& _sorts); CVC4::ExprManager m_context; CVC4::SmtEngine m_solver; - std::map m_constants; - std::map m_functions; + std::map m_variables; }; } diff --git a/libsolidity/formal/EncodingContext.cpp b/libsolidity/formal/EncodingContext.cpp new file mode 100644 index 000000000..6c22ec9ca --- /dev/null +++ b/libsolidity/formal/EncodingContext.cpp @@ -0,0 +1,262 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity::smt; + +EncodingContext::EncodingContext(): + m_thisAddress(make_unique("this", *this)) +{ + auto sort = make_shared( + make_shared(Kind::Int), + make_shared(Kind::Int) + ); + m_balances = make_unique(sort, "balances", *this); +} + +void EncodingContext::reset() +{ + resetAllVariables(); + m_expressions.clear(); + m_globalContext.clear(); + m_thisAddress->resetIndex(); + m_balances->resetIndex(); + m_assertions.clear(); +} + +void EncodingContext::clear() +{ + m_variables.clear(); + reset(); +} + +/// Variables. + +shared_ptr EncodingContext::variable(solidity::VariableDeclaration const& _varDecl) +{ + solAssert(knownVariable(_varDecl), ""); + return m_variables[&_varDecl]; +} + +bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDecl) +{ + solAssert(!knownVariable(_varDecl), ""); + auto const& type = _varDecl.type(); + auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *this); + m_variables.emplace(&_varDecl, result.second); + return result.first; +} + +bool EncodingContext::knownVariable(solidity::VariableDeclaration const& _varDecl) +{ + return m_variables.count(&_varDecl); +} + +void EncodingContext::resetVariable(solidity::VariableDeclaration const& _variable) +{ + newValue(_variable); + setUnknownValue(_variable); +} + +void EncodingContext::resetVariables(set const& _variables) +{ + for (auto const* decl: _variables) + resetVariable(*decl); +} + +void EncodingContext::resetVariables(function const& _filter) +{ + for_each(begin(m_variables), end(m_variables), [&](auto _variable) + { + if (_filter(*_variable.first)) + this->resetVariable(*_variable.first); + }); +} + +void EncodingContext::resetAllVariables() +{ + resetVariables([&](solidity::VariableDeclaration const&) { return true; }); +} + +Expression EncodingContext::newValue(solidity::VariableDeclaration const& _decl) +{ + solAssert(knownVariable(_decl), ""); + return m_variables.at(&_decl)->increaseIndex(); +} + +void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl) +{ + solAssert(knownVariable(_decl), ""); + setZeroValue(*m_variables.at(&_decl)); +} + +void EncodingContext::setZeroValue(SymbolicVariable& _variable) +{ + setSymbolicZeroValue(_variable, *this); +} + +void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl) +{ + solAssert(knownVariable(_decl), ""); + setUnknownValue(*m_variables.at(&_decl)); +} + +void EncodingContext::setUnknownValue(SymbolicVariable& _variable) +{ + setSymbolicUnknownValue(_variable, *this); +} + +/// Expressions + +shared_ptr EncodingContext::expression(solidity::Expression const& _e) +{ + if (!knownExpression(_e)) + createExpression(_e); + return m_expressions.at(&_e); +} + +bool EncodingContext::createExpression(solidity::Expression const& _e, shared_ptr _symbVar) +{ + solAssert(_e.annotation().type, ""); + if (knownExpression(_e)) + { + expression(_e)->increaseIndex(); + return false; + } + else if (_symbVar) + { + m_expressions.emplace(&_e, _symbVar); + return false; + } + else + { + auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *this); + m_expressions.emplace(&_e, result.second); + return result.first; + } +} + +bool EncodingContext::knownExpression(solidity::Expression const& _e) const +{ + return m_expressions.count(&_e); +} + +/// Global variables and functions. + +shared_ptr EncodingContext::globalSymbol(string const& _name) +{ + solAssert(knownGlobalSymbol(_name), ""); + return m_globalContext.at(_name); +} + +bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr) +{ + solAssert(!knownGlobalSymbol(_name), ""); + auto result = newSymbolicVariable(*_expr.annotation().type, _name, *this); + m_globalContext.emplace(_name, result.second); + setUnknownValue(*result.second); + return result.first; +} + +bool EncodingContext::knownGlobalSymbol(string const& _var) const +{ + return m_globalContext.count(_var); +} + +// Blockchain + +Expression EncodingContext::thisAddress() +{ + return m_thisAddress->currentValue(); +} + +Expression EncodingContext::balance() +{ + return balance(m_thisAddress->currentValue()); +} + +Expression EncodingContext::balance(Expression _address) +{ + return Expression::select(m_balances->currentValue(), move(_address)); +} + +void EncodingContext::transfer(Expression _from, Expression _to, Expression _value) +{ + unsigned indexBefore = m_balances->index(); + addBalance(_from, 0 - _value); + addBalance(_to, move(_value)); + unsigned indexAfter = m_balances->index(); + solAssert(indexAfter > indexBefore, ""); + m_balances->increaseIndex(); + /// Do not apply the transfer operation if _from == _to. + auto newBalances = Expression::ite( + move(_from) == move(_to), + m_balances->valueAtIndex(indexBefore), + m_balances->valueAtIndex(indexAfter) + ); + addAssertion(m_balances->currentValue() == newBalances); +} + +/// Solver. + +Expression EncodingContext::assertions() +{ + if (m_assertions.empty()) + return Expression(true); + + return m_assertions.back(); +} + +void EncodingContext::pushSolver() +{ + if (m_accumulateAssertions) + m_assertions.push_back(assertions()); + else + m_assertions.push_back(smt::Expression(true)); +} + +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( + m_balances->currentValue(), + _address, + balance(_address) + move(_value) + ); + m_balances->increaseIndex(); + addAssertion(newBalances == m_balances->currentValue()); +} diff --git a/libsolidity/formal/EncodingContext.h b/libsolidity/formal/EncodingContext.h new file mode 100644 index 000000000..4e1e15658 --- /dev/null +++ b/libsolidity/formal/EncodingContext.h @@ -0,0 +1,192 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +/** + * Stores the context of the SMT encoding. + */ +class EncodingContext +{ +public: + EncodingContext(); + + /// Resets the entire context except for symbolic variables which stay + /// alive because of state variables and inlined function calls. + /// To be used in the beginning of a root function visit. + void reset(); + /// Clears the entire context, erasing everything. + /// To be used before a model checking engine starts. + void clear(); + + /// Sets the current solver used by the current engine for + /// SMT variable declaration. + void setSolver(std::shared_ptr _solver) + { + solAssert(_solver, ""); + m_solver = _solver; + } + + /// Sets whether the context should conjoin assertions in the assertion stack. + void setAssertionAccumulation(bool _acc) { m_accumulateAssertions = _acc; } + + /// Forwards variable creation to the solver. + Expression newVariable(std::string _name, SortPointer _sort) + { + solAssert(m_solver, ""); + return m_solver->newVariable(move(_name), move(_sort)); + } + + /// Variables. + //@{ + /// @returns the symbolic representation of a program variable. + std::shared_ptr variable(solidity::VariableDeclaration const& _varDecl); + /// @returns all symbolic variables. + std::unordered_map> const& variables() const { return m_variables; } + + /// Creates a symbolic variable and + /// @returns true if a variable's type is not supported and is therefore abstract. + bool createVariable(solidity::VariableDeclaration const& _varDecl); + /// @returns true if variable was created. + bool knownVariable(solidity::VariableDeclaration const& _varDecl); + + /// Resets a specific variable. + void resetVariable(solidity::VariableDeclaration const& _variable); + /// Resets a set of variables. + void resetVariables(std::set const& _variables); + /// Resets variables according to a predicate. + void resetVariables(std::function const& _filter); + ///Resets all variables. + void resetAllVariables(); + + /// Allocates a new index for the declaration, updates the current + /// index to this value and returns the expression. + Expression newValue(solidity::VariableDeclaration const& _decl); + /// Sets the value of the declaration to zero. + void setZeroValue(solidity::VariableDeclaration const& _decl); + void setZeroValue(SymbolicVariable& _variable); + /// Resets the variable to an unknown value (in its range). + void setUnknownValue(solidity::VariableDeclaration const& decl); + void setUnknownValue(SymbolicVariable& _variable); + //@} + + /// Expressions. + ////@{ + /// @returns the symbolic representation of an AST node expression. + std::shared_ptr expression(solidity::Expression const& _e); + /// @returns all symbolic expressions. + std::unordered_map> const& expressions() const { return m_expressions; } + + /// Creates the expression (value can be arbitrary). + /// @returns true if type is not supported. + bool createExpression(solidity::Expression const& _e, std::shared_ptr _symbExpr = nullptr); + /// Checks if expression was created. + bool knownExpression(solidity::Expression const& _e) const; + //@} + + /// Global variables and functions. + //@{ + /// Global variables and functions. + std::shared_ptr globalSymbol(std::string const& _name); + /// @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); + /// Checks if special variable or function was seen. + bool knownGlobalSymbol(std::string const& _var) const; + //@} + + /// Blockchain. + //@{ + /// Value of `this` address. + Expression thisAddress(); + /// @returns the symbolic balance of address `this`. + Expression balance(); + /// @returns the symbolic balance of an address. + Expression balance(Expression _address); + /// Transfer _value from _from to _to. + 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() + { + solAssert(m_solver, ""); + return m_solver; + } + //@} + +private: + /// Adds _value to _account's balance. + void addBalance(Expression _account, Expression _value); + + /// Symbolic expressions. + //{@ + /// Symbolic variables. + std::unordered_map> m_variables; + + /// Symbolic expressions. + std::unordered_map> m_expressions; + + /// Symbolic representation of global symbols including + /// variables and functions. + std::unordered_map> m_globalContext; + + /// Symbolic `this` address. + std::unique_ptr m_thisAddress; + + /// 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; + + /// Whether to conjoin assertions in the assertion stack. + bool m_accumulateAssertions = true; + //@} +}; + +} +} +} diff --git a/libsolidity/formal/ModelChecker.cpp b/libsolidity/formal/ModelChecker.cpp new file mode 100644 index 000000000..265d0f991 --- /dev/null +++ b/libsolidity/formal/ModelChecker.cpp @@ -0,0 +1,44 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +ModelChecker::ModelChecker(ErrorReporter& _errorReporter, map const& _smtlib2Responses): + m_bmc(m_context, _errorReporter, _smtlib2Responses), + m_chc(m_context, _errorReporter), + m_context() +{ +} + +void ModelChecker::analyze(SourceUnit const& _source) +{ + if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) + return; + + m_chc.analyze(_source); + m_bmc.analyze(_source, m_chc.safeAssertions()); +} + +vector ModelChecker::unhandledQueries() +{ + return m_bmc.unhandledQueries(); +} diff --git a/libsolidity/formal/ModelChecker.h b/libsolidity/formal/ModelChecker.h new file mode 100644 index 000000000..349ed76d4 --- /dev/null +++ b/libsolidity/formal/ModelChecker.h @@ -0,0 +1,67 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * Entry point to the model checking engines. + * The goal of this class is to make different + * engines share knowledge to boost their proving power. + */ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ + +class ModelChecker +{ +public: + ModelChecker(langutil::ErrorReporter& _errorReporter, std::map const& _smtlib2Responses); + + void analyze(SourceUnit const& _sources); + + /// This is used if the SMT solver is not directly linked into this binary. + /// @returns a list of inputs to the SMT solver that were not part of the argument to + /// the constructor. + std::vector unhandledQueries(); + +private: + /// Bounded Model Checker engine. + BMC m_bmc; + + /// Constrained Horn Clauses engine. + CHC m_chc; + + /// Stores the context of the encoding. + smt::EncodingContext m_context; +}; + +} +} diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp deleted file mode 100644 index cc580021e..000000000 --- a/libsolidity/formal/SMTChecker.cpp +++ /dev/null @@ -1,1142 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ - -#include - -#include - -#include -#include - -#include - -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readFileCallback): - m_interface(make_shared(_readFileCallback)), - m_errorReporter(_errorReporter) -{ -} - -void SMTChecker::analyze(SourceUnit const& _source, shared_ptr const& _scanner) -{ - m_variableUsage = make_shared(_source); - m_scanner = _scanner; - if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker)) - _source.accept(*this); -} - -bool SMTChecker::visit(ContractDefinition const& _contract) -{ - for (auto _var : _contract.stateVariables()) - if (_var->type()->isValueType()) - createVariable(*_var); - return true; -} - -void SMTChecker::endVisit(ContractDefinition const&) -{ - m_variables.clear(); -} - -void SMTChecker::endVisit(VariableDeclaration const& _varDecl) -{ - if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) - assignment(_varDecl, *_varDecl.value(), _varDecl.location()); -} - -bool SMTChecker::visit(FunctionDefinition const& _function) -{ - if (!_function.modifiers().empty() || _function.isConstructor()) - m_errorReporter.warning( - _function.location(), - "Assertion checker does not yet support constructors and functions with modifiers." - ); - m_functionPath.push_back(&_function); - // Not visited by a function call - if (isRootFunction()) - { - m_interface->reset(); - m_pathConditions.clear(); - m_expressions.clear(); - resetStateVariables(); - initializeLocalVariables(_function); - } - - m_loopExecutionHappened = false; - return true; -} - -void SMTChecker::endVisit(FunctionDefinition const&) -{ - // If _function was visited from a function call we don't remove - // the local variables just yet, since we might need them for - // future calls. - // Otherwise we remove any local variables from the context and - // keep the state variables. - if (isRootFunction()) - removeLocalVariables(); - m_functionPath.pop_back(); -} - -bool SMTChecker::visit(IfStatement const& _node) -{ - _node.condition().accept(*this); - - // We ignore called functions here because they have - // specific input values. - if (isRootFunction()) - checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE."); - - auto indicesEndTrue = visitBranch(_node.trueStatement(), expr(_node.condition())); - vector touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement()); - decltype(indicesEndTrue) indicesEndFalse; - if (_node.falseStatement()) - { - indicesEndFalse = visitBranch(*_node.falseStatement(), !expr(_node.condition())); - touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement()); - } - else - indicesEndFalse = copyVariableIndices(); - - mergeVariables(touchedVariables, expr(_node.condition()), indicesEndTrue, indicesEndFalse); - - return false; -} - -bool SMTChecker::visit(WhileStatement const& _node) -{ - auto touchedVariables = m_variableUsage->touchedVariables(_node); - resetVariables(touchedVariables); - if (_node.isDoWhile()) - { - visitBranch(_node.body()); - // TODO the assertions generated in the body should still be active in the condition - _node.condition().accept(*this); - if (isRootFunction()) - checkBooleanNotConstant(_node.condition(), "Do-while loop condition is always $VALUE."); - } - else - { - _node.condition().accept(*this); - if (isRootFunction()) - checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE."); - - visitBranch(_node.body(), expr(_node.condition())); - } - m_loopExecutionHappened = true; - resetVariables(touchedVariables); - - return false; -} - -bool SMTChecker::visit(ForStatement const& _node) -{ - if (_node.initializationExpression()) - _node.initializationExpression()->accept(*this); - - // Do not reset the init expression part. - auto touchedVariables = - m_variableUsage->touchedVariables(_node.body()); - if (_node.condition()) - touchedVariables += m_variableUsage->touchedVariables(*_node.condition()); - if (_node.loopExpression()) - touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression()); - // Remove duplicates - std::sort(touchedVariables.begin(), touchedVariables.end()); - touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end()); - - resetVariables(touchedVariables); - - if (_node.condition()) - { - _node.condition()->accept(*this); - if (isRootFunction()) - checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE."); - } - - m_interface->push(); - if (_node.condition()) - m_interface->addAssertion(expr(*_node.condition())); - _node.body().accept(*this); - if (_node.loopExpression()) - _node.loopExpression()->accept(*this); - - m_interface->pop(); - - m_loopExecutionHappened = true; - - resetVariables(touchedVariables); - - return false; -} - -void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) -{ - if (_varDecl.declarations().size() != 1) - m_errorReporter.warning( - _varDecl.location(), - "Assertion checker does not yet support such variable declarations." - ); - else if (knownVariable(*_varDecl.declarations()[0])) - { - if (_varDecl.initialValue()) - assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location()); - } - else - m_errorReporter.warning( - _varDecl.location(), - "Assertion checker does not yet implement such variable declarations." - ); -} - -void SMTChecker::endVisit(Assignment const& _assignment) -{ - if (_assignment.assignmentOperator() != Token::Assign) - m_errorReporter.warning( - _assignment.location(), - "Assertion checker does not yet implement compound assignment." - ); - else if (!isSupportedType(_assignment.annotation().type->category())) - m_errorReporter.warning( - _assignment.location(), - "Assertion checker does not yet implement type " + _assignment.annotation().type->toString() - ); - else if (Identifier const* identifier = dynamic_cast(&_assignment.leftHandSide())) - { - VariableDeclaration const& decl = dynamic_cast(*identifier->annotation().referencedDeclaration); - if (knownVariable(decl)) - { - assignment(decl, _assignment.rightHandSide(), _assignment.location()); - defineExpr(_assignment, expr(_assignment.rightHandSide())); - } - else - m_errorReporter.warning( - _assignment.location(), - "Assertion checker does not yet implement such assignments." - ); - } - else - m_errorReporter.warning( - _assignment.location(), - "Assertion checker does not yet implement such assignments." - ); -} - -void SMTChecker::endVisit(TupleExpression const& _tuple) -{ - if (_tuple.isInlineArray() || _tuple.components().size() != 1) - m_errorReporter.warning( - _tuple.location(), - "Assertion checker does not yet implement tuples and inline arrays." - ); - else - defineExpr(_tuple, expr(*_tuple.components()[0])); -} - -void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location) -{ - checkCondition( - _value < minValue(_type), - _location, - "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")", - "", - &_value - ); - checkCondition( - _value > maxValue(_type), - _location, - "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")", - "", - &_value - ); -} - -void SMTChecker::endVisit(UnaryOperation const& _op) -{ - switch (_op.getOperator()) - { - case Token::Not: // ! - { - solAssert(isBool(_op.annotation().type->category()), ""); - defineExpr(_op, !expr(_op.subExpression())); - break; - } - case Token::Inc: // ++ (pre- or postfix) - case Token::Dec: // -- (pre- or postfix) - { - - solAssert(isInteger(_op.annotation().type->category()), ""); - solAssert(_op.subExpression().annotation().lValueRequested, ""); - if (Identifier const* identifier = dynamic_cast(&_op.subExpression())) - { - VariableDeclaration const& decl = dynamic_cast(*identifier->annotation().referencedDeclaration); - if (knownVariable(decl)) - { - auto innerValue = currentValue(decl); - auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; - assignment(decl, newValue, _op.location()); - defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); - } - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement such assignments." - ); - } - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement such increments / decrements." - ); - break; - } - case Token::Add: // + - defineExpr(_op, expr(_op.subExpression())); - break; - case Token::Sub: // - - { - defineExpr(_op, 0 - expr(_op.subExpression())); - if (auto intType = dynamic_cast(_op.annotation().type.get())) - checkUnderOverflow(expr(_op), *intType, _op.location()); - break; - } - default: - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement this operator." - ); - } -} - -void SMTChecker::endVisit(BinaryOperation const& _op) -{ - if (TokenTraits::isArithmeticOp(_op.getOperator())) - arithmeticOperation(_op); - else if (TokenTraits::isCompareOp(_op.getOperator())) - compareOperation(_op); - else if (TokenTraits::isBooleanOp(_op.getOperator())) - booleanOperation(_op); - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement this operator." - ); -} - -void SMTChecker::endVisit(FunctionCall const& _funCall) -{ - solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) - { - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not yet implement this expression." - ); - return; - } - - FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); - - std::vector> const args = _funCall.arguments(); - if (funType.kind() == FunctionType::Kind::Assert) - visitAssert(_funCall); - else if (funType.kind() == FunctionType::Kind::Require) - visitRequire(_funCall); - else if (funType.kind() == FunctionType::Kind::GasLeft) - visitGasLeft(_funCall); - else if (funType.kind() == FunctionType::Kind::BlockHash) - visitBlockHash(_funCall); - else if (funType.kind() == FunctionType::Kind::Internal) - inlineFunctionCall(_funCall); - else - { - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not yet implement this type of function call." - ); - } -} - -void SMTChecker::visitAssert(FunctionCall const& _funCall) -{ - auto const& args = _funCall.arguments(); - solAssert(args.size() == 1, ""); - solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); - checkCondition(!(expr(*args[0])), _funCall.location(), "Assertion violation"); - addPathImpliedExpression(expr(*args[0])); -} - -void SMTChecker::visitRequire(FunctionCall const& _funCall) -{ - auto const& args = _funCall.arguments(); - solAssert(args.size() == 1, ""); - solAssert(args[0]->annotation().type->category() == Type::Category::Bool, ""); - if (isRootFunction()) - checkBooleanNotConstant(*args[0], "Condition is always $VALUE."); - addPathImpliedExpression(expr(*args[0])); -} - -void SMTChecker::visitGasLeft(FunctionCall const& _funCall) -{ - string gasLeft = "gasleft()"; - // We increase the variable index since gasleft changes - // inside a tx. - defineSpecialVariable(gasLeft, _funCall, true); - auto const& symbolicVar = m_specialVariables.at(gasLeft); - unsigned index = symbolicVar->index(); - // We set the current value to unknown anyway to add type constraints. - symbolicVar->setUnknownValue(); - if (index > 0) - m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); -} - -void SMTChecker::visitBlockHash(FunctionCall const& _funCall) -{ - string blockHash = "blockhash()"; - // TODO Define blockhash as an uninterpreted function - defineSpecialVariable(blockHash, _funCall); -} - -void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall) -{ - FunctionDefinition const* _funDef = nullptr; - Expression const* _calledExpr = &_funCall.expression(); - - if (TupleExpression const* _fun = dynamic_cast(&_funCall.expression())) - { - solAssert(_fun->components().size() == 1, ""); - _calledExpr = _fun->components().at(0).get(); - } - - if (Identifier const* _fun = dynamic_cast(_calledExpr)) - _funDef = dynamic_cast(_fun->annotation().referencedDeclaration); - else if (MemberAccess const* _fun = dynamic_cast(_calledExpr)) - _funDef = dynamic_cast(_fun->annotation().referencedDeclaration); - else - { - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not yet implement this type of function call." - ); - return; - } - solAssert(_funDef, ""); - - if (visitedFunction(_funDef)) - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not support recursive function calls.", - SecondarySourceLocation().append("Starting from function:", _funDef->location()) - ); - else if (_funDef && _funDef->isImplemented()) - { - vector funArgs; - for (auto arg: _funCall.arguments()) - funArgs.push_back(expr(*arg)); - initializeFunctionCallParameters(*_funDef, funArgs); - _funDef->accept(*this); - auto const& returnParams = _funDef->returnParameters(); - if (_funDef->returnParameters().size()) - { - if (returnParams.size() > 1) - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not yet support calls to functions that return more than one value." - ); - else - defineExpr(_funCall, currentValue(*returnParams[0])); - } - } - else - { - m_errorReporter.warning( - _funCall.location(), - "Assertion checker does not support calls to functions without implementation." - ); - } -} - -void SMTChecker::endVisit(Identifier const& _identifier) -{ - if (_identifier.annotation().lValueRequested) - { - // Will be translated as part of the node that requested the lvalue. - } - else if (FunctionType const* fun = dynamic_cast(_identifier.annotation().type.get())) - { - if ( - fun->kind() == FunctionType::Kind::Assert || - fun->kind() == FunctionType::Kind::Require || - fun->kind() == FunctionType::Kind::GasLeft || - fun->kind() == FunctionType::Kind::BlockHash - ) - return; - createExpr(_identifier); - } - else if (isSupportedType(_identifier.annotation().type->category())) - { - if (VariableDeclaration const* decl = dynamic_cast(_identifier.annotation().referencedDeclaration)) - defineExpr(_identifier, currentValue(*decl)); - else if (_identifier.name() == "now") - defineSpecialVariable(_identifier.name(), _identifier); - else - // TODO: handle MagicVariableDeclaration here - m_errorReporter.warning( - _identifier.location(), - "Assertion checker does not yet support the type of this variable." - ); - } -} - -void SMTChecker::endVisit(Literal const& _literal) -{ - Type const& type = *_literal.annotation().type; - if (isNumber(type.category())) - - defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); - else if (isBool(type.category())) - defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); - else - m_errorReporter.warning( - _literal.location(), - "Assertion checker does not yet support the type of this literal (" + - _literal.annotation().type->toString() + - ")." - ); -} - -void SMTChecker::endVisit(Return const& _return) -{ - if (knownExpr(*_return.expression())) - { - auto returnParams = m_functionPath.back()->returnParameters(); - if (returnParams.size() > 1) - m_errorReporter.warning( - _return.location(), - "Assertion checker does not yet support more than one return value." - ); - else if (returnParams.size() == 1) - m_interface->addAssertion(expr(*_return.expression()) == newValue(*returnParams[0])); - } -} - -bool SMTChecker::visit(MemberAccess const& _memberAccess) -{ - auto const& exprType = _memberAccess.expression().annotation().type; - solAssert(exprType, ""); - if (exprType->category() == Type::Category::Magic) - { - auto identifier = dynamic_cast(&_memberAccess.expression()); - string accessedName; - if (identifier) - accessedName = identifier->name(); - else - m_errorReporter.warning( - _memberAccess.location(), - "Assertion checker does not yet support this expression." - ); - defineSpecialVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); - return false; - } - else - m_errorReporter.warning( - _memberAccess.location(), - "Assertion checker does not yet support this expression." - ); - - return true; -} - -void SMTChecker::defineSpecialVariable(string const& _name, Expression const& _expr, bool _increaseIndex) -{ - if (!knownSpecialVariable(_name)) - { - auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_interface); - m_specialVariables.emplace(_name, result.second); - result.second->setUnknownValue(); - if (result.first) - m_errorReporter.warning( - _expr.location(), - "Assertion checker does not yet support this special variable." - ); - } - else if (_increaseIndex) - m_specialVariables.at(_name)->increaseIndex(); - // The default behavior is not to increase the index since - // most of the special values stay the same throughout a tx. - defineExpr(_expr, m_specialVariables.at(_name)->currentValue()); -} - - -void SMTChecker::arithmeticOperation(BinaryOperation const& _op) -{ - switch (_op.getOperator()) - { - case Token::Add: - case Token::Sub: - case Token::Mul: - case Token::Div: - { - solAssert(_op.annotation().commonType, ""); - if (_op.annotation().commonType->category() != Type::Category::Integer) - { - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement this operator on non-integer types." - ); - break; - } - auto const& intType = dynamic_cast(*_op.annotation().commonType); - smt::Expression left(expr(_op.leftExpression())); - smt::Expression right(expr(_op.rightExpression())); - Token op = _op.getOperator(); - smt::Expression value( - op == Token::Add ? left + right : - op == Token::Sub ? left - right : - op == Token::Div ? division(left, right, intType) : - /*op == Token::Mul*/ left * right - ); - - if (_op.getOperator() == Token::Div) - { - checkCondition(right == 0, _op.location(), "Division by zero", "", &right); - m_interface->addAssertion(right != 0); - } - - checkUnderOverflow(value, intType, _op.location()); - - defineExpr(_op, value); - break; - } - default: - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement this operator." - ); - } -} - -void SMTChecker::compareOperation(BinaryOperation const& _op) -{ - solAssert(_op.annotation().commonType, ""); - if (isSupportedType(_op.annotation().commonType->category())) - { - smt::Expression left(expr(_op.leftExpression())); - smt::Expression right(expr(_op.rightExpression())); - Token op = _op.getOperator(); - shared_ptr value; - if (isNumber(_op.annotation().commonType->category())) - { - value = make_shared( - op == Token::Equal ? (left == right) : - op == Token::NotEqual ? (left != right) : - op == Token::LessThan ? (left < right) : - op == Token::LessThanOrEqual ? (left <= right) : - op == Token::GreaterThan ? (left > right) : - /*op == Token::GreaterThanOrEqual*/ (left >= right) - ); - } - else // Bool - { - solUnimplementedAssert(isBool(_op.annotation().commonType->category()), "Operation not yet supported"); - value = make_shared( - op == Token::Equal ? (left == right) : - /*op == Token::NotEqual*/ (left != right) - ); - } - // TODO: check that other values for op are not possible. - defineExpr(_op, *value); - } - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons" - ); -} - -void SMTChecker::booleanOperation(BinaryOperation const& _op) -{ - solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, ""); - solAssert(_op.annotation().commonType, ""); - if (_op.annotation().commonType->category() == Type::Category::Bool) - { - // @TODO check that both of them are not constant - if (_op.getOperator() == Token::And) - defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression())); - else - defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression())); - } - else - m_errorReporter.warning( - _op.location(), - "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" - ); -} - -smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) -{ - // Signed division in SMTLIB2 rounds differently for negative division. - if (_type.isSigned()) - return (smt::Expression::ite( - _left >= 0, - smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))), - smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right)) - )); - else - return _left / _right; -} - -void SMTChecker::assignment(VariableDeclaration const& _variable, Expression const& _value, SourceLocation const& _location) -{ - assignment(_variable, expr(_value), _location); -} - -void SMTChecker::assignment(VariableDeclaration const& _variable, smt::Expression const& _value, SourceLocation const& _location) -{ - TypePointer type = _variable.type(); - if (auto const* intType = dynamic_cast(type.get())) - checkUnderOverflow(_value, *intType, _location); - else if (dynamic_cast(type.get())) - checkUnderOverflow(_value, IntegerType(160), _location); - m_interface->addAssertion(newValue(_variable) == _value); -} - -SMTChecker::VariableIndices SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition) -{ - return visitBranch(_statement, &_condition); -} - -SMTChecker::VariableIndices SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition) -{ - auto indicesBeforeBranch = copyVariableIndices(); - if (_condition) - pushPathCondition(*_condition); - _statement.accept(*this); - if (_condition) - popPathCondition(); - auto indicesAfterBranch = copyVariableIndices(); - resetVariableIndices(indicesBeforeBranch); - return indicesAfterBranch; -} - -void SMTChecker::checkCondition( - smt::Expression _condition, - SourceLocation const& _location, - string const& _description, - string const& _additionalValueName, - smt::Expression* _additionalValue -) -{ - m_interface->push(); - addPathConjoinedExpression(_condition); - - vector expressionsToEvaluate; - vector expressionNames; - if (m_functionPath.size()) - { - solAssert(m_scanner, ""); - if (_additionalValue) - { - expressionsToEvaluate.emplace_back(*_additionalValue); - expressionNames.push_back(_additionalValueName); - } - for (auto const& var: m_variables) - { - expressionsToEvaluate.emplace_back(currentValue(*var.first)); - expressionNames.push_back(var.first->name()); - } - for (auto const& var: m_specialVariables) - { - expressionsToEvaluate.emplace_back(var.second->currentValue()); - expressionNames.push_back(var.first); - } - } - smt::CheckResult result; - vector values; - tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate); - - string loopComment; - if (m_loopExecutionHappened) - loopComment = - "\nNote that some information is erased after the execution of loops.\n" - "You can re-introduce information using require()."; - switch (result) - { - case smt::CheckResult::SATISFIABLE: - { - std::ostringstream message; - message << _description << " happens here"; - if (m_functionPath.size()) - { - std::ostringstream modelMessage; - modelMessage << " for:\n"; - solAssert(values.size() == expressionNames.size(), ""); - map sortedModel; - for (size_t i = 0; i < values.size(); ++i) - if (expressionsToEvaluate.at(i).name != values.at(i)) - sortedModel[expressionNames.at(i)] = values.at(i); - - for (auto const& eval: sortedModel) - modelMessage << " " << eval.first << " = " << eval.second << "\n"; - m_errorReporter.warning(_location, message.str() + loopComment, SecondarySourceLocation().append(modelMessage.str(), SourceLocation())); - } - else - { - message << "."; - m_errorReporter.warning(_location, message.str() + loopComment); - } - break; - } - case smt::CheckResult::UNSATISFIABLE: - break; - case smt::CheckResult::UNKNOWN: - m_errorReporter.warning(_location, _description + " might happen here." + loopComment); - break; - case smt::CheckResult::CONFLICTING: - m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound."); - break; - case smt::CheckResult::ERROR: - m_errorReporter.warning(_location, "Error trying to invoke SMT solver."); - break; - } - m_interface->pop(); -} - -void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description) -{ - // Do not check for const-ness if this is a constant. - if (dynamic_cast(&_condition)) - return; - - m_interface->push(); - addPathConjoinedExpression(expr(_condition)); - auto positiveResult = checkSatisfiable(); - m_interface->pop(); - - m_interface->push(); - addPathConjoinedExpression(!expr(_condition)); - auto negatedResult = checkSatisfiable(); - m_interface->pop(); - - if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR) - m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver."); - else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING) - m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound."); - else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE) - { - // everything fine. - } - else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE) - m_errorReporter.warning(_condition.location(), "Condition unreachable."); - else - { - string value; - if (positiveResult == smt::CheckResult::SATISFIABLE) - { - solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, ""); - value = "true"; - } - else - { - solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, ""); - solAssert(negatedResult == smt::CheckResult::SATISFIABLE, ""); - value = "false"; - } - m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value)); - } -} - -pair> -SMTChecker::checkSatisfiableAndGenerateModel(vector const& _expressionsToEvaluate) -{ - smt::CheckResult result; - vector values; - try - { - tie(result, values) = m_interface->check(_expressionsToEvaluate); - } - catch (smt::SolverError const& _e) - { - string description("Error querying SMT solver"); - if (_e.comment()) - description += ": " + *_e.comment(); - m_errorReporter.warning(description); - result = smt::CheckResult::ERROR; - } - - for (string& value: values) - { - try - { - // Parse and re-format nicely - value = formatNumber(bigint(value)); - } - catch (...) { } - } - - return make_pair(result, values); -} - -smt::CheckResult SMTChecker::checkSatisfiable() -{ - return checkSatisfiableAndGenerateModel({}).first; -} - -void SMTChecker::initializeFunctionCallParameters(FunctionDefinition const& _function, vector const& _callArgs) -{ - auto const& funParams = _function.parameters(); - solAssert(funParams.size() == _callArgs.size(), ""); - for (unsigned i = 0; i < funParams.size(); ++i) - if (createVariable(*funParams[i])) - m_interface->addAssertion(_callArgs[i] == newValue(*funParams[i])); - - for (auto const& variable: _function.localVariables()) - if (createVariable(*variable)) - { - newValue(*variable); - setZeroValue(*variable); - } - - if (_function.returnParameterList()) - for (auto const& retParam: _function.returnParameters()) - if (createVariable(*retParam)) - { - newValue(*retParam); - setZeroValue(*retParam); - } -} - -void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function) -{ - for (auto const& variable: _function.localVariables()) - if (createVariable(*variable)) - setZeroValue(*variable); - - for (auto const& param: _function.parameters()) - if (createVariable(*param)) - setUnknownValue(*param); - - if (_function.returnParameterList()) - for (auto const& retParam: _function.returnParameters()) - if (createVariable(*retParam)) - setZeroValue(*retParam); -} - -void SMTChecker::removeLocalVariables() -{ - for (auto it = m_variables.begin(); it != m_variables.end(); ) - { - if (it->first->isLocalVariable()) - it = m_variables.erase(it); - else - ++it; - } -} - -void SMTChecker::resetStateVariables() -{ - for (auto const& variable: m_variables) - { - if (variable.first->isStateVariable()) - { - newValue(*variable.first); - setUnknownValue(*variable.first); - } - } -} - -void SMTChecker::resetVariables(vector _variables) -{ - for (auto const* decl: _variables) - { - newValue(*decl); - setUnknownValue(*decl); - } -} - -void SMTChecker::mergeVariables(vector const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) -{ - set uniqueVars(_variables.begin(), _variables.end()); - for (auto const* decl: uniqueVars) - { - solAssert(_indicesEndTrue.count(decl) && _indicesEndFalse.count(decl), ""); - int trueIndex = _indicesEndTrue.at(decl); - int falseIndex = _indicesEndFalse.at(decl); - solAssert(trueIndex != falseIndex, ""); - m_interface->addAssertion(newValue(*decl) == smt::Expression::ite( - _condition, - valueAtIndex(*decl, trueIndex), - valueAtIndex(*decl, falseIndex)) - ); - } -} - -bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) -{ - // This might be the case for multiple calls to the same function. - if (knownVariable(_varDecl)) - return true; - auto const& type = _varDecl.type(); - solAssert(m_variables.count(&_varDecl) == 0, ""); - auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_interface); - m_variables.emplace(&_varDecl, result.second); - if (result.first) - { - m_errorReporter.warning( - _varDecl.location(), - "Assertion checker does not yet support the type of this variable." - ); - return false; - } - return true; -} - -bool SMTChecker::knownVariable(VariableDeclaration const& _decl) -{ - return m_variables.count(&_decl); -} - -smt::Expression SMTChecker::currentValue(VariableDeclaration const& _decl) -{ - solAssert(knownVariable(_decl), ""); - return m_variables.at(&_decl)->currentValue(); -} - -smt::Expression SMTChecker::valueAtIndex(VariableDeclaration const& _decl, int _index) -{ - solAssert(knownVariable(_decl), ""); - return m_variables.at(&_decl)->valueAtIndex(_index); -} - -smt::Expression SMTChecker::newValue(VariableDeclaration const& _decl) -{ - solAssert(knownVariable(_decl), ""); - return m_variables.at(&_decl)->increaseIndex(); -} - -void SMTChecker::setZeroValue(VariableDeclaration const& _decl) -{ - solAssert(knownVariable(_decl), ""); - m_variables.at(&_decl)->setZeroValue(); -} - -void SMTChecker::setUnknownValue(VariableDeclaration const& _decl) -{ - solAssert(knownVariable(_decl), ""); - m_variables.at(&_decl)->setUnknownValue(); -} - -smt::Expression SMTChecker::expr(Expression const& _e) -{ - if (!knownExpr(_e)) - { - m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." ); - createExpr(_e); - } - return m_expressions.at(&_e)->currentValue(); -} - -bool SMTChecker::knownExpr(Expression const& _e) const -{ - return m_expressions.count(&_e); -} - -bool SMTChecker::knownSpecialVariable(string const& _var) const -{ - return m_specialVariables.count(_var); -} - -void SMTChecker::createExpr(Expression const& _e) -{ - solAssert(_e.annotation().type, ""); - if (knownExpr(_e)) - m_expressions.at(&_e)->increaseIndex(); - else - { - auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_interface); - m_expressions.emplace(&_e, result.second); - if (result.first) - m_errorReporter.warning( - _e.location(), - "Assertion checker does not yet implement this type." - ); - } -} - -void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value) -{ - createExpr(_e); - m_interface->addAssertion(expr(_e) == _value); -} - -void SMTChecker::popPathCondition() -{ - solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); - m_pathConditions.pop_back(); -} - -void SMTChecker::pushPathCondition(smt::Expression const& _e) -{ - m_pathConditions.push_back(currentPathConditions() && _e); -} - -smt::Expression SMTChecker::currentPathConditions() -{ - if (m_pathConditions.empty()) - return smt::Expression(true); - return m_pathConditions.back(); -} - -void SMTChecker::addPathConjoinedExpression(smt::Expression const& _e) -{ - m_interface->addAssertion(currentPathConditions() && _e); -} - -void SMTChecker::addPathImpliedExpression(smt::Expression const& _e) -{ - m_interface->addAssertion(smt::Expression::implies(currentPathConditions(), _e)); -} - -bool SMTChecker::isRootFunction() -{ - return m_functionPath.size() == 1; -} - -bool SMTChecker::visitedFunction(FunctionDefinition const* _funDef) -{ - return contains(m_functionPath, _funDef); -} - -SMTChecker::VariableIndices SMTChecker::copyVariableIndices() -{ - VariableIndices indices; - for (auto const& var: m_variables) - indices.emplace(var.first, var.second->index()); - return indices; -} - -void SMTChecker::resetVariableIndices(VariableIndices const& _indices) -{ - for (auto const& var: _indices) - m_variables.at(var.first)->index() = var.second; -} diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h deleted file mode 100644 index a7f955dd4..000000000 --- a/libsolidity/formal/SMTChecker.h +++ /dev/null @@ -1,210 +0,0 @@ -/* - This file is part of solidity. - - solidity is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - solidity is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with solidity. If not, see . -*/ - -#pragma once - - -#include -#include - -#include - -#include - -#include - -#include -#include -#include - -namespace dev -{ -namespace solidity -{ - -class VariableUsage; -class ErrorReporter; - -class SMTChecker: private ASTConstVisitor -{ -public: - SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback const& _readCallback); - - void analyze(SourceUnit const& _sources, std::shared_ptr const& _scanner); - -private: - // TODO: Check that we do not have concurrent reads and writes to a variable, - // because the order of expression evaluation is undefined - // TODO: or just force a certain order, but people might have a different idea about that. - - virtual bool visit(ContractDefinition const& _node) override; - virtual void endVisit(ContractDefinition const& _node) override; - virtual void endVisit(VariableDeclaration const& _node) override; - virtual bool visit(FunctionDefinition const& _node) override; - virtual void endVisit(FunctionDefinition const& _node) override; - virtual bool visit(IfStatement const& _node) override; - virtual bool visit(WhileStatement const& _node) override; - virtual bool visit(ForStatement const& _node) override; - virtual void endVisit(VariableDeclarationStatement const& _node) override; - virtual void endVisit(Assignment const& _node) override; - virtual void endVisit(TupleExpression const& _node) override; - virtual void endVisit(UnaryOperation const& _node) override; - virtual void endVisit(BinaryOperation const& _node) override; - virtual void endVisit(FunctionCall const& _node) override; - virtual void endVisit(Identifier const& _node) override; - virtual void endVisit(Literal const& _node) override; - virtual void endVisit(Return const& _node) override; - virtual bool visit(MemberAccess const& _node) override; - - void arithmeticOperation(BinaryOperation const& _op); - void compareOperation(BinaryOperation const& _op); - void booleanOperation(BinaryOperation const& _op); - - void visitAssert(FunctionCall const&); - void visitRequire(FunctionCall const&); - void visitGasLeft(FunctionCall const&); - void visitBlockHash(FunctionCall const&); - /// Visits the FunctionDefinition of the called function - /// if available and inlines the return value. - void inlineFunctionCall(FunctionCall const&); - - void defineSpecialVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); - - /// Division expression in the given type. Requires special treatment because - /// of rounding for signed division. - smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); - - void assignment(VariableDeclaration const& _variable, Expression const& _value, SourceLocation const& _location); - void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, SourceLocation const& _location); - - /// Maps a variable to an SSA index. - using VariableIndices = std::unordered_map; - - /// Visits the branch given by the statement, pushes and pops the current path conditions. - /// @param _condition if present, asserts that this condition is true within the branch. - /// @returns the variable indices after visiting the branch. - VariableIndices visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr); - VariableIndices visitBranch(Statement const& _statement, smt::Expression _condition); - - /// Check that a condition can be satisfied. - void checkCondition( - smt::Expression _condition, - SourceLocation const& _location, - std::string const& _description, - std::string const& _additionalValueName = "", - smt::Expression* _additionalValue = nullptr - ); - /// Checks that a boolean condition is not constant. Do not warn if the expression - /// is a literal constant. - /// @param _description the warning string, $VALUE will be replaced by the constant value. - void checkBooleanNotConstant( - Expression const& _condition, - std::string const& _description - ); - /// Checks that the value is in the range given by the type. - void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location); - - - std::pair> - checkSatisfiableAndGenerateModel(std::vector const& _expressionsToEvaluate); - - smt::CheckResult checkSatisfiable(); - - void initializeLocalVariables(FunctionDefinition const& _function); - void initializeFunctionCallParameters(FunctionDefinition const& _function, std::vector const& _callArgs); - void resetStateVariables(); - void resetVariables(std::vector _variables); - /// Given two different branches and the touched variables, - /// merge the touched variables into after-branch ite variables - /// using the branch condition as guard. - void mergeVariables(std::vector const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); - /// Tries to create an uninitialized variable and returns true on success. - /// This fails if the type is not supported. - bool createVariable(VariableDeclaration const& _varDecl); - - /// @returns true if _delc is a variable that is known at the current point, i.e. - /// has a valid index - bool knownVariable(VariableDeclaration const& _decl); - /// @returns an expression denoting the value of the variable declared in @a _decl - /// at the current point. - smt::Expression currentValue(VariableDeclaration const& _decl); - /// @returns an expression denoting the value of the variable declared in @a _decl - /// at the given index. Does not ensure that this index exists. - smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index); - /// Allocates a new index for the declaration, updates the current - /// index to this value and returns the expression. - smt::Expression newValue(VariableDeclaration const& _decl); - - /// Sets the value of the declaration to zero. - void setZeroValue(VariableDeclaration const& _decl); - /// Resets the variable to an unknown value (in its range). - void setUnknownValue(VariableDeclaration const& decl); - - /// Returns the expression corresponding to the AST node. Throws if the expression does not exist. - smt::Expression expr(Expression const& _e); - /// Creates the expression (value can be arbitrary) - void createExpr(Expression const& _e); - /// Checks if expression was created - bool knownExpr(Expression const& _e) const; - /// Creates the expression and sets its value. - void defineExpr(Expression const& _e, smt::Expression _value); - - /// Checks if special variable was seen. - bool knownSpecialVariable(std::string const& _var) const; - - /// Adds a new path condition - void pushPathCondition(smt::Expression const& _e); - /// Remove the last path condition - void popPathCondition(); - /// Returns the conjunction of all path conditions or True if empty - smt::Expression currentPathConditions(); - /// Conjoin the current path conditions with the given parameter and add to the solver - void addPathConjoinedExpression(smt::Expression const& _e); - /// Add to the solver: the given expression implied by the current path conditions - void addPathImpliedExpression(smt::Expression const& _e); - - /// Removes local variables from the context. - void removeLocalVariables(); - - /// Copy the SSA indices of m_variables. - VariableIndices copyVariableIndices(); - /// Resets the variable indices. - void resetVariableIndices(VariableIndices const& _indices); - - std::shared_ptr m_interface; - std::shared_ptr m_variableUsage; - bool m_loopExecutionHappened = false; - /// An Expression may have multiple smt::Expression due to - /// repeated calls to the same function. - std::unordered_map> m_expressions; - std::unordered_map> m_variables; - std::unordered_map> m_specialVariables; - std::vector m_pathConditions; - ErrorReporter& m_errorReporter; - std::shared_ptr m_scanner; - - /// Stores the current path of function calls. - std::vector m_functionPath; - /// Returns true if the current function was not visited by - /// a function call. - bool isRootFunction(); - /// Returns true if _funDef was already visited. - bool visitedFunction(FunctionDefinition const* _funDef); -}; - -} -} diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp new file mode 100644 index 000000000..8f4b369da --- /dev/null +++ b/libsolidity/formal/SMTEncoder.cpp @@ -0,0 +1,1472 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace langutil; +using namespace dev::solidity; + +SMTEncoder::SMTEncoder(smt::EncodingContext& _context): + m_errorReporter(m_smtErrors), + m_context(_context) +{ +} + +bool SMTEncoder::visit(ContractDefinition const& _contract) +{ + solAssert(m_currentContract == nullptr, ""); + m_currentContract = &_contract; + + initializeStateVariables(_contract); + + return true; +} + +void SMTEncoder::endVisit(ContractDefinition const& _contract) +{ + m_context.resetAllVariables(); + + solAssert(m_currentContract == &_contract, ""); + m_currentContract = nullptr; +} + +void SMTEncoder::endVisit(VariableDeclaration const& _varDecl) +{ + if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value()) + assignment(_varDecl, *_varDecl.value()); +} + +bool SMTEncoder::visit(ModifierDefinition const&) +{ + return false; +} + +bool SMTEncoder::visit(FunctionDefinition const& _function) +{ + // Not visited by a function call + if (m_callStack.empty()) + initFunction(_function); + + m_modifierDepthStack.push_back(-1); + if (_function.isConstructor()) + { + m_errorReporter.warning( + _function.location(), + "Assertion checker does not yet support constructors." + ); + } + else + { + _function.parameterList().accept(*this); + if (_function.returnParameterList()) + _function.returnParameterList()->accept(*this); + visitFunctionOrModifier(); + } + return false; +} + +void SMTEncoder::visitFunctionOrModifier() +{ + solAssert(!m_callStack.empty(), ""); + solAssert(!m_modifierDepthStack.empty(), ""); + + ++m_modifierDepthStack.back(); + FunctionDefinition const& function = dynamic_cast(*m_callStack.back().first); + + if (m_modifierDepthStack.back() == int(function.modifiers().size())) + { + if (function.isImplemented()) + function.body().accept(*this); + } + else + { + solAssert(m_modifierDepthStack.back() < int(function.modifiers().size()), ""); + ASTPointer const& modifierInvocation = function.modifiers()[m_modifierDepthStack.back()]; + solAssert(modifierInvocation, ""); + modifierInvocation->accept(*this); + auto const& modifierDef = dynamic_cast( + *modifierInvocation->name()->annotation().referencedDeclaration + ); + vector modifierArgsExpr; + if (modifierInvocation->arguments()) + for (auto arg: *modifierInvocation->arguments()) + modifierArgsExpr.push_back(expr(*arg)); + initializeFunctionCallParameters(modifierDef, modifierArgsExpr); + pushCallStack({&modifierDef, modifierInvocation.get()}); + modifierDef.body().accept(*this); + popCallStack(); + } + + --m_modifierDepthStack.back(); +} + +bool SMTEncoder::visit(PlaceholderStatement const&) +{ + solAssert(!m_callStack.empty(), ""); + auto lastCall = popCallStack(); + visitFunctionOrModifier(); + pushCallStack(lastCall); + return true; +} + +void SMTEncoder::endVisit(FunctionDefinition const&) +{ + popCallStack(); + solAssert(m_modifierDepthStack.back() == -1, ""); + m_modifierDepthStack.pop_back(); + if (m_callStack.empty()) + m_context.popSolver(); +} + +bool SMTEncoder::visit(InlineAssembly const& _inlineAsm) +{ + m_errorReporter.warning( + _inlineAsm.location(), + "Assertion checker does not support inline assembly." + ); + return false; +} + +bool SMTEncoder::visit(IfStatement const& _node) +{ + _node.condition().accept(*this); + + auto indicesEndTrue = visitBranch(&_node.trueStatement(), expr(_node.condition())); + auto touchedVars = touchedVariables(_node.trueStatement()); + decltype(indicesEndTrue) indicesEndFalse; + if (_node.falseStatement()) + { + indicesEndFalse = visitBranch(_node.falseStatement(), !expr(_node.condition())); + touchedVars += touchedVariables(*_node.falseStatement()); + } + else + indicesEndFalse = copyVariableIndices(); + + mergeVariables(touchedVars, expr(_node.condition()), indicesEndTrue, indicesEndFalse); + + return false; +} + +void SMTEncoder::endVisit(VariableDeclarationStatement const& _varDecl) +{ + if (_varDecl.declarations().size() != 1) + { + if (auto init = _varDecl.initialValue()) + { + auto symbTuple = dynamic_pointer_cast(m_context.expression(*init)); + solAssert(symbTuple, ""); + auto const& components = symbTuple->components(); + auto const& declarations = _varDecl.declarations(); + if (!components.empty()) + { + solAssert(components.size() == declarations.size(), ""); + for (unsigned i = 0; i < declarations.size(); ++i) + if ( + components.at(i) && + declarations.at(i) && + m_context.knownVariable(*declarations.at(i)) + ) + assignment(*declarations.at(i), components.at(i)->currentValue(declarations.at(i)->type())); + } + } + } + else if (m_context.knownVariable(*_varDecl.declarations().front())) + { + if (_varDecl.initialValue()) + assignment(*_varDecl.declarations().front(), *_varDecl.initialValue()); + } + else + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet implement such variable declarations." + ); + +} + +void SMTEncoder::endVisit(Assignment const& _assignment) +{ + createExpr(_assignment); + + static set const compoundOps{ + Token::AssignAdd, + Token::AssignSub, + Token::AssignMul, + Token::AssignDiv, + Token::AssignMod + }; + Token op = _assignment.assignmentOperator(); + if (op != Token::Assign && !compoundOps.count(op)) + { + // Give it a new index anyway to keep the SSA scheme sound. + if (auto varDecl = identifierToVariable(_assignment.leftHandSide())) + m_context.newValue(*varDecl); + + m_errorReporter.warning( + _assignment.location(), + "Assertion checker does not yet implement this assignment operator." + ); + } + else if (!smt::isSupportedType(_assignment.annotation().type->category())) + { + // Give it a new index anyway to keep the SSA scheme sound. + if (auto varDecl = identifierToVariable(_assignment.leftHandSide())) + m_context.newValue(*varDecl); + } + else + { + auto const& type = _assignment.annotation().type; + vector rightArguments; + if (_assignment.rightHandSide().annotation().type->category() == Type::Category::Tuple) + { + auto symbTupleLeft = dynamic_pointer_cast(m_context.expression(_assignment.leftHandSide())); + solAssert(symbTupleLeft, ""); + auto symbTupleRight = dynamic_pointer_cast(m_context.expression(_assignment.rightHandSide())); + solAssert(symbTupleRight, ""); + + auto const& leftComponents = symbTupleLeft->components(); + auto const& rightComponents = symbTupleRight->components(); + solAssert(leftComponents.size() == rightComponents.size(), ""); + + for (unsigned i = 0; i < leftComponents.size(); ++i) + { + auto const& left = leftComponents.at(i); + auto const& right = rightComponents.at(i); + /// Right hand side tuple component cannot be empty. + solAssert(right, ""); + if (left) + rightArguments.push_back(right->currentValue(left->originalType())); + else + rightArguments.push_back(right->currentValue()); + } + } + else + { + auto rightHandSide = compoundOps.count(op) ? + compoundAssignment(_assignment) : + expr(_assignment.rightHandSide(), type); + defineExpr(_assignment, rightHandSide); + rightArguments.push_back(expr(_assignment, type)); + } + assignment( + _assignment.leftHandSide(), + rightArguments, + type, + _assignment.location() + ); + } +} + +void SMTEncoder::endVisit(TupleExpression const& _tuple) +{ + createExpr(_tuple); + + if (_tuple.isInlineArray()) + m_errorReporter.warning( + _tuple.location(), + "Assertion checker does not yet implement inline arrays." + ); + else if (_tuple.annotation().type->category() == Type::Category::Tuple) + { + auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_tuple)); + solAssert(symbTuple, ""); + if (symbTuple->components().empty()) + { + vector> components; + for (auto const& component: _tuple.components()) + if (component) + { + if (auto varDecl = identifierToVariable(*component)) + components.push_back(m_context.variable(*varDecl)); + else + { + solAssert(m_context.knownExpression(*component), ""); + components.push_back(m_context.expression(*component)); + } + } + else + components.push_back(nullptr); + solAssert(components.size() == _tuple.components().size(), ""); + symbTuple->setComponents(move(components)); + } + } + else + { + /// Parenthesized expressions are also TupleExpression regardless their type. + auto const& components = _tuple.components(); + solAssert(components.size() == 1, ""); + if (smt::isSupportedType(components.front()->annotation().type->category())) + defineExpr(_tuple, expr(*components.front())); + } +} + +void SMTEncoder::endVisit(UnaryOperation const& _op) +{ + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + + createExpr(_op); + + switch (_op.getOperator()) + { + case Token::Not: // ! + { + solAssert(smt::isBool(_op.annotation().type->category()), ""); + defineExpr(_op, !expr(_op.subExpression())); + break; + } + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) + { + + solAssert(smt::isInteger(_op.annotation().type->category()), ""); + solAssert(_op.subExpression().annotation().lValueRequested, ""); + if (auto identifier = dynamic_cast(&_op.subExpression())) + { + auto decl = identifierToVariable(*identifier); + solAssert(decl, ""); + auto innerValue = currentValue(*decl); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + assignment(*decl, newValue); + } + else if (dynamic_cast(&_op.subExpression())) + { + auto innerValue = expr(_op.subExpression()); + auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1; + defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue); + arrayIndexAssignment(_op.subExpression(), newValue); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement such increments / decrements." + ); + + break; + } + case Token::Sub: // - + { + defineExpr(_op, 0 - expr(_op.subExpression())); + break; + } + case Token::Delete: + { + auto const& subExpr = _op.subExpression(); + if (auto decl = identifierToVariable(subExpr)) + { + m_context.newValue(*decl); + m_context.setZeroValue(*decl); + } + else + { + solAssert(m_context.knownExpression(subExpr), ""); + auto const& symbVar = m_context.expression(subExpr); + symbVar->increaseIndex(); + m_context.setZeroValue(*symbVar); + if (dynamic_cast(&_op.subExpression())) + arrayIndexAssignment(_op.subExpression(), symbVar->currentValue()); + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement \"delete\" for this expression." + ); + } + break; + } + default: + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); + } +} + +bool SMTEncoder::visit(UnaryOperation const& _op) +{ + return !shortcutRationalNumber(_op); +} + +bool SMTEncoder::visit(BinaryOperation const& _op) +{ + if (shortcutRationalNumber(_op)) + return false; + if (TokenTraits::isBooleanOp(_op.getOperator())) + { + booleanOperation(_op); + return false; + } + return true; +} + +void SMTEncoder::endVisit(BinaryOperation const& _op) +{ + if (_op.annotation().type->category() == Type::Category::RationalNumber) + return; + if (TokenTraits::isBooleanOp(_op.getOperator())) + return; + + createExpr(_op); + + if (TokenTraits::isArithmeticOp(_op.getOperator())) + arithmeticOperation(_op); + else if (TokenTraits::isCompareOp(_op.getOperator())) + compareOperation(_op); + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); +} + +void SMTEncoder::endVisit(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); + createExpr(_funCall); + if (_funCall.annotation().kind == FunctionCallKind::StructConstructorCall) + { + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not yet implement this expression." + ); + return; + } + + if (_funCall.annotation().kind == FunctionCallKind::TypeConversion) + { + visitTypeConversion(_funCall); + return; + } + + FunctionType const& funType = dynamic_cast(*_funCall.expression().annotation().type); + + std::vector> const args = _funCall.arguments(); + switch (funType.kind()) + { + case FunctionType::Kind::Assert: + visitAssert(_funCall); + break; + case FunctionType::Kind::Require: + visitRequire(_funCall); + break; + case FunctionType::Kind::GasLeft: + visitGasLeft(_funCall); + break; + case FunctionType::Kind::Internal: + case FunctionType::Kind::External: + case FunctionType::Kind::DelegateCall: + case FunctionType::Kind::BareCall: + case FunctionType::Kind::BareCallCode: + case FunctionType::Kind::BareDelegateCall: + case FunctionType::Kind::BareStaticCall: + case FunctionType::Kind::Creation: + break; + case FunctionType::Kind::KECCAK256: + case FunctionType::Kind::ECRecover: + case FunctionType::Kind::SHA256: + case FunctionType::Kind::RIPEMD160: + case FunctionType::Kind::BlockHash: + case FunctionType::Kind::AddMod: + case FunctionType::Kind::MulMod: + break; + case FunctionType::Kind::Send: + case FunctionType::Kind::Transfer: + { + auto const& memberAccess = dynamic_cast(_funCall.expression()); + auto const& address = memberAccess.expression(); + auto const& value = args.front(); + solAssert(value, ""); + + smt::Expression thisBalance = m_context.balance(); + setSymbolicUnknownValue(thisBalance, TypeProvider::uint256(), m_context); + + m_context.transfer(m_context.thisAddress(), expr(address), expr(*value)); + break; + } + default: + m_errorReporter.warning( + _funCall.location(), + "Assertion checker does not yet implement this type of function call." + ); + } +} + +void SMTEncoder::initFunction(FunctionDefinition const& _function) +{ + solAssert(m_callStack.empty(), ""); + m_context.reset(); + m_context.pushSolver(); + m_pathConditions.clear(); + pushCallStack({&_function, nullptr}); + m_uninterpretedTerms.clear(); + resetStateVariables(); + initializeLocalVariables(_function); + m_arrayAssignmentHappened = false; +} + +void SMTEncoder::visitAssert(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() == 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + addPathImpliedExpression(expr(*args.front())); +} + +void SMTEncoder::visitRequire(FunctionCall const& _funCall) +{ + auto const& args = _funCall.arguments(); + solAssert(args.size() >= 1, ""); + solAssert(args.front()->annotation().type->category() == Type::Category::Bool, ""); + addPathImpliedExpression(expr(*args.front())); +} + +void SMTEncoder::visitGasLeft(FunctionCall const& _funCall) +{ + string gasLeft = "gasleft()"; + // We increase the variable index since gasleft changes + // inside a tx. + defineGlobalVariable(gasLeft, _funCall, true); + auto const& symbolicVar = m_context.globalSymbol(gasLeft); + unsigned index = symbolicVar->index(); + // We set the current value to unknown anyway to add type constraints. + m_context.setUnknownValue(*symbolicVar); + if (index > 0) + m_context.addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1)); +} + +void SMTEncoder::endVisit(Identifier const& _identifier) +{ + if (_identifier.annotation().lValueRequested) + { + // Will be translated as part of the node that requested the lvalue. + } + else if (_identifier.annotation().type->category() == Type::Category::Function) + visitFunctionIdentifier(_identifier); + else if (auto decl = identifierToVariable(_identifier)) + defineExpr(_identifier, currentValue(*decl)); + else if (_identifier.name() == "now") + defineGlobalVariable(_identifier.name(), _identifier); + else if (_identifier.name() == "this") + { + defineExpr(_identifier, m_context.thisAddress()); + m_uninterpretedTerms.insert(&_identifier); + } + else if (smt::isSupportedType(_identifier.annotation().type->category())) + // TODO: handle MagicVariableDeclaration here + m_errorReporter.warning( + _identifier.location(), + "Assertion checker does not yet support the type of this variable." + ); +} + +void SMTEncoder::visitTypeConversion(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind == FunctionCallKind::TypeConversion, ""); + solAssert(_funCall.arguments().size() == 1, ""); + auto argument = _funCall.arguments().front(); + unsigned argSize = argument->annotation().type->storageBytes(); + unsigned castSize = _funCall.annotation().type->storageBytes(); + if (argSize == castSize) + defineExpr(_funCall, expr(*argument)); + else + { + m_context.setUnknownValue(*m_context.expression(_funCall)); + auto const& funCallCategory = _funCall.annotation().type->category(); + // TODO: truncating and bytesX needs a different approach because of right padding. + if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address) + { + if (argSize < castSize) + defineExpr(_funCall, expr(*argument)); + else + { + auto const& intType = dynamic_cast(*m_context.expression(_funCall)->type()); + defineExpr(_funCall, smt::Expression::ite( + expr(*argument) >= smt::minValue(intType) && expr(*argument) <= smt::maxValue(intType), + expr(*argument), + expr(_funCall) + )); + } + } + + m_errorReporter.warning( + _funCall.location(), + "Type conversion is not yet fully supported and might yield false positives." + ); + } +} + +void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier) +{ + auto const& fType = dynamic_cast(*_identifier.annotation().type); + if (fType.returnParameterTypes().size() == 1) + { + defineGlobalVariable(fType.identifier(), _identifier); + m_context.createExpression(_identifier, m_context.globalSymbol(fType.identifier())); + } +} + +void SMTEncoder::endVisit(Literal const& _literal) +{ + solAssert(_literal.annotation().type, "Expected type for AST node"); + Type const& type = *_literal.annotation().type; + if (smt::isNumber(type.category())) + defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); + else if (smt::isBool(type.category())) + defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); + else if (smt::isStringLiteral(type.category())) + createExpr(_literal); + else + { + m_errorReporter.warning( + _literal.location(), + "Assertion checker does not yet support the type of this literal (" + + _literal.annotation().type->toString() + + ")." + ); + } +} + +void SMTEncoder::endVisit(Return const& _return) +{ + if (_return.expression() && m_context.knownExpression(*_return.expression())) + { + auto returnParams = m_callStack.back().first->returnParameters(); + if (returnParams.size() > 1) + { + auto const& symbTuple = dynamic_pointer_cast(m_context.expression(*_return.expression())); + solAssert(symbTuple, ""); + auto const& components = symbTuple->components(); + solAssert(components.size() == returnParams.size(), ""); + for (unsigned i = 0; i < returnParams.size(); ++i) + { + solAssert(components.at(i), ""); + m_context.addAssertion(components.at(i)->currentValue(returnParams.at(i)->type()) == m_context.newValue(*returnParams.at(i))); + } + } + else if (returnParams.size() == 1) + m_context.addAssertion(expr(*_return.expression()) == m_context.newValue(*returnParams.front())); + } +} + +bool SMTEncoder::visit(MemberAccess const& _memberAccess) +{ + auto const& accessType = _memberAccess.annotation().type; + if (accessType->category() == Type::Category::Function) + return true; + + createExpr(_memberAccess); + + auto const& exprType = _memberAccess.expression().annotation().type; + solAssert(exprType, ""); + auto identifier = dynamic_cast(&_memberAccess.expression()); + if (exprType->category() == Type::Category::Magic) + { + string accessedName; + if (identifier) + accessedName = identifier->name(); + else + m_errorReporter.warning( + _memberAccess.location(), + "Assertion checker does not yet support this expression." + ); + defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess); + return false; + } + else if (exprType->category() == Type::Category::TypeType) + { + if (identifier && dynamic_cast(identifier->annotation().referencedDeclaration)) + { + auto enumType = dynamic_cast(accessType); + solAssert(enumType, ""); + defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName())); + } + return false; + } + else if (exprType->category() == Type::Category::Address) + { + _memberAccess.expression().accept(*this); + if (_memberAccess.memberName() == "balance") + { + defineExpr(_memberAccess, m_context.balance(expr(_memberAccess.expression()))); + setSymbolicUnknownValue(*m_context.expression(_memberAccess), m_context); + m_uninterpretedTerms.insert(&_memberAccess); + return false; + } + } + else + m_errorReporter.warning( + _memberAccess.location(), + "Assertion checker does not yet support this expression." + ); + + return true; +} + +void SMTEncoder::endVisit(IndexAccess const& _indexAccess) +{ + createExpr(_indexAccess); + + shared_ptr array; + if (auto const& id = dynamic_cast(&_indexAccess.baseExpression())) + { + auto varDecl = identifierToVariable(*id); + solAssert(varDecl, ""); + array = m_context.variable(*varDecl); + } + else if (auto const& innerAccess = dynamic_cast(&_indexAccess.baseExpression())) + { + solAssert(m_context.knownExpression(*innerAccess), ""); + array = m_context.expression(*innerAccess); + } + else + { + m_errorReporter.warning( + _indexAccess.location(), + "Assertion checker does not yet implement this expression." + ); + return; + } + + if (!_indexAccess.indexExpression()) + { + solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, ""); + return; + } + + solAssert(array, ""); + defineExpr(_indexAccess, smt::Expression::select( + array->currentValue(), + expr(*_indexAccess.indexExpression()) + )); + setSymbolicUnknownValue( + expr(_indexAccess), + _indexAccess.annotation().type, + m_context + ); + m_uninterpretedTerms.insert(&_indexAccess); +} + +void SMTEncoder::arrayAssignment() +{ + m_arrayAssignmentHappened = true; +} + +void SMTEncoder::arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide) +{ + auto const& indexAccess = dynamic_cast(_expr); + if (auto const& id = dynamic_cast(&indexAccess.baseExpression())) + { + auto varDecl = identifierToVariable(*id); + solAssert(varDecl, ""); + + if (varDecl->hasReferenceOrMappingType()) + m_context.resetVariables([&](VariableDeclaration const& _var) { + if (_var == *varDecl) + return false; + + // If both are state variables no need to clear knowledge. + if (_var.isStateVariable() && varDecl->isStateVariable()) + return false; + + TypePointer prefix = _var.type(); + TypePointer originalType = typeWithoutPointer(varDecl->type()); + while ( + prefix->category() == Type::Category::Mapping || + prefix->category() == Type::Category::Array + ) + { + if (*originalType == *typeWithoutPointer(prefix)) + return true; + if (prefix->category() == Type::Category::Mapping) + { + auto mapPrefix = dynamic_cast(prefix); + solAssert(mapPrefix, ""); + prefix = mapPrefix->valueType(); + } + else + { + auto arrayPrefix = dynamic_cast(prefix); + solAssert(arrayPrefix, ""); + prefix = arrayPrefix->baseType(); + } + } + return false; + }); + + smt::Expression store = smt::Expression::store( + m_context.variable(*varDecl)->currentValue(), + expr(*indexAccess.indexExpression()), + _rightHandSide + ); + m_context.addAssertion(m_context.newValue(*varDecl) == store); + // Update the SMT select value after the assignment, + // necessary for sound models. + defineExpr(indexAccess, smt::Expression::select( + m_context.variable(*varDecl)->currentValue(), + expr(*indexAccess.indexExpression()) + )); + } + else if (dynamic_cast(&indexAccess.baseExpression())) + { + auto identifier = dynamic_cast(leftmostBase(indexAccess)); + if (identifier) + { + auto varDecl = identifierToVariable(*identifier); + m_context.newValue(*varDecl); + } + + m_errorReporter.warning( + indexAccess.location(), + "Assertion checker does not yet implement assignments to multi-dimensional mappings or arrays." + ); + } + else + m_errorReporter.warning( + _expr.location(), + "Assertion checker does not yet implement this expression." + ); +} + +void SMTEncoder::defineGlobalVariable(string const& _name, Expression const& _expr, bool _increaseIndex) +{ + if (!m_context.knownGlobalSymbol(_name)) + { + bool abstract = m_context.createGlobalSymbol(_name, _expr); + if (abstract) + m_errorReporter.warning( + _expr.location(), + "Assertion checker does not yet support this global variable." + ); + } + else if (_increaseIndex) + m_context.globalSymbol(_name)->increaseIndex(); + // The default behavior is not to increase the index since + // most of the global values stay the same throughout a tx. + if (smt::isSupportedType(_expr.annotation().type->category())) + defineExpr(_expr, m_context.globalSymbol(_name)->currentValue()); +} + +bool SMTEncoder::shortcutRationalNumber(Expression const& _expr) +{ + if (_expr.annotation().type->category() == Type::Category::RationalNumber) + { + auto rationalType = dynamic_cast(_expr.annotation().type); + solAssert(rationalType, ""); + if (rationalType->isNegative()) + defineExpr(_expr, smt::Expression(u2s(rationalType->literalValue(nullptr)))); + else + defineExpr(_expr, smt::Expression(rationalType->literalValue(nullptr))); + return true; + } + return false; +} + +void SMTEncoder::arithmeticOperation(BinaryOperation const& _op) +{ + auto type = _op.annotation().commonType; + solAssert(type, ""); + if (type->category() == Type::Category::Integer) + { + switch (_op.getOperator()) + { + case Token::Add: + case Token::Sub: + case Token::Mul: + case Token::Div: + case Token::Mod: + { + auto values = arithmeticOperation( + _op.getOperator(), + expr(_op.leftExpression()), + expr(_op.rightExpression()), + _op.annotation().commonType, + _op + ); + defineExpr(_op, values.first); + break; + } + default: + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator." + ); + } + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement this operator for type " + type->richIdentifier() + "." + ); +} + +pair SMTEncoder::arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& +) +{ + static set validOperators{ + Token::Add, + Token::Sub, + Token::Mul, + Token::Div, + Token::Mod + }; + solAssert(validOperators.count(_op), ""); + solAssert(_commonType, ""); + solAssert(_commonType->category() == Type::Category::Integer, ""); + + auto const& intType = dynamic_cast(*_commonType); + smt::Expression valueNoMod( + _op == Token::Add ? _left + _right : + _op == Token::Sub ? _left - _right : + _op == Token::Div ? division(_left, _right, intType) : + _op == Token::Mul ? _left * _right : + /*_op == Token::Mod*/ _left % _right + ); + + if (_op == Token::Div || _op == Token::Mod) + m_context.addAssertion(_right != 0); + + smt::Expression intValueRange = (0 - smt::minValue(intType)) + smt::maxValue(intType) + 1; + auto value = smt::Expression::ite( + valueNoMod > smt::maxValue(intType), + valueNoMod % intValueRange, + smt::Expression::ite( + valueNoMod < smt::minValue(intType), + valueNoMod % intValueRange, + valueNoMod + ) + ); + + if (intType.isSigned()) + value = smt::Expression::ite( + value > smt::maxValue(intType), + value - intValueRange, + value + ); + + return {value, valueNoMod}; +} + +void SMTEncoder::compareOperation(BinaryOperation const& _op) +{ + auto const& commonType = _op.annotation().commonType; + solAssert(commonType, ""); + if (smt::isSupportedType(commonType->category())) + { + smt::Expression left(expr(_op.leftExpression(), commonType)); + smt::Expression right(expr(_op.rightExpression(), commonType)); + Token op = _op.getOperator(); + shared_ptr value; + if (smt::isNumber(commonType->category())) + { + value = make_shared( + op == Token::Equal ? (left == right) : + op == Token::NotEqual ? (left != right) : + op == Token::LessThan ? (left < right) : + op == Token::LessThanOrEqual ? (left <= right) : + op == Token::GreaterThan ? (left > right) : + /*op == Token::GreaterThanOrEqual*/ (left >= right) + ); + } + else // Bool + { + solUnimplementedAssert(smt::isBool(commonType->category()), "Operation not yet supported"); + value = make_shared( + op == Token::Equal ? (left == right) : + /*op == Token::NotEqual*/ (left != right) + ); + } + // TODO: check that other values for op are not possible. + defineExpr(_op, *value); + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for comparisons" + ); +} + +void SMTEncoder::booleanOperation(BinaryOperation const& _op) +{ + solAssert(_op.getOperator() == Token::And || _op.getOperator() == Token::Or, ""); + solAssert(_op.annotation().commonType, ""); + if (_op.annotation().commonType->category() == Type::Category::Bool) + { + // @TODO check that both of them are not constant + _op.leftExpression().accept(*this); + if (_op.getOperator() == Token::And) + { + auto indicesAfterSecond = visitBranch(&_op.rightExpression(), expr(_op.leftExpression())); + mergeVariables(touchedVariables(_op.rightExpression()), !expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond); + defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression())); + } + else + { + auto indicesAfterSecond = visitBranch(&_op.rightExpression(), !expr(_op.leftExpression())); + mergeVariables(touchedVariables(_op.rightExpression()), expr(_op.leftExpression()), copyVariableIndices(), indicesAfterSecond); + defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression())); + } + } + else + m_errorReporter.warning( + _op.location(), + "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" + ); +} + +smt::Expression SMTEncoder::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type) +{ + // Signed division in SMTLIB2 rounds differently for negative division. + if (_type.isSigned()) + return (smt::Expression::ite( + _left >= 0, + smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))), + smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right)) + )); + else + return _left / _right; +} + +void SMTEncoder::assignment( + Expression const& _left, + vector const& _right, + TypePointer const& _type, + langutil::SourceLocation const& _location +) +{ + if (!smt::isSupportedType(_type->category())) + { + // Give it a new index anyway to keep the SSA scheme sound. + if (auto varDecl = identifierToVariable(_left)) + m_context.newValue(*varDecl); + + m_errorReporter.warning( + _location, + "Assertion checker does not yet implement type " + _type->toString() + ); + } + else if (auto varDecl = identifierToVariable(_left)) + { + solAssert(_right.size() == 1, ""); + assignment(*varDecl, _right.front()); + } + else if (dynamic_cast(&_left)) + { + solAssert(_right.size() == 1, ""); + arrayIndexAssignment(_left, _right.front()); + } + else if (auto tuple = dynamic_cast(&_left)) + { + auto const& components = tuple->components(); + if (!_right.empty()) + { + solAssert(_right.size() == components.size(), ""); + for (unsigned i = 0; i < _right.size(); ++i) + if (auto component = components.at(i)) + assignment(*component, {_right.at(i)}, component->annotation().type, component->location()); + } + } + else + m_errorReporter.warning( + _location, + "Assertion checker does not yet implement such assignments." + ); +} + +smt::Expression SMTEncoder::compoundAssignment(Assignment const& _assignment) +{ + static map const compoundToArithmetic{ + {Token::AssignAdd, Token::Add}, + {Token::AssignSub, Token::Sub}, + {Token::AssignMul, Token::Mul}, + {Token::AssignDiv, Token::Div}, + {Token::AssignMod, Token::Mod} + }; + Token op = _assignment.assignmentOperator(); + solAssert(compoundToArithmetic.count(op), ""); + auto decl = identifierToVariable(_assignment.leftHandSide()); + auto values = arithmeticOperation( + compoundToArithmetic.at(op), + decl ? currentValue(*decl) : expr(_assignment.leftHandSide()), + expr(_assignment.rightHandSide()), + _assignment.annotation().type, + _assignment + ); + return values.first; +} + +void SMTEncoder::assignment(VariableDeclaration const& _variable, Expression const& _value) +{ + assignment(_variable, expr(_value, _variable.type())); +} + +void SMTEncoder::assignment(VariableDeclaration const& _variable, smt::Expression const& _value) +{ + TypePointer type = _variable.type(); + if (type->category() == Type::Category::Mapping) + arrayAssignment(); + m_context.addAssertion(m_context.newValue(_variable) == _value); +} + +SMTEncoder::VariableIndices SMTEncoder::visitBranch(ASTNode const* _statement, smt::Expression _condition) +{ + return visitBranch(_statement, &_condition); +} + +SMTEncoder::VariableIndices SMTEncoder::visitBranch(ASTNode const* _statement, smt::Expression const* _condition) +{ + auto indicesBeforeBranch = copyVariableIndices(); + if (_condition) + pushPathCondition(*_condition); + _statement->accept(*this); + if (_condition) + popPathCondition(); + auto indicesAfterBranch = copyVariableIndices(); + resetVariableIndices(indicesBeforeBranch); + return indicesAfterBranch; +} + +void SMTEncoder::initializeFunctionCallParameters(CallableDeclaration const& _function, vector const& _callArgs) +{ + auto const& funParams = _function.parameters(); + solAssert(funParams.size() == _callArgs.size(), ""); + for (unsigned i = 0; i < funParams.size(); ++i) + if (createVariable(*funParams[i])) + { + m_context.addAssertion(_callArgs[i] == m_context.newValue(*funParams[i])); + if (funParams[i]->annotation().type->category() == Type::Category::Mapping) + m_arrayAssignmentHappened = true; + } + + for (auto const& variable: _function.localVariables()) + if (createVariable(*variable)) + { + m_context.newValue(*variable); + m_context.setZeroValue(*variable); + } + + if (_function.returnParameterList()) + for (auto const& retParam: _function.returnParameters()) + if (createVariable(*retParam)) + { + m_context.newValue(*retParam); + m_context.setZeroValue(*retParam); + } +} + +void SMTEncoder::initializeStateVariables(ContractDefinition const& _contract) +{ + for (auto var: _contract.stateVariablesIncludingInherited()) + createVariable(*var); +} + +void SMTEncoder::initializeLocalVariables(FunctionDefinition const& _function) +{ + for (auto const& variable: _function.localVariables()) + if (createVariable(*variable)) + m_context.setZeroValue(*variable); + + for (auto const& param: _function.parameters()) + if (createVariable(*param)) + m_context.setUnknownValue(*param); + + if (_function.returnParameterList()) + for (auto const& retParam: _function.returnParameters()) + if (createVariable(*retParam)) + m_context.setZeroValue(*retParam); +} + +void SMTEncoder::resetStateVariables() +{ + m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.isStateVariable(); }); +} + +TypePointer SMTEncoder::typeWithoutPointer(TypePointer const& _type) +{ + if (auto refType = dynamic_cast(_type)) + return TypeProvider::withLocationIfReference(refType->location(), _type); + return _type; +} + +void SMTEncoder::mergeVariables(set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) +{ + auto cmp = [] (VariableDeclaration const* var1, VariableDeclaration const* var2) { + return var1->id() < var2->id(); + }; + set sortedVars(begin(_variables), end(_variables), cmp); + + /// Knowledge about references is erased if a reference is assigned, + /// so those also need their SSA's merged. + /// This does not cause scope harm since the symbolic variables + /// are kept alive. + for (auto const& var: _indicesEndTrue) + { + solAssert(_indicesEndFalse.count(var.first), ""); + if ( + _indicesEndFalse.at(var.first) != var.second && + !sortedVars.count(var.first) + ) + sortedVars.insert(var.first); + } + + for (auto const* decl: sortedVars) + { + solAssert(_indicesEndTrue.count(decl) && _indicesEndFalse.count(decl), ""); + int trueIndex = _indicesEndTrue.at(decl); + int falseIndex = _indicesEndFalse.at(decl); + solAssert(trueIndex != falseIndex, ""); + m_context.addAssertion(m_context.newValue(*decl) == smt::Expression::ite( + _condition, + valueAtIndex(*decl, trueIndex), + valueAtIndex(*decl, falseIndex)) + ); + } +} + +smt::Expression SMTEncoder::currentValue(VariableDeclaration const& _decl) +{ + solAssert(m_context.knownVariable(_decl), ""); + return m_context.variable(_decl)->currentValue(); +} + +smt::Expression SMTEncoder::valueAtIndex(VariableDeclaration const& _decl, int _index) +{ + solAssert(m_context.knownVariable(_decl), ""); + return m_context.variable(_decl)->valueAtIndex(_index); +} + +bool SMTEncoder::createVariable(VariableDeclaration const& _varDecl) +{ + if (m_context.knownVariable(_varDecl)) + return true; + bool abstract = m_context.createVariable(_varDecl); + if (abstract) + { + m_errorReporter.warning( + _varDecl.location(), + "Assertion checker does not yet support the type of this variable." + ); + return false; + } + return true; +} + +smt::Expression SMTEncoder::expr(Expression const& _e, TypePointer _targetType) +{ + if (!m_context.knownExpression(_e)) + { + m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." ); + createExpr(_e); + } + + return m_context.expression(_e)->currentValue(_targetType); +} + +void SMTEncoder::createExpr(Expression const& _e) +{ + bool abstract = m_context.createExpression(_e); + if (abstract) + m_errorReporter.warning( + _e.location(), + "Assertion checker does not yet implement type " + _e.annotation().type->toString() + ); +} + +void SMTEncoder::defineExpr(Expression const& _e, smt::Expression _value) +{ + createExpr(_e); + solAssert(smt::smtKind(_e.annotation().type->category()) != smt::Kind::Function, "Equality operator applied to type that is not fully supported"); + m_context.addAssertion(expr(_e) == _value); +} + +void SMTEncoder::popPathCondition() +{ + solAssert(m_pathConditions.size() > 0, "Cannot pop path condition, empty."); + m_pathConditions.pop_back(); +} + +void SMTEncoder::pushPathCondition(smt::Expression const& _e) +{ + m_pathConditions.push_back(currentPathConditions() && _e); +} + +smt::Expression SMTEncoder::currentPathConditions() +{ + if (m_pathConditions.empty()) + return smt::Expression(true); + return m_pathConditions.back(); +} + +SecondarySourceLocation SMTEncoder::callStackMessage(vector const& _callStack) +{ + SecondarySourceLocation callStackLocation; + solAssert(!_callStack.empty(), ""); + callStackLocation.append("Callstack: ", SourceLocation()); + for (auto const& call: _callStack | boost::adaptors::reversed) + if (call.second) + callStackLocation.append("", call.second->location()); + return callStackLocation; +} + +pair SMTEncoder::popCallStack() +{ + solAssert(!m_callStack.empty(), ""); + auto lastCalled = m_callStack.back(); + m_callStack.pop_back(); + return lastCalled; +} + +void SMTEncoder::pushCallStack(CallStackEntry _entry) +{ + m_callStack.push_back(_entry); +} + +void SMTEncoder::addPathImpliedExpression(smt::Expression const& _e) +{ + m_context.addAssertion(smt::Expression::implies(currentPathConditions(), _e)); +} + +bool SMTEncoder::isRootFunction() +{ + return m_callStack.size() == 1; +} + +bool SMTEncoder::visitedFunction(FunctionDefinition const* _funDef) +{ + for (auto const& call: m_callStack) + if (call.first == _funDef) + return true; + return false; +} + +SMTEncoder::VariableIndices SMTEncoder::copyVariableIndices() +{ + VariableIndices indices; + for (auto const& var: m_context.variables()) + indices.emplace(var.first, var.second->index()); + return indices; +} + +void SMTEncoder::resetVariableIndices(VariableIndices const& _indices) +{ + for (auto const& var: _indices) + m_context.variable(*var.first)->index() = var.second; +} + +Expression const* SMTEncoder::leftmostBase(IndexAccess const& _indexAccess) +{ + Expression const* base = &_indexAccess.baseExpression(); + while (auto access = dynamic_cast(base)) + base = &access->baseExpression(); + return base; +} + +set SMTEncoder::touchedVariables(ASTNode const& _node) +{ + solAssert(!m_callStack.empty(), ""); + vector callStack; + for (auto const& call: m_callStack) + callStack.push_back(call.first); + return m_variableUsage.touchedVariables(_node, callStack); +} + +VariableDeclaration const* SMTEncoder::identifierToVariable(Expression const& _expr) +{ + if (auto identifier = dynamic_cast(&_expr)) + { + if (auto decl = dynamic_cast(identifier->annotation().referencedDeclaration)) + { + solAssert(m_context.knownVariable(*decl), ""); + return decl; + } + } + return nullptr; +} + +string SMTEncoder::extraComment() +{ + string extra; + if (m_arrayAssignmentHappened) + extra += + "\nNote that array aliasing is not supported," + " therefore all mapping information is erased after" + " a mapping local variable/parameter is assigned.\n" + "You can re-introduce information using require()."; + return extra; +} + +FunctionDefinition const* SMTEncoder::functionCallToDefinition(FunctionCall const& _funCall) +{ + if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + return nullptr; + + FunctionDefinition const* funDef = nullptr; + Expression const* calledExpr = &_funCall.expression(); + + if (TupleExpression const* fun = dynamic_cast(&_funCall.expression())) + { + solAssert(fun->components().size() == 1, ""); + calledExpr = fun->components().front().get(); + } + + if (Identifier const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + else if (MemberAccess const* fun = dynamic_cast(calledExpr)) + funDef = dynamic_cast(fun->annotation().referencedDeclaration); + + return funDef; +} + +void SMTEncoder::createReturnedExpressions(FunctionCall const& _funCall) +{ + FunctionDefinition const* funDef = functionCallToDefinition(_funCall); + if (!funDef) + return; + + auto const& returnParams = funDef->returnParameters(); + for (auto param: returnParams) + createVariable(*param); + + if (returnParams.size() > 1) + { + auto const& symbTuple = dynamic_pointer_cast(m_context.expression(_funCall)); + solAssert(symbTuple, ""); + if (symbTuple->components().empty()) + { + vector> components; + for (auto param: returnParams) + { + solAssert(m_context.knownVariable(*param), ""); + components.push_back(m_context.variable(*param)); + } + symbTuple->setComponents(move(components)); + } + } + else if (returnParams.size() == 1) + defineExpr(_funCall, currentValue(*returnParams.front())); +} diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h new file mode 100644 index 000000000..04984d090 --- /dev/null +++ b/libsolidity/formal/SMTEncoder.h @@ -0,0 +1,259 @@ +/* + 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 . +*/ +/** + * Encodes Solidity into SMT expressions without creating + * any verification targets. + * Also implements the SSA scheme for branches. + */ + +#pragma once + + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace dev +{ +namespace solidity +{ + +class SMTEncoder: public ASTConstVisitor +{ +public: + SMTEncoder(smt::EncodingContext& _context); + + /// @returns the leftmost identifier in a multi-d IndexAccess. + static Expression const* leftmostBase(IndexAccess const& _indexAccess); + + /// @returns the FunctionDefinition of a FunctionCall + /// if possible or nullptr. + static FunctionDefinition const* functionCallToDefinition(FunctionCall const& _funCall); + +protected: + // TODO: Check that we do not have concurrent reads and writes to a variable, + // because the order of expression evaluation is undefined + // TODO: or just force a certain order, but people might have a different idea about that. + + bool visit(ContractDefinition const& _node) override; + void endVisit(ContractDefinition const& _node) override; + void endVisit(VariableDeclaration const& _node) override; + bool visit(ModifierDefinition const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + bool visit(PlaceholderStatement const& _node) override; + bool visit(IfStatement const& _node) override; + bool visit(WhileStatement const&) override { return false; } + bool visit(ForStatement const&) override { return false; } + void endVisit(VariableDeclarationStatement const& _node) override; + void endVisit(Assignment const& _node) override; + void endVisit(TupleExpression const& _node) override; + bool visit(UnaryOperation const& _node) override; + void endVisit(UnaryOperation const& _node) override; + bool visit(BinaryOperation const& _node) override; + void endVisit(BinaryOperation const& _node) override; + void endVisit(FunctionCall const& _node) override; + void endVisit(Identifier const& _node) override; + void endVisit(Literal const& _node) override; + void endVisit(Return const& _node) override; + bool visit(MemberAccess const& _node) override; + void endVisit(IndexAccess const& _node) override; + bool visit(InlineAssembly const& _node) override; + void endVisit(Break const&) override {} + void endVisit(Continue const&) override {} + + /// Do not visit subtree if node is a RationalNumber. + /// Symbolic _expr is the rational literal. + bool shortcutRationalNumber(Expression const& _expr); + void arithmeticOperation(BinaryOperation const& _op); + /// @returns _op(_left, _right) with and without modular arithmetic. + /// Used by the function above, compound assignments and + /// unary increment/decrement. + virtual std::pair arithmeticOperation( + Token _op, + smt::Expression const& _left, + smt::Expression const& _right, + TypePointer const& _commonType, + Expression const& _expression + ); + void compareOperation(BinaryOperation const& _op); + void booleanOperation(BinaryOperation const& _op); + + void initFunction(FunctionDefinition const& _function); + void visitAssert(FunctionCall const& _funCall); + void visitRequire(FunctionCall const& _funCall); + void visitGasLeft(FunctionCall const& _funCall); + void visitTypeConversion(FunctionCall const& _funCall); + void visitFunctionIdentifier(Identifier const& _identifier); + + /// Encodes a modifier or function body according to the modifier + /// visit depth. + void visitFunctionOrModifier(); + + /// Defines a new global variable or function. + void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false); + + /// Handles the side effects of assignment + /// to variable of some SMT array type + /// while aliasing is not supported. + void arrayAssignment(); + /// Handles assignment to SMT array index. + void arrayIndexAssignment(Expression const& _expr, smt::Expression const& _rightHandSide); + + /// Division expression in the given type. Requires special treatment because + /// of rounding for signed division. + smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type); + + void assignment(VariableDeclaration const& _variable, Expression const& _value); + /// Handles assignments to variables of different types. + void assignment(VariableDeclaration const& _variable, smt::Expression const& _value); + /// Handles assignments between generic expressions. + /// Will also be used for assignments of tuple components. + void assignment( + Expression const& _left, + std::vector const& _right, + TypePointer const& _type, + langutil::SourceLocation const& _location + ); + /// Computes the right hand side of a compound assignment. + smt::Expression compoundAssignment(Assignment const& _assignment); + + /// Maps a variable to an SSA index. + using VariableIndices = std::unordered_map; + + /// Visits the branch given by the statement, pushes and pops the current path conditions. + /// @param _condition if present, asserts that this condition is true within the branch. + /// @returns the variable indices after visiting the branch. + VariableIndices visitBranch(ASTNode const* _statement, smt::Expression const* _condition = nullptr); + VariableIndices visitBranch(ASTNode const* _statement, smt::Expression _condition); + + using CallStackEntry = std::pair; + + void initializeStateVariables(ContractDefinition const& _contract); + void initializeLocalVariables(FunctionDefinition const& _function); + void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector const& _callArgs); + void resetStateVariables(); + /// @returns the type without storage pointer information if it has it. + TypePointer typeWithoutPointer(TypePointer const& _type); + + /// Given two different branches and the touched variables, + /// merge the touched variables into after-branch ite variables + /// using the branch condition as guard. + void mergeVariables(std::set const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse); + /// Tries to create an uninitialized variable and returns true on success. + bool createVariable(VariableDeclaration const& _varDecl); + + /// @returns an expression denoting the value of the variable declared in @a _decl + /// at the current point. + smt::Expression currentValue(VariableDeclaration const& _decl); + /// @returns an expression denoting the value of the variable declared in @a _decl + /// at the given index. Does not ensure that this index exists. + smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index); + /// Returns the expression corresponding to the AST node. + /// If _targetType is not null apply conversion. + /// Throws if the expression does not exist. + smt::Expression expr(Expression const& _e, TypePointer _targetType = nullptr); + /// Creates the expression (value can be arbitrary) + void createExpr(Expression const& _e); + /// Creates the expression and sets its value. + void defineExpr(Expression const& _e, smt::Expression _value); + + /// Adds a new path condition + void pushPathCondition(smt::Expression const& _e); + /// Remove the last path condition + void popPathCondition(); + /// Returns the conjunction of all path conditions or True if empty + smt::Expression currentPathConditions(); + /// @returns a human-readable call stack. Used for models. + langutil::SecondarySourceLocation callStackMessage(std::vector const& _callStack); + /// Copies and pops the last called node. + CallStackEntry popCallStack(); + /// Adds (_definition, _node) to the callstack. + void pushCallStack(CallStackEntry _entry); + /// Add to the solver: the given expression implied by the current path conditions + void addPathImpliedExpression(smt::Expression const& _e); + + /// Copy the SSA indices of m_variables. + VariableIndices copyVariableIndices(); + /// Resets the variable indices. + void resetVariableIndices(VariableIndices const& _indices); + + /// @returns variables that are touched in _node's subtree. + std::set touchedVariables(ASTNode const& _node); + + /// @returns the VariableDeclaration referenced by an Identifier or nullptr. + VariableDeclaration const* identifierToVariable(Expression const& _expr); + + /// Creates symbolic expressions for the returned values + /// and set them as the components of the symbolic tuple. + void createReturnedExpressions(FunctionCall const& _funCall); + + /// @returns a note to be added to warnings. + std::string extraComment(); + + smt::VariableUsage m_variableUsage; + bool m_arrayAssignmentHappened = false; + // True if the "No SMT solver available" warning was already created. + bool m_noSolverWarning = false; + + /// Stores the instances of an Uninterpreted Function applied to arguments. + /// These may be direct application of UFs or Array index access. + /// Used to retrieve models. + std::set m_uninterpretedTerms; + std::vector m_pathConditions; + /// Local SMTEncoder ErrorReporter. + /// This is necessary to show the "No SMT solver available" + /// warning before the others in case it's needed. + langutil::ErrorReporter m_errorReporter; + langutil::ErrorList m_smtErrors; + + /// Stores the current function/modifier call/invocation path. + std::vector m_callStack; + /// Returns true if the current function was not visited by + /// a function call. + bool isRootFunction(); + /// Returns true if _funDef was already visited. + bool visitedFunction(FunctionDefinition const* _funDef); + + /// Depth of visit to modifiers. + /// When m_modifierDepth == #modifiers the function can be visited + /// when placeholder is visited. + /// Needs to be a stack because of function calls. + std::vector m_modifierDepthStack; + + ContractDefinition const* m_currentContract = nullptr; + + /// Stores the context of the encoding. + smt::EncodingContext& m_context; +}; + +} +} diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp index a6c1f87c7..c0d37431e 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -17,28 +17,25 @@ #include -#include -#include +#include -#include #include +#include #include -#include +#include #include #include #include #include -#include -#include using namespace std; using namespace dev; using namespace dev::solidity; using namespace dev::solidity::smt; -SMTLib2Interface::SMTLib2Interface(ReadCallback::Callback const& _queryCallback): - m_queryCallback(_queryCallback) +SMTLib2Interface::SMTLib2Interface(map const& _queryResponses): + m_queryResponses(_queryResponses) { reset(); } @@ -47,10 +44,9 @@ void SMTLib2Interface::reset() { m_accumulatedOutput.clear(); m_accumulatedOutput.emplace_back(); - m_constants.clear(); - m_functions.clear(); + m_variables.clear(); write("(set-option :produce-models true)"); - write("(set-logic QF_UFLIA)"); + write("(set-logic ALL)"); } void SMTLib2Interface::push() @@ -64,48 +60,45 @@ void SMTLib2Interface::pop() m_accumulatedOutput.pop_back(); } -void SMTLib2Interface::declareFunction(string _name, Sort _domain, Sort _codomain) +void SMTLib2Interface::declareVariable(string const& _name, Sort const& _sort) +{ + if (_sort.kind == Kind::Function) + declareFunction(_name, _sort); + else if (!m_variables.count(_name)) + { + m_variables.insert(_name); + write("(declare-fun |" + _name + "| () " + toSmtLibSort(_sort) + ')'); + } +} + +void SMTLib2Interface::declareFunction(string const& _name, Sort const& _sort) { + solAssert(_sort.kind == smt::Kind::Function, ""); // TODO Use domain and codomain as key as well - if (!m_functions.count(_name)) + if (!m_variables.count(_name)) { - m_functions.insert(_name); + FunctionSort fSort = dynamic_cast(_sort); + string domain = toSmtLibSort(fSort.domain); + string codomain = toSmtLibSort(*fSort.codomain); + m_variables.insert(_name); write( "(declare-fun |" + _name + - "| (" + - (_domain == Sort::Int ? "Int" : "Bool") + - ") " + - (_codomain == Sort::Int ? "Int" : "Bool") + + "| " + + domain + + " " + + codomain + ")" ); } } -void SMTLib2Interface::declareInteger(string _name) -{ - if (!m_constants.count(_name)) - { - m_constants.insert(_name); - write("(declare-const |" + _name + "| Int)"); - } -} - -void SMTLib2Interface::declareBool(string _name) -{ - if (!m_constants.count(_name)) - { - m_constants.insert(_name); - write("(declare-const |" + _name + "| Bool)"); - } -} - -void SMTLib2Interface::addAssertion(Expression const& _expr) +void SMTLib2Interface::addAssertion(smt::Expression const& _expr) { write("(assert " + toSExpr(_expr) + ")"); } -pair> SMTLib2Interface::check(vector const& _expressionsToEvaluate) +pair> SMTLib2Interface::check(vector const& _expressionsToEvaluate) { string response = querySolver( boost::algorithm::join(m_accumulatedOutput, "\n") + @@ -129,24 +122,66 @@ pair> SMTLib2Interface::check(vector con return make_pair(result, values); } -string SMTLib2Interface::toSExpr(Expression const& _expr) +string SMTLib2Interface::toSExpr(smt::Expression const& _expr) { if (_expr.arguments.empty()) return _expr.name; - std::string sexpr = "(" + _expr.name; - for (auto const& arg: _expr.arguments) - sexpr += " " + toSExpr(arg); + + std::string sexpr = "("; + if (_expr.name == "const_array") + { + solAssert(_expr.arguments.size() == 2, ""); + auto sortSort = std::dynamic_pointer_cast(_expr.arguments.at(0).sort); + solAssert(sortSort, ""); + auto arraySort = dynamic_pointer_cast(sortSort->inner); + solAssert(arraySort, ""); + sexpr += "(as const " + toSmtLibSort(*arraySort) + ") "; + sexpr += toSExpr(_expr.arguments.at(1)); + } + else + { + sexpr += _expr.name; + for (auto const& arg: _expr.arguments) + sexpr += " " + toSExpr(arg); + } sexpr += ")"; return sexpr; } +string SMTLib2Interface::toSmtLibSort(Sort const& _sort) +{ + switch (_sort.kind) + { + case Kind::Int: + return "Int"; + case Kind::Bool: + return "Bool"; + case Kind::Array: + { + auto const& arraySort = dynamic_cast(_sort); + return "(Array " + toSmtLibSort(*arraySort.domain) + ' ' + toSmtLibSort(*arraySort.range) + ')'; + } + default: + solAssert(false, "Invalid SMT sort"); + } +} + +string SMTLib2Interface::toSmtLibSort(vector const& _sorts) +{ + string ssort("("); + for (auto const& sort: _sorts) + ssort += toSmtLibSort(*sort) + " "; + ssort += ")"; + return ssort; +} + void SMTLib2Interface::write(string _data) { solAssert(!m_accumulatedOutput.empty(), ""); m_accumulatedOutput.back() += move(_data) + "\n"; } -string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _expressionsToEvaluate) +string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _expressionsToEvaluate) { string command; if (_expressionsToEvaluate.empty()) @@ -157,8 +192,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector const& _ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) { auto const& e = _expressionsToEvaluate.at(i); - solAssert(e.sort == Sort::Int || e.sort == Sort::Bool, "Invalid sort for expression to evaluate."); - command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + ")\n"; + solAssert(e.sort->kind == Kind::Int || e.sort->kind == Kind::Bool, "Invalid sort for expression to evaluate."); + command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort->kind == Kind::Int ? "Int" : "Bool") + ")\n"; command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n"; } command += "(check-sat)\n"; @@ -189,11 +224,12 @@ vector SMTLib2Interface::parseValues(string::const_iterator _start, stri string SMTLib2Interface::querySolver(string const& _input) { - if (!m_queryCallback) - BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment("No SMT solver available.")); - - ReadCallback::Result queryResult = m_queryCallback(_input); - if (!queryResult.success) - BOOST_THROW_EXCEPTION(SolverError() << errinfo_comment(queryResult.responseOrErrorMessage)); - return queryResult.responseOrErrorMessage; + h256 inputHash = dev::keccak256(_input); + if (m_queryResponses.count(inputHash)) + return m_queryResponses.at(inputHash); + else + { + m_unhandledQueries.push_back(_input); + return "unknown\n"; + } } diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h index eb876a7f0..a39c861c6 100644 --- a/libsolidity/formal/SMTLib2Interface.h +++ b/libsolidity/formal/SMTLib2Interface.h @@ -19,18 +19,17 @@ #include -#include #include - +#include #include +#include #include - +#include #include +#include #include #include -#include -#include namespace dev { @@ -42,35 +41,40 @@ namespace smt class SMTLib2Interface: public SolverInterface, public boost::noncopyable { public: - explicit SMTLib2Interface(ReadCallback::Callback const& _queryCallback); + explicit SMTLib2Interface(std::map const& _queryResponses); void reset() override; void push() override; void pop() override; - void declareFunction(std::string _name, Sort _domain, Sort _codomain) override; - void declareInteger(std::string _name) override; - void declareBool(std::string _name) override; + void declareVariable(std::string const&, Sort const&) override; + + void addAssertion(smt::Expression const& _expr) override; + std::pair> check(std::vector const& _expressionsToEvaluate) override; - void addAssertion(Expression const& _expr) override; - std::pair> check(std::vector const& _expressionsToEvaluate) override; + std::vector unhandledQueries() override { return m_unhandledQueries; } private: - std::string toSExpr(Expression const& _expr); + void declareFunction(std::string const&, Sort const&); + + std::string toSExpr(smt::Expression const& _expr); + std::string toSmtLibSort(Sort const& _sort); + std::string toSmtLibSort(std::vector const& _sort); void write(std::string _data); - std::string checkSatAndGetValuesCommand(std::vector const& _expressionsToEvaluate); + std::string checkSatAndGetValuesCommand(std::vector const& _expressionsToEvaluate); std::vector parseValues(std::string::const_iterator _start, std::string::const_iterator _end); /// Communicates with the solver via the callback. Throws SMTSolverError on error. std::string querySolver(std::string const& _input); - ReadCallback::Callback m_queryCallback; std::vector m_accumulatedOutput; - std::set m_constants; - std::set m_functions; + std::set m_variables; + + std::map const& m_queryResponses; + std::vector m_unhandledQueries; }; } diff --git a/libsolidity/formal/SMTPortfolio.cpp b/libsolidity/formal/SMTPortfolio.cpp index 8b9fe9ce1..73c194a01 100644 --- a/libsolidity/formal/SMTPortfolio.cpp +++ b/libsolidity/formal/SMTPortfolio.cpp @@ -23,68 +23,51 @@ #ifdef HAVE_CVC4 #include #endif -#if !defined (HAVE_Z3) && !defined (HAVE_CVC4) #include -#endif using namespace std; using namespace dev; using namespace dev::solidity; using namespace dev::solidity::smt; -SMTPortfolio::SMTPortfolio(ReadCallback::Callback const& _readCallback) +SMTPortfolio::SMTPortfolio(map const& _smtlib2Responses) { + m_solvers.emplace_back(make_unique(_smtlib2Responses)); #ifdef HAVE_Z3 - m_solvers.emplace_back(make_shared()); + m_solvers.emplace_back(make_unique()); #endif #ifdef HAVE_CVC4 - m_solvers.emplace_back(make_shared()); -#endif -#if !defined (HAVE_Z3) && !defined (HAVE_CVC4) - m_solvers.emplace_back(make_shared(_readCallback)), + m_solvers.emplace_back(make_unique()); #endif - (void)_readCallback; } void SMTPortfolio::reset() { - for (auto s : m_solvers) + for (auto const& s: m_solvers) s->reset(); } void SMTPortfolio::push() { - for (auto s : m_solvers) + for (auto const& s: m_solvers) s->push(); } void SMTPortfolio::pop() { - for (auto s : m_solvers) + for (auto const& s: m_solvers) s->pop(); } -void SMTPortfolio::declareFunction(string _name, Sort _domain, Sort _codomain) -{ - for (auto s : m_solvers) - s->declareFunction(_name, _domain, _codomain); -} - -void SMTPortfolio::declareInteger(string _name) +void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort) { - for (auto s : m_solvers) - s->declareInteger(_name); + for (auto const& s: m_solvers) + s->declareVariable(_name, _sort); } -void SMTPortfolio::declareBool(string _name) +void SMTPortfolio::addAssertion(smt::Expression const& _expr) { - for (auto s : m_solvers) - s->declareBool(_name); -} - -void SMTPortfolio::addAssertion(Expression const& _expr) -{ - for (auto s : m_solvers) + for (auto const& s: m_solvers) s->addAssertion(_expr); } @@ -118,11 +101,11 @@ void SMTPortfolio::addAssertion(Expression const& _expr) * * If all solvers return ERROR, the result is ERROR. */ -pair> SMTPortfolio::check(vector const& _expressionsToEvaluate) +pair> SMTPortfolio::check(vector const& _expressionsToEvaluate) { CheckResult lastResult = CheckResult::ERROR; vector finalValues; - for (auto s : m_solvers) + for (auto const& s: m_solvers) { CheckResult result; vector values; @@ -146,6 +129,15 @@ pair> SMTPortfolio::check(vector const& return make_pair(lastResult, finalValues); } +vector SMTPortfolio::unhandledQueries() +{ + // This code assumes that the constructor guarantees that + // SmtLib2Interface is in position 0. + solAssert(!m_solvers.empty(), ""); + solAssert(dynamic_cast(m_solvers.front().get()), ""); + return m_solvers.front()->unhandledQueries(); +} + bool SMTPortfolio::solverAnswered(CheckResult result) { return result == CheckResult::SATISFIABLE || result == CheckResult::UNSATISFIABLE; diff --git a/libsolidity/formal/SMTPortfolio.h b/libsolidity/formal/SMTPortfolio.h index 96c7ff57b..ded7985f9 100644 --- a/libsolidity/formal/SMTPortfolio.h +++ b/libsolidity/formal/SMTPortfolio.h @@ -19,11 +19,11 @@ #include - #include +#include #include - +#include #include namespace dev @@ -42,24 +42,27 @@ namespace smt class SMTPortfolio: public SolverInterface, public boost::noncopyable { public: - SMTPortfolio(ReadCallback::Callback const& _readCallback); + SMTPortfolio(std::map const& _smtlib2Responses); void reset() override; void push() override; void pop() override; - void declareFunction(std::string _name, Sort _domain, Sort _codomain) override; - void declareInteger(std::string _name) override; - void declareBool(std::string _name) override; + void declareVariable(std::string const&, Sort const&) override; - void addAssertion(Expression const& _expr) override; - std::pair> check(std::vector const& _expressionsToEvaluate) override; + void addAssertion(smt::Expression const& _expr) override; + std::pair> check(std::vector const& _expressionsToEvaluate) override; + + std::vector unhandledQueries() override; + unsigned solvers() override { return m_solvers.size(); } private: static bool solverAnswered(CheckResult result); - std::vector> m_solvers; + std::vector> m_solvers; + + std::vector m_assertions; }; } diff --git a/libsolidity/formal/SSAVariable.cpp b/libsolidity/formal/SSAVariable.cpp index 36e15508f..7c8ecc2b4 100644 --- a/libsolidity/formal/SSAVariable.cpp +++ b/libsolidity/formal/SSAVariable.cpp @@ -18,7 +18,7 @@ #include using namespace std; -using namespace dev::solidity; +using namespace dev::solidity::smt; SSAVariable::SSAVariable() { @@ -28,6 +28,5 @@ SSAVariable::SSAVariable() void SSAVariable::resetIndex() { m_currentIndex = 0; - m_nextFreeIndex.reset (new unsigned); - *m_nextFreeIndex = 1; + m_nextFreeIndex = make_unique(1); } diff --git a/libsolidity/formal/SSAVariable.h b/libsolidity/formal/SSAVariable.h index 46935472c..b4cd59133 100644 --- a/libsolidity/formal/SSAVariable.h +++ b/libsolidity/formal/SSAVariable.h @@ -23,6 +23,8 @@ namespace dev { namespace solidity { +namespace smt +{ /** * This class represents the SSA representation of a program variable. @@ -44,10 +46,9 @@ class SSAVariable private: unsigned m_currentIndex; - /// The next free index is a shared pointer because we want - /// the copy and the copied to share it. - std::shared_ptr m_nextFreeIndex; + std::unique_ptr m_nextFreeIndex; }; } } +} diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h index af1cc8e40..0004e1726 100644 --- a/libsolidity/formal/SolverInterface.h +++ b/libsolidity/formal/SolverInterface.h @@ -17,18 +17,17 @@ #pragma once -#include +#include #include - +#include #include #include #include - +#include #include #include #include -#include namespace dev { @@ -42,23 +41,108 @@ enum class CheckResult SATISFIABLE, UNSATISFIABLE, UNKNOWN, CONFLICTING, ERROR }; -enum class Sort +enum class Kind { Int, Bool, - IntIntFun, // Function of one Int returning a single Int - IntBoolFun // Function of one Int returning a single Bool + Function, + Array, + Sort +}; + +struct Sort +{ + Sort(Kind _kind): + kind(_kind) {} + virtual ~Sort() = default; + virtual bool operator==(Sort const& _other) const { return kind == _other.kind; } + + Kind const kind; +}; +using SortPointer = std::shared_ptr; + +struct FunctionSort: public Sort +{ + FunctionSort(std::vector _domain, SortPointer _codomain): + Sort(Kind::Function), domain(std::move(_domain)), codomain(std::move(_codomain)) {} + bool operator==(Sort const& _other) const override + { + if (!Sort::operator==(_other)) + return false; + auto _otherFunction = dynamic_cast(&_other); + solAssert(_otherFunction, ""); + if (domain.size() != _otherFunction->domain.size()) + return false; + if (!std::equal( + domain.begin(), + domain.end(), + _otherFunction->domain.begin(), + [&](SortPointer _a, SortPointer _b) { return *_a == *_b; } + )) + return false; + solAssert(codomain, ""); + solAssert(_otherFunction->codomain, ""); + return *codomain == *_otherFunction->codomain; + } + + std::vector domain; + SortPointer codomain; +}; + +struct ArraySort: public Sort +{ + /// _domain is the sort of the indices + /// _range is the sort of the values + ArraySort(SortPointer _domain, SortPointer _range): + Sort(Kind::Array), domain(std::move(_domain)), range(std::move(_range)) {} + bool operator==(Sort const& _other) const override + { + if (!Sort::operator==(_other)) + return false; + auto _otherArray = dynamic_cast(&_other); + solAssert(_otherArray, ""); + solAssert(_otherArray->domain, ""); + solAssert(_otherArray->range, ""); + solAssert(domain, ""); + solAssert(range, ""); + return *domain == *_otherArray->domain && *range == *_otherArray->range; + } + + SortPointer domain; + SortPointer range; +}; + +struct SortSort: public Sort +{ + SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {} + bool operator==(Sort const& _other) const override + { + if (!Sort::operator==(_other)) + return false; + auto _otherSort = dynamic_cast(&_other); + solAssert(_otherSort, ""); + solAssert(_otherSort->inner, ""); + solAssert(inner, ""); + return *inner == *_otherSort->inner; + } + + SortPointer inner; }; +// Forward declaration. +SortPointer smtSort(solidity::Type const& _type); + /// C++ representation of an SMTLIB2 expression. class Expression { friend class SolverInterface; public: - explicit Expression(bool _v): name(_v ? "true" : "false"), sort(Sort::Bool) {} - Expression(size_t _number): name(std::to_string(_number)), sort(Sort::Int) {} - Expression(u256 const& _number): name(_number.str()), sort(Sort::Int) {} - Expression(bigint const& _number): name(_number.str()), sort(Sort::Int) {} + explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {} + explicit Expression(solidity::TypePointer _type): Expression(_type->toString(), {}, std::make_shared(smtSort(*_type))) {} + Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {} + Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {} + Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {} + Expression(bigint const& _number): Expression(_number.str(), Kind::Int) {} Expression(Expression const&) = default; Expression(Expression&&) = default; @@ -72,6 +156,7 @@ class Expression {"not", 1}, {"and", 2}, {"or", 2}, + {"implies", 2}, {"=", 2}, {"<", 2}, {"<=", 2}, @@ -80,39 +165,97 @@ class Expression {"+", 2}, {"-", 2}, {"*", 2}, - {"/", 2} + {"/", 2}, + {"mod", 2}, + {"select", 2}, + {"store", 3}, + {"const_array", 2} }; return operatorsArity.count(name) && operatorsArity.at(name) == arguments.size(); } static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue) { - solAssert(_trueValue.sort == _falseValue.sort, ""); + solAssert(*_trueValue.sort == *_falseValue.sort, ""); + SortPointer sort = _trueValue.sort; return Expression("ite", std::vector{ std::move(_condition), std::move(_trueValue), std::move(_falseValue) - }, _trueValue.sort); + }, std::move(sort)); } 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. + static Expression select(Expression _array, Expression _index) + { + solAssert(_array.sort->kind == Kind::Array, ""); + std::shared_ptr arraySort = std::dynamic_pointer_cast(_array.sort); + solAssert(arraySort, ""); + solAssert(_index.sort, ""); + solAssert(*arraySort->domain == *_index.sort, ""); + return Expression( + "select", + std::vector{std::move(_array), std::move(_index)}, + arraySort->range + ); + } + + /// store is the SMT representation of an assignment to array index. + /// The function is pure and returns the modified array. + static Expression store(Expression _array, Expression _index, Expression _element) + { + solAssert(_array.sort->kind == Kind::Array, ""); + std::shared_ptr arraySort = std::dynamic_pointer_cast(_array.sort); + solAssert(arraySort, ""); + solAssert(_index.sort, ""); + solAssert(_element.sort, ""); + solAssert(*arraySort->domain == *_index.sort, ""); + solAssert(*arraySort->range == *_element.sort, ""); + return Expression( + "store", + std::vector{std::move(_array), std::move(_index), std::move(_element)}, + arraySort + ); + } + + static Expression const_array(Expression _sort, Expression _value) + { + solAssert(_sort.sort->kind == Kind::Sort, ""); + auto sortSort = std::dynamic_pointer_cast(_sort.sort); + auto arraySort = std::dynamic_pointer_cast(sortSort->inner); + solAssert(sortSort && arraySort, ""); + solAssert(_value.sort, ""); + solAssert(*arraySort->range == *_value.sort, ""); + return Expression( + "const_array", + std::vector{std::move(_sort), std::move(_value)}, + arraySort + ); } friend Expression operator!(Expression _a) { - return Expression("not", std::move(_a), Sort::Bool); + return Expression("not", std::move(_a), Kind::Bool); } friend Expression operator&&(Expression _a, Expression _b) { - return Expression("and", std::move(_a), std::move(_b), Sort::Bool); + return Expression("and", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator||(Expression _a, Expression _b) { - return Expression("or", std::move(_a), std::move(_b), Sort::Bool); + return Expression("or", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator==(Expression _a, Expression _b) { - return Expression("=", std::move(_a), std::move(_b), Sort::Bool); + return Expression("=", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator!=(Expression _a, Expression _b) { @@ -120,72 +263,68 @@ class Expression } friend Expression operator<(Expression _a, Expression _b) { - return Expression("<", std::move(_a), std::move(_b), Sort::Bool); + return Expression("<", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator<=(Expression _a, Expression _b) { - return Expression("<=", std::move(_a), std::move(_b), Sort::Bool); + return Expression("<=", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator>(Expression _a, Expression _b) { - return Expression(">", std::move(_a), std::move(_b), Sort::Bool); + return Expression(">", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator>=(Expression _a, Expression _b) { - return Expression(">=", std::move(_a), std::move(_b), Sort::Bool); + return Expression(">=", std::move(_a), std::move(_b), Kind::Bool); } friend Expression operator+(Expression _a, Expression _b) { - return Expression("+", std::move(_a), std::move(_b), Sort::Int); + return Expression("+", std::move(_a), std::move(_b), Kind::Int); } friend Expression operator-(Expression _a, Expression _b) { - return Expression("-", std::move(_a), std::move(_b), Sort::Int); + return Expression("-", std::move(_a), std::move(_b), Kind::Int); } friend Expression operator*(Expression _a, Expression _b) { - return Expression("*", std::move(_a), std::move(_b), Sort::Int); + return Expression("*", std::move(_a), std::move(_b), Kind::Int); } friend Expression operator/(Expression _a, Expression _b) { - return Expression("/", std::move(_a), std::move(_b), Sort::Int); + return Expression("/", std::move(_a), std::move(_b), Kind::Int); + } + friend Expression operator%(Expression _a, Expression _b) + { + return Expression("mod", std::move(_a), std::move(_b), Kind::Int); } - Expression operator()(Expression _a) const + Expression operator()(std::vector _arguments) const { solAssert( - arguments.empty(), + sort->kind == Kind::Function, "Attempted function application to non-function." ); - switch (sort) - { - case Sort::IntIntFun: - return Expression(name, _a, Sort::Int); - case Sort::IntBoolFun: - return Expression(name, _a, Sort::Bool); - default: - solAssert( - false, - "Attempted function application to invalid type." - ); - break; - } + auto fSort = dynamic_cast(sort.get()); + solAssert(fSort, ""); + return Expression(name, std::move(_arguments), fSort->codomain); } std::string name; std::vector arguments; - Sort sort; + SortPointer sort; private: - /// Manual constructor, should only be used by SolverInterface and this class itself. - Expression(std::string _name, std::vector _arguments, Sort _sort): - name(std::move(_name)), arguments(std::move(_arguments)), sort(_sort) {} - - explicit Expression(std::string _name, Sort _sort): - Expression(std::move(_name), std::vector{}, _sort) {} - Expression(std::string _name, Expression _arg, Sort _sort): - Expression(std::move(_name), std::vector{std::move(_arg)}, _sort) {} - Expression(std::string _name, Expression _arg1, Expression _arg2, Sort _sort): - Expression(std::move(_name), std::vector{std::move(_arg1), std::move(_arg2)}, _sort) {} + /// Manual constructors, should only be used by SolverInterface and this class itself. + Expression(std::string _name, std::vector _arguments, SortPointer _sort): + name(std::move(_name)), arguments(std::move(_arguments)), sort(std::move(_sort)) {} + Expression(std::string _name, std::vector _arguments, Kind _kind): + Expression(std::move(_name), std::move(_arguments), std::make_shared(_kind)) {} + + explicit Expression(std::string _name, Kind _kind): + Expression(std::move(_name), std::vector{}, _kind) {} + Expression(std::string _name, Expression _arg, Kind _kind): + Expression(std::move(_name), std::vector{std::move(_arg)}, _kind) {} + Expression(std::string _name, Expression _arg1, Expression _arg2, Kind _kind): + Expression(std::move(_name), std::vector{std::move(_arg1), std::move(_arg2)}, _kind) {} }; DEV_SIMPLE_EXCEPTION(SolverError); @@ -199,36 +338,13 @@ class SolverInterface virtual void push() = 0; virtual void pop() = 0; - virtual void declareFunction(std::string _name, Sort _domain, Sort _codomain) = 0; - Expression newFunction(std::string _name, Sort _domain, Sort _codomain) - { - declareFunction(_name, _domain, _codomain); - solAssert(_domain == Sort::Int, "Function sort not supported."); - // Subclasses should do something here - switch (_codomain) - { - case Sort::Int: - return Expression(std::move(_name), {}, Sort::IntIntFun); - case Sort::Bool: - return Expression(std::move(_name), {}, Sort::IntBoolFun); - default: - solAssert(false, "Function sort not supported."); - break; - } - } - virtual void declareInteger(std::string _name) = 0; - Expression newInteger(std::string _name) - { - // Subclasses should do something here - declareInteger(_name); - return Expression(std::move(_name), {}, Sort::Int); - } - virtual void declareBool(std::string _name) = 0; - Expression newBool(std::string _name) + virtual void declareVariable(std::string const& _name, Sort const& _sort) = 0; + Expression newVariable(std::string _name, SortPointer _sort) { // Subclasses should do something here - declareBool(_name); - return Expression(std::move(_name), {}, Sort::Bool); + solAssert(_sort, ""); + declareVariable(_name, *_sort); + return Expression(std::move(_name), {}, std::move(_sort)); } virtual void addAssertion(Expression const& _expr) = 0; @@ -238,6 +354,12 @@ class SolverInterface virtual std::pair> check(std::vector const& _expressionsToEvaluate) = 0; + /// @returns a list of queries that the system was not able to respond to. + virtual std::vector unhandledQueries() { return {}; } + + /// @returns how many SMT solvers this interface has. + virtual unsigned solvers() { return 1; } + protected: // SMT query timeout in milliseconds. static int const queryTimeout = 10000; diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 3eb1c1ce6..ab23b8712 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -17,111 +17,315 @@ #include +#include #include - #include using namespace std; -using namespace dev::solidity; -bool dev::solidity::isSupportedType(Type::Category _category) +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +SortPointer smtSort(solidity::Type const& _type) +{ + switch (smtKind(_type.category())) + { + case Kind::Int: + return make_shared(Kind::Int); + case Kind::Bool: + return make_shared(Kind::Bool); + case Kind::Function: + { + auto fType = dynamic_cast(&_type); + solAssert(fType, ""); + vector parameterSorts = smtSort(fType->parameterTypes()); + auto returnTypes = fType->returnParameterTypes(); + SortPointer returnSort; + // TODO change this when we support tuples. + if (returnTypes.size() == 0) + // We cannot declare functions without a return sort, so we use the smallest. + returnSort = make_shared(Kind::Bool); + else if (returnTypes.size() > 1) + // Abstract sort. + returnSort = make_shared(Kind::Int); + else + returnSort = smtSort(*returnTypes.front()); + return make_shared(parameterSorts, returnSort); + } + case Kind::Array: + { + if (isMapping(_type.category())) + { + auto mapType = dynamic_cast(&_type); + solAssert(mapType, ""); + return make_shared(smtSort(*mapType->keyType()), smtSort(*mapType->valueType())); + } + else if (isStringLiteral(_type.category())) + { + auto stringLitType = dynamic_cast(&_type); + solAssert(stringLitType, ""); + auto intSort = make_shared(Kind::Int); + return make_shared(intSort, intSort); + } + else + { + solAssert(isArray(_type.category()), ""); + auto arrayType = dynamic_cast(&_type); + solAssert(arrayType, ""); + return make_shared(make_shared(Kind::Int), smtSort(*arrayType->baseType())); + } + } + default: + // Abstract case. + return make_shared(Kind::Int); + } +} + +vector smtSort(vector const& _types) +{ + vector sorts; + for (auto const& type: _types) + sorts.push_back(smtSort(*type)); + return sorts; +} + +Kind smtKind(solidity::Type::Category _category) +{ + if (isNumber(_category)) + return Kind::Int; + else if (isBool(_category)) + return Kind::Bool; + else if (isFunction(_category)) + return Kind::Function; + else if (isMapping(_category) || isArray(_category)) + return Kind::Array; + // Abstract case. + return Kind::Int; +} + +bool isSupportedType(solidity::Type::Category _category) { return isNumber(_category) || isBool(_category) || + isMapping(_category) || + isArray(_category) || + isTuple(_category); +} + +bool isSupportedTypeDeclaration(solidity::Type::Category _category) +{ + return isSupportedType(_category) || isFunction(_category); } -pair> dev::solidity::newSymbolicVariable( - Type const& _type, +pair> newSymbolicVariable( + solidity::Type const& _type, std::string const& _uniqueName, - smt::SolverInterface& _solver + EncodingContext& _context ) { bool abstract = false; shared_ptr var; - TypePointer type = _type.shared_from_this(); - if (!isSupportedType(_type)) + solidity::TypePointer type = &_type; + if (!isSupportedTypeDeclaration(_type)) { abstract = true; - var = make_shared(make_shared(256), _uniqueName, _solver); + var = make_shared(solidity::TypeProvider::uint256(), type, _uniqueName, _context); } else if (isBool(_type.category())) - var = make_shared(type, _uniqueName, _solver); + var = make_shared(type, _uniqueName, _context); else if (isFunction(_type.category())) - var = make_shared(make_shared(256), _uniqueName, _solver); + var = make_shared(type, _uniqueName, _context); else if (isInteger(_type.category())) - var = make_shared(type, _uniqueName, _solver); + var = make_shared(type, type, _uniqueName, _context); else if (isFixedBytes(_type.category())) { - auto fixedBytesType = dynamic_cast(type.get()); + auto fixedBytesType = dynamic_cast(type); solAssert(fixedBytesType, ""); - var = make_shared(fixedBytesType->numBytes(), _uniqueName, _solver); + var = make_shared(type, fixedBytesType->numBytes(), _uniqueName, _context); } - else if (isAddress(_type.category())) - var = make_shared(_uniqueName, _solver); + else if (isAddress(_type.category()) || isContract(_type.category())) + var = make_shared(_uniqueName, _context); + else if (isEnum(_type.category())) + var = make_shared(type, _uniqueName, _context); else if (isRational(_type.category())) { - auto rational = dynamic_cast(&_type); + auto rational = dynamic_cast(&_type); solAssert(rational, ""); if (rational->isFractional()) - var = make_shared(make_shared(256), _uniqueName, _solver); + var = make_shared(solidity::TypeProvider::uint256(), type, _uniqueName, _context); else - var = make_shared(type, _uniqueName, _solver); + var = make_shared(type, type, _uniqueName, _context); + } + else if (isMapping(_type.category())) + var = make_shared(type, _uniqueName, _context); + else if (isArray(_type.category())) + var = make_shared(type, type, _uniqueName, _context); + else if (isTuple(_type.category())) + var = make_shared(type, _uniqueName, _context); + else if (isStringLiteral(_type.category())) + { + auto stringType = TypeProvider::stringMemory(); + var = make_shared(stringType, type, _uniqueName, _context); } else solAssert(false, ""); return make_pair(abstract, var); } -bool dev::solidity::isSupportedType(Type const& _type) +bool isSupportedType(solidity::Type const& _type) { return isSupportedType(_type.category()); } -bool dev::solidity::isInteger(Type::Category _category) +bool isSupportedTypeDeclaration(solidity::Type const& _type) +{ + return isSupportedTypeDeclaration(_type.category()); +} + +bool isInteger(solidity::Type::Category _category) +{ + return _category == solidity::Type::Category::Integer; +} + +bool isRational(solidity::Type::Category _category) { - return _category == Type::Category::Integer; + return _category == solidity::Type::Category::RationalNumber; } -bool dev::solidity::isRational(Type::Category _category) +bool isFixedBytes(solidity::Type::Category _category) { - return _category == Type::Category::RationalNumber; + return _category == solidity::Type::Category::FixedBytes; } -bool dev::solidity::isFixedBytes(Type::Category _category) +bool isAddress(solidity::Type::Category _category) { - return _category == Type::Category::FixedBytes; + return _category == solidity::Type::Category::Address; } -bool dev::solidity::isAddress(Type::Category _category) +bool isContract(solidity::Type::Category _category) { - return _category == Type::Category::Address; + return _category == solidity::Type::Category::Contract; } -bool dev::solidity::isNumber(Type::Category _category) +bool isEnum(solidity::Type::Category _category) +{ + return _category == solidity::Type::Category::Enum; +} + +bool isNumber(solidity::Type::Category _category) { return isInteger(_category) || isRational(_category) || isFixedBytes(_category) || - isAddress(_category); + isAddress(_category) || + isContract(_category) || + isEnum(_category); +} + +bool isBool(solidity::Type::Category _category) +{ + return _category == solidity::Type::Category::Bool; +} + +bool isFunction(solidity::Type::Category _category) +{ + return _category == solidity::Type::Category::Function; } -bool dev::solidity::isBool(Type::Category _category) +bool isMapping(solidity::Type::Category _category) { - return _category == Type::Category::Bool; + return _category == solidity::Type::Category::Mapping; } -bool dev::solidity::isFunction(Type::Category _category) +bool isArray(solidity::Type::Category _category) { - return _category == Type::Category::Function; + return _category == solidity::Type::Category::Array || + _category == solidity::Type::Category::StringLiteral; } -smt::Expression dev::solidity::minValue(IntegerType const& _type) +bool isTuple(solidity::Type::Category _category) { - return smt::Expression(_type.minValue()); + return _category == solidity::Type::Category::Tuple; } -smt::Expression dev::solidity::maxValue(IntegerType const& _type) +bool isStringLiteral(solidity::Type::Category _category) { - return smt::Expression(_type.maxValue()); + return _category == solidity::Type::Category::StringLiteral; +} + +Expression minValue(solidity::IntegerType const& _type) +{ + return Expression(_type.minValue()); +} + +Expression maxValue(solidity::IntegerType const& _type) +{ + return Expression(_type.maxValue()); +} + +void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context) +{ + setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _context); +} + +void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context) +{ + solAssert(_type, ""); + _context.addAssertion(_expr == zeroValue(_type)); +} + +Expression zeroValue(solidity::TypePointer const& _type) +{ + solAssert(_type, ""); + if (isSupportedType(_type->category())) + { + if (isNumber(_type->category())) + return 0; + if (isBool(_type->category())) + return Expression(false); + if (isArray(_type->category()) || isMapping(_type->category())) + { + if (auto arrayType = dynamic_cast(_type)) + return Expression::const_array(Expression(arrayType), zeroValue(arrayType->baseType())); + auto mappingType = dynamic_cast(_type); + solAssert(mappingType, ""); + return Expression::const_array(Expression(mappingType), zeroValue(mappingType->valueType())); + } + solAssert(false, ""); + } + // Unsupported types are abstracted as Int. + return 0; +} + +void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context) +{ + setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _context); +} + +void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context) +{ + solAssert(_type, ""); + if (isEnum(_type->category())) + { + auto enumType = dynamic_cast(_type); + solAssert(enumType, ""); + _context.addAssertion(_expr >= 0); + _context.addAssertion(_expr < enumType->numberOfMembers()); + } + else if (isInteger(_type->category())) + { + auto intType = dynamic_cast(_type); + solAssert(intType, ""); + _context.addAssertion(_expr >= minValue(*intType)); + _context.addAssertion(_expr <= maxValue(*intType)); + } +} + +} +} } diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index dcdd9ea42..30a341431 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -17,9 +17,8 @@ #pragma once -#include +#include #include - #include #include @@ -27,27 +26,50 @@ namespace dev { namespace solidity { +namespace smt +{ + +/// Returns the SMT sort that models the Solidity type _type. +SortPointer smtSort(solidity::Type const& _type); +std::vector smtSort(std::vector const& _types); +/// Returns the SMT kind that models the Solidity type type category _category. +Kind smtKind(solidity::Type::Category _category); -/// So far int, bool and address are supported. -/// Returns true if type is supported. -bool isSupportedType(Type::Category _category); -bool isSupportedType(Type const& _type); +/// Returns true if type is fully supported (declaration and operations). +bool isSupportedType(solidity::Type::Category _category); +bool isSupportedType(solidity::Type const& _type); +/// Returns true if type is partially supported (declaration). +bool isSupportedTypeDeclaration(solidity::Type::Category _category); +bool isSupportedTypeDeclaration(solidity::Type const& _type); -bool isInteger(Type::Category _category); -bool isRational(Type::Category _category); -bool isFixedBytes(Type::Category _category); -bool isAddress(Type::Category _category); -bool isNumber(Type::Category _category); -bool isBool(Type::Category _category); -bool isFunction(Type::Category _category); +bool isInteger(solidity::Type::Category _category); +bool isRational(solidity::Type::Category _category); +bool isFixedBytes(solidity::Type::Category _category); +bool isAddress(solidity::Type::Category _category); +bool isContract(solidity::Type::Category _category); +bool isEnum(solidity::Type::Category _category); +bool isNumber(solidity::Type::Category _category); +bool isBool(solidity::Type::Category _category); +bool isFunction(solidity::Type::Category _category); +bool isMapping(solidity::Type::Category _category); +bool isArray(solidity::Type::Category _category); +bool isTuple(solidity::Type::Category _category); +bool isStringLiteral(solidity::Type::Category _category); /// Returns a new symbolic variable, according to _type. /// Also returns whether the type is abstract or not, /// which is true for unsupported types. -std::pair> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver); +std::pair> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, EncodingContext& _context); -smt::Expression minValue(IntegerType const& _type); -smt::Expression maxValue(IntegerType const& _type); +Expression minValue(solidity::IntegerType const& _type); +Expression maxValue(solidity::IntegerType const& _type); +Expression zeroValue(solidity::TypePointer const& _type); +void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context); +void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context); +void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context); +void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context); + +} } } diff --git a/libsolidity/formal/SymbolicVariables.cpp b/libsolidity/formal/SymbolicVariables.cpp index 85818ba07..3190cc6c6 100644 --- a/libsolidity/formal/SymbolicVariables.cpp +++ b/libsolidity/formal/SymbolicVariables.cpp @@ -18,23 +18,61 @@ #include #include - #include +#include using namespace std; using namespace dev; -using namespace dev::solidity; +using namespace dev::solidity::smt; SymbolicVariable::SymbolicVariable( - TypePointer _type, - string const& _uniqueName, - smt::SolverInterface& _interface + solidity::TypePointer _type, + solidity::TypePointer _originalType, + string _uniqueName, + EncodingContext& _context ): - m_type(move(_type)), - m_uniqueName(_uniqueName), - m_interface(_interface), - m_ssa(make_shared()) + m_type(_type), + m_originalType(_originalType), + m_uniqueName(move(_uniqueName)), + m_context(_context), + m_ssa(make_unique()) +{ + solAssert(m_type, ""); + m_sort = smtSort(*m_type); + solAssert(m_sort, ""); +} + +SymbolicVariable::SymbolicVariable( + SortPointer _sort, + string _uniqueName, + EncodingContext& _context +): + m_sort(move(_sort)), + m_uniqueName(move(_uniqueName)), + m_context(_context), + m_ssa(make_unique()) +{ + solAssert(m_sort, ""); +} + +Expression SymbolicVariable::currentValue(solidity::TypePointer const&) const +{ + return valueAtIndex(m_ssa->index()); +} + +string SymbolicVariable::currentName() const +{ + return uniqueSymbol(m_ssa->index()); +} + +Expression SymbolicVariable::valueAtIndex(int _index) const +{ + return m_context.newVariable(uniqueSymbol(_index), m_sort); +} + +string SymbolicVariable::nameAtIndex(int _index) const { + return uniqueSymbol(_index); } string SymbolicVariable::uniqueSymbol(unsigned _index) const @@ -42,71 +80,154 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const return m_uniqueName + "_" + to_string(_index); } +Expression SymbolicVariable::resetIndex() +{ + m_ssa->resetIndex(); + return currentValue(); +} + +Expression SymbolicVariable::increaseIndex() +{ + ++(*m_ssa); + return currentValue(); +} + SymbolicBoolVariable::SymbolicBoolVariable( - TypePointer _type, - string const& _uniqueName, - smt::SolverInterface&_interface + solidity::TypePointer _type, + string _uniqueName, + EncodingContext& _context ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(_type, _type, move(_uniqueName), _context) { - solAssert(m_type->category() == Type::Category::Bool, ""); + solAssert(m_type->category() == solidity::Type::Category::Bool, ""); } -smt::Expression SymbolicBoolVariable::valueAtIndex(int _index) const +SymbolicIntVariable::SymbolicIntVariable( + solidity::TypePointer _type, + solidity::TypePointer _originalType, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(_type, _originalType, move(_uniqueName), _context) { - return m_interface.newBool(uniqueSymbol(_index)); + solAssert(isNumber(m_type->category()), ""); } -void SymbolicBoolVariable::setZeroValue() +SymbolicAddressVariable::SymbolicAddressVariable( + string _uniqueName, + EncodingContext& _context +): + SymbolicIntVariable(TypeProvider::uint(160), TypeProvider::uint(160), move(_uniqueName), _context) { - m_interface.addAssertion(currentValue() == smt::Expression(false)); } -void SymbolicBoolVariable::setUnknownValue() +SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( + solidity::TypePointer _originalType, + unsigned _numBytes, + string _uniqueName, + EncodingContext& _context +): + SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), _originalType, move(_uniqueName), _context) { } -SymbolicIntVariable::SymbolicIntVariable( - TypePointer _type, - string const& _uniqueName, - smt::SolverInterface& _interface +SymbolicFunctionVariable::SymbolicFunctionVariable( + solidity::TypePointer _type, + string _uniqueName, + EncodingContext& _context ): - SymbolicVariable(move(_type), _uniqueName, _interface) + SymbolicVariable(_type, _type, move(_uniqueName), _context), + m_declaration(m_context.newVariable(currentName(), m_sort)) { - solAssert(isNumber(m_type->category()), ""); + solAssert(m_type->category() == solidity::Type::Category::Function, ""); +} + +SymbolicFunctionVariable::SymbolicFunctionVariable( + SortPointer _sort, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(move(_sort), move(_uniqueName), _context), + m_declaration(m_context.newVariable(currentName(), m_sort)) +{ + solAssert(m_sort->kind == Kind::Function, ""); } -smt::Expression SymbolicIntVariable::valueAtIndex(int _index) const +void SymbolicFunctionVariable::resetDeclaration() { - return m_interface.newInteger(uniqueSymbol(_index)); + m_declaration = m_context.newVariable(currentName(), m_sort); } -void SymbolicIntVariable::setZeroValue() +Expression SymbolicFunctionVariable::increaseIndex() { - m_interface.addAssertion(currentValue() == 0); + ++(*m_ssa); + resetDeclaration(); + return currentValue(); } -void SymbolicIntVariable::setUnknownValue() +Expression SymbolicFunctionVariable::operator()(vector _arguments) const { - auto intType = dynamic_cast(m_type.get()); - solAssert(intType, ""); - m_interface.addAssertion(currentValue() >= minValue(*intType)); - m_interface.addAssertion(currentValue() <= maxValue(*intType)); + return m_declaration(_arguments); } -SymbolicAddressVariable::SymbolicAddressVariable( - string const& _uniqueName, - smt::SolverInterface& _interface +SymbolicMappingVariable::SymbolicMappingVariable( + solidity::TypePointer _type, + string _uniqueName, + EncodingContext& _context ): - SymbolicIntVariable(make_shared(160), _uniqueName, _interface) + SymbolicVariable(_type, _type, move(_uniqueName), _context) { + solAssert(isMapping(m_type->category()), ""); } -SymbolicFixedBytesVariable::SymbolicFixedBytesVariable( - unsigned _numBytes, - string const& _uniqueName, - smt::SolverInterface& _interface +SymbolicArrayVariable::SymbolicArrayVariable( + solidity::TypePointer _type, + solidity::TypePointer _originalType, + string _uniqueName, + EncodingContext& _context ): - SymbolicIntVariable(make_shared(_numBytes * 8), _uniqueName, _interface) + SymbolicVariable(_type, _originalType, move(_uniqueName), _context) +{ + solAssert(isArray(m_type->category()), ""); +} + +Expression SymbolicArrayVariable::currentValue(solidity::TypePointer const& _targetType) const +{ + if (_targetType) + // StringLiterals are encoded as SMT arrays in the generic case, + // but they can also be compared/assigned to fixed bytes, in which + // case they'd need to be encoded as numbers. + if (auto strType = dynamic_cast(m_originalType)) + if (_targetType->category() == solidity::Type::Category::FixedBytes) + return smt::Expression(u256(toHex(asBytes(strType->value()), HexPrefix::Add))); + + return SymbolicVariable::currentValue(_targetType); +} + +SymbolicEnumVariable::SymbolicEnumVariable( + solidity::TypePointer _type, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(_type, _type, move(_uniqueName), _context) +{ + solAssert(isEnum(m_type->category()), ""); +} + +SymbolicTupleVariable::SymbolicTupleVariable( + solidity::TypePointer _type, + string _uniqueName, + EncodingContext& _context +): + SymbolicVariable(_type, _type, move(_uniqueName), _context) +{ + solAssert(isTuple(m_type->category()), ""); +} + +void SymbolicTupleVariable::setComponents(vector> _components) { + solAssert(m_components.empty(), ""); + auto const& tupleType = dynamic_cast(m_type); + solAssert(_components.size() == tupleType->components().size(), ""); + m_components = move(_components); } diff --git a/libsolidity/formal/SymbolicVariables.h b/libsolidity/formal/SymbolicVariables.h index 4fd9b2451..c65a26326 100644 --- a/libsolidity/formal/SymbolicVariables.h +++ b/libsolidity/formal/SymbolicVariables.h @@ -17,19 +17,19 @@ #pragma once -#include - #include - +#include #include - #include namespace dev { namespace solidity { +namespace smt +{ +class EncodingContext; class Type; /** @@ -39,43 +39,49 @@ class SymbolicVariable { public: SymbolicVariable( - TypePointer _type, - std::string const& _uniqueName, - smt::SolverInterface& _interface + solidity::TypePointer _type, + solidity::TypePointer _originalType, + std::string _uniqueName, + EncodingContext& _context + ); + SymbolicVariable( + SortPointer _sort, + std::string _uniqueName, + EncodingContext& _context ); virtual ~SymbolicVariable() = default; - smt::Expression currentValue() const + virtual Expression currentValue(solidity::TypePointer const& _targetType = TypePointer{}) const; + std::string currentName() const; + virtual Expression valueAtIndex(int _index) const; + virtual std::string nameAtIndex(int _index) const; + virtual Expression resetIndex(); + virtual Expression increaseIndex(); + virtual Expression operator()(std::vector /*_arguments*/) const { - return valueAtIndex(m_ssa->index()); - } - - virtual smt::Expression valueAtIndex(int _index) const = 0; - - smt::Expression increaseIndex() - { - ++(*m_ssa); - return currentValue(); + solAssert(false, "Function application to non-function."); } unsigned index() const { return m_ssa->index(); } unsigned& index() { return m_ssa->index(); } - /// Sets the var to the default value of its type. - /// Inherited types must implement. - virtual void setZeroValue() = 0; - /// The unknown value is the full range of valid values. - /// It is sub-type dependent, but not mandatory. - virtual void setUnknownValue() {} + SortPointer const& sort() const { return m_sort; } + solidity::TypePointer const& type() const { return m_type; } + solidity::TypePointer const& originalType() const { return m_originalType; } protected: std::string uniqueSymbol(unsigned _index) const; - TypePointer m_type = nullptr; + /// SMT sort. + SortPointer m_sort; + /// Solidity type, used for size and range in number types. + solidity::TypePointer m_type; + /// Solidity original type, used for type conversion if necessary. + solidity::TypePointer m_originalType; std::string m_uniqueName; - smt::SolverInterface& m_interface; - std::shared_ptr m_ssa = nullptr; + EncodingContext& m_context; + std::unique_ptr m_ssa; }; /** @@ -85,18 +91,10 @@ class SymbolicBoolVariable: public SymbolicVariable { public: SymbolicBoolVariable( - TypePointer _type, - std::string const& _uniqueName, - smt::SolverInterface& _interface + solidity::TypePointer _type, + std::string _uniqueName, + EncodingContext& _context ); - - /// Sets the var to false. - void setZeroValue(); - /// Does nothing since the SMT solver already knows the valid values for Bool. - void setUnknownValue(); - -protected: - smt::Expression valueAtIndex(int _index) const; }; /** @@ -106,18 +104,11 @@ class SymbolicIntVariable: public SymbolicVariable { public: SymbolicIntVariable( - TypePointer _type, - std::string const& _uniqueName, - smt::SolverInterface& _interface + solidity::TypePointer _type, + solidity::TypePointer _originalType, + std::string _uniqueName, + EncodingContext& _context ); - - /// Sets the var to 0. - void setZeroValue(); - /// Sets the variable to the full valid value range. - void setUnknownValue(); - -protected: - smt::Expression valueAtIndex(int _index) const; }; /** @@ -127,8 +118,8 @@ class SymbolicAddressVariable: public SymbolicIntVariable { public: SymbolicAddressVariable( - std::string const& _uniqueName, - smt::SolverInterface& _interface + std::string _uniqueName, + EncodingContext& _context ); }; @@ -139,11 +130,106 @@ class SymbolicFixedBytesVariable: public SymbolicIntVariable { public: SymbolicFixedBytesVariable( + solidity::TypePointer _originalType, unsigned _numBytes, - std::string const& _uniqueName, - smt::SolverInterface& _interface + std::string _uniqueName, + EncodingContext& _context + ); +}; + +/** + * Specialization of SymbolicVariable for FunctionType + */ +class SymbolicFunctionVariable: public SymbolicVariable +{ +public: + SymbolicFunctionVariable( + solidity::TypePointer _type, + std::string _uniqueName, + EncodingContext& _context + ); + SymbolicFunctionVariable( + SortPointer _sort, + std::string _uniqueName, + EncodingContext& _context + ); + + Expression increaseIndex(); + Expression operator()(std::vector _arguments) const; + +private: + /// Creates a new function declaration. + void resetDeclaration(); + + /// Stores the current function declaration. + Expression m_declaration; +}; + +/** + * Specialization of SymbolicVariable for Mapping + */ +class SymbolicMappingVariable: public SymbolicVariable +{ +public: + SymbolicMappingVariable( + solidity::TypePointer _type, + std::string _uniqueName, + EncodingContext& _context + ); +}; + +/** + * Specialization of SymbolicVariable for Array + */ +class SymbolicArrayVariable: public SymbolicVariable +{ +public: + SymbolicArrayVariable( + solidity::TypePointer _type, + solidity::TypePointer _originalTtype, + std::string _uniqueName, + EncodingContext& _context + ); + + Expression currentValue(solidity::TypePointer const& _targetType = TypePointer{}) const override; +}; + +/** + * Specialization of SymbolicVariable for Enum + */ +class SymbolicEnumVariable: public SymbolicVariable +{ +public: + SymbolicEnumVariable( + solidity::TypePointer _type, + std::string _uniqueName, + EncodingContext& _context + ); +}; + +/** + * Specialization of SymbolicVariable for Tuple + */ +class SymbolicTupleVariable: public SymbolicVariable +{ +public: + SymbolicTupleVariable( + solidity::TypePointer _type, + std::string _uniqueName, + EncodingContext& _context ); + + std::vector> const& components() + { + return m_components; + } + + void setComponents(std::vector> _components); + +private: + std::vector> m_components; }; } } +} diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp index 9282a5606..585072d3b 100644 --- a/libsolidity/formal/VariableUsage.cpp +++ b/libsolidity/formal/VariableUsage.cpp @@ -17,63 +17,93 @@ #include -#include +#include +#include + +#include using namespace std; using namespace dev; using namespace dev::solidity; +using namespace dev::solidity::smt; + +set VariableUsage::touchedVariables(ASTNode const& _node, vector const& _outerCallstack) +{ + m_touchedVariables.clear(); + m_callStack.clear(); + m_callStack += _outerCallstack; + m_lastCall = m_callStack.back(); + _node.accept(*this); + return m_touchedVariables; +} + +void VariableUsage::endVisit(Identifier const& _identifier) +{ + if (_identifier.annotation().lValueRequested) + checkIdentifier(_identifier); +} -VariableUsage::VariableUsage(ASTNode const& _node) +void VariableUsage::endVisit(IndexAccess const& _indexAccess) { - auto nodeFun = [&](ASTNode const& n) -> bool + if (_indexAccess.annotation().lValueRequested) { - if (Identifier const* identifier = dynamic_cast(&n)) + /// identifier.annotation().lValueRequested == false, that's why we + /// need to check that before. + auto identifier = dynamic_cast(SMTEncoder::leftmostBase(_indexAccess)); + if (identifier) + checkIdentifier(*identifier); + } +} + +void VariableUsage::endVisit(FunctionCall const& _funCall) +{ + if (m_inlineFunctionCalls) + if (auto const& funDef = SMTEncoder::functionCallToDefinition(_funCall)) { - Declaration const* declaration = identifier->annotation().referencedDeclaration; - solAssert(declaration, ""); - if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) - if ( - identifier->annotation().lValueRequested && - varDecl->annotation().type->isValueType() - ) - m_touchedVariable[&n] = varDecl; + solAssert(funDef, ""); + if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end()) + funDef->accept(*this); } - return true; - }; - auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child) - { - if (m_touchedVariable.count(&_child) || m_children.count(&_child)) - m_children[&_parent].push_back(&_child); - }; +} - ASTReduce reducer(nodeFun, edgeFun); - _node.accept(reducer); +bool VariableUsage::visit(FunctionDefinition const& _function) +{ + m_callStack.push_back(&_function); + return true; } -vector VariableUsage::touchedVariables(ASTNode const& _node) const +void VariableUsage::endVisit(FunctionDefinition const&) { - if (!m_children.count(&_node) && !m_touchedVariable.count(&_node)) - return {}; + solAssert(!m_callStack.empty(), ""); + m_callStack.pop_back(); +} - set touched; - vector toVisit; - toVisit.push_back(&_node); +void VariableUsage::endVisit(ModifierInvocation const& _modifierInv) +{ + auto const& modifierDef = dynamic_cast(_modifierInv.name()->annotation().referencedDeclaration); + if (modifierDef) + modifierDef->accept(*this); +} + +void VariableUsage::endVisit(PlaceholderStatement const&) +{ + solAssert(!m_callStack.empty(), ""); + FunctionDefinition const* funDef = nullptr; + for (auto it = m_callStack.rbegin(); it != m_callStack.rend() && !funDef; ++it) + funDef = dynamic_cast(*it); + solAssert(funDef, ""); + if (funDef->isImplemented()) + funDef->body().accept(*this); +} - while (!toVisit.empty()) +void VariableUsage::checkIdentifier(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + solAssert(declaration, ""); + if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) { - ASTNode const* n = toVisit.back(); - toVisit.pop_back(); - if (m_children.count(n)) - { - solAssert(!m_touchedVariable.count(n), ""); - toVisit += m_children.at(n); - } - else - { - solAssert(m_touchedVariable.count(n), ""); - touched.insert(m_touchedVariable.at(n)); - } + solAssert(m_lastCall, ""); + if (!varDecl->isLocalVariable() || varDecl->functionOrModifierDefinition() == m_lastCall) + m_touchedVariables.insert(varDecl); } - - return {touched.begin(), touched.end()}; } diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h index dda13de25..7985655ab 100644 --- a/libsolidity/formal/VariableUsage.h +++ b/libsolidity/formal/VariableUsage.h @@ -17,34 +17,49 @@ #pragma once -#include -#include +#include + #include +#include namespace dev { namespace solidity { - -class ASTNode; -class VariableDeclaration; +namespace smt +{ /** - * This class collects information about which local variables of value type - * are modified in which parts of the AST. + * This class computes information about which variables are modified in a certain subtree. */ -class VariableUsage +class VariableUsage: private ASTConstVisitor { public: - explicit VariableUsage(ASTNode const& _node); + /// @param _outerCallstack the current callstack in the callers context. + std::set touchedVariables(ASTNode const& _node, std::vector const& _outerCallstack); - std::vector touchedVariables(ASTNode const& _node) const; + /// Sets whether to inline function calls. + void setFunctionInlining(bool _inlineFunction) { m_inlineFunctionCalls = _inlineFunction; } private: - // Variable touched by a specific AST node. - std::map m_touchedVariable; - std::map> m_children; + void endVisit(Identifier const& _node) override; + void endVisit(IndexAccess const& _node) override; + void endVisit(FunctionCall const& _node) override; + bool visit(FunctionDefinition const& _node) override; + void endVisit(FunctionDefinition const& _node) override; + void endVisit(ModifierInvocation const& _node) override; + void endVisit(PlaceholderStatement const& _node) override; + + /// Checks whether an identifier should be added to touchedVariables. + void checkIdentifier(Identifier const& _identifier); + + std::set m_touchedVariables; + std::vector m_callStack; + CallableDeclaration const* m_lastCall = nullptr; + + bool m_inlineFunctionCalls = false; }; } } +} diff --git a/libsolidity/formal/Z3CHCInterface.cpp b/libsolidity/formal/Z3CHCInterface.cpp new file mode 100644 index 000000000..100a1fb7d --- /dev/null +++ b/libsolidity/formal/Z3CHCInterface.cpp @@ -0,0 +1,111 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity::smt; + +Z3CHCInterface::Z3CHCInterface(): + m_z3Interface(make_shared()), + m_context(m_z3Interface->context()), + m_solver(*m_context) +{ + // This needs to be set globally. + z3::set_param("rewriter.pull_cheap_ite", true); + // This needs to be set in the context. + m_context->set("timeout", queryTimeout); + + // Spacer options. + // These needs to be set in the solver. + // https://github.com/Z3Prover/z3/blob/master/src/muz/base/fp_params.pyg + z3::params p(*m_context); + // These are useful for solving problems with arrays and loops. + // Use quantified lemma generalizer. + p.set("fp.spacer.q3.use_qgen", true); + p.set("fp.spacer.mbqi", false); + // Ground pobs by using values from a model. + p.set("fp.spacer.ground_pobs", false); + m_solver.set(p); +} + +void Z3CHCInterface::declareVariable(string const& _name, Sort const& _sort) +{ + m_z3Interface->declareVariable(_name, _sort); +} + +void Z3CHCInterface::registerRelation(Expression const& _expr) +{ + m_solver.register_relation(m_z3Interface->functions().at(_expr.name)); +} + +void Z3CHCInterface::addRule(Expression const& _expr, string const& _name) +{ + z3::expr rule = m_z3Interface->toZ3Expr(_expr); + if (m_z3Interface->constants().empty()) + m_solver.add_rule(rule, m_context->str_symbol(_name.c_str())); + else + { + z3::expr_vector variables(*m_context); + for (auto const& var: m_z3Interface->constants()) + variables.push_back(var.second); + z3::expr boundRule = z3::forall(variables, rule); + m_solver.add_rule(boundRule, m_context->str_symbol(_name.c_str())); + } +} + +pair> Z3CHCInterface::query(Expression const& _expr) +{ + CheckResult result; + vector values; + try + { + z3::expr z3Expr = m_z3Interface->toZ3Expr(_expr); + switch (m_solver.query(z3Expr)) + { + case z3::check_result::sat: + { + result = CheckResult::SATISFIABLE; + // TODO retrieve model. + break; + } + case z3::check_result::unsat: + { + result = CheckResult::UNSATISFIABLE; + // TODO retrieve invariants. + break; + } + case z3::check_result::unknown: + { + result = CheckResult::UNKNOWN; + break; + } + } + // TODO retrieve model / invariants + } + catch (z3::exception const&) + { + result = CheckResult::ERROR; + values.clear(); + } + + return make_pair(result, values); +} diff --git a/libsolidity/formal/Z3CHCInterface.h b/libsolidity/formal/Z3CHCInterface.h new file mode 100644 index 000000000..a107ad424 --- /dev/null +++ b/libsolidity/formal/Z3CHCInterface.h @@ -0,0 +1,64 @@ +/* + 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 . +*/ + +/** + * Z3 specific Horn solver interface. + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace solidity +{ +namespace smt +{ + +class Z3CHCInterface: public CHCSolverInterface +{ +public: + Z3CHCInterface(); + + /// Forwards variable declaration to Z3Interface. + void declareVariable(std::string const& _name, Sort const& _sort) override; + + void registerRelation(Expression const& _expr) override; + + void addRule(Expression const& _expr, std::string const& _name) override; + + std::pair> query(Expression const& _expr) override; + + std::shared_ptr z3Interface() { return m_z3Interface; } + +private: + // Used to handle variables. + std::shared_ptr m_z3Interface; + + z3::context* m_context; + // Horn solver. + z3::fixedpoint m_solver; + + // SMT query timeout in milliseconds. + static int const queryTimeout = 10000; +}; + +} +} +} diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 9a0ccf48b..e07bacdb7 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -17,8 +17,7 @@ #include -#include - +#include #include using namespace std; @@ -51,22 +50,22 @@ void Z3Interface::pop() m_solver.pop(); } -void Z3Interface::declareFunction(string _name, Sort _domain, Sort _codomain) -{ - if (!m_functions.count(_name)) - m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(_domain), z3Sort(_codomain))}); -} - -void Z3Interface::declareInteger(string _name) +void Z3Interface::declareVariable(string const& _name, Sort const& _sort) { - if (!m_constants.count(_name)) - m_constants.insert({_name, m_context.int_const(_name.c_str())}); + if (_sort.kind == Kind::Function) + declareFunction(_name, _sort); + else if (!m_constants.count(_name)) + m_constants.insert({_name, m_context.constant(_name.c_str(), z3Sort(_sort))}); } -void Z3Interface::declareBool(string _name) +void Z3Interface::declareFunction(string const& _name, Sort const& _sort) { - if (!m_constants.count(_name)) - m_constants.insert({_name, m_context.bool_const(_name.c_str())}); + solAssert(_sort.kind == smt::Kind::Function, ""); + if (!m_functions.count(_name)) + { + FunctionSort fSort = dynamic_cast(_sort); + m_functions.insert({_name, m_context.function(_name.c_str(), z3Sort(fSort.domain), z3Sort(*fSort.codomain))}); + } } void Z3Interface::addAssertion(Expression const& _expr) @@ -117,65 +116,106 @@ 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 if (_expr.sort->kind == Kind::Sort) + { + auto sortSort = dynamic_pointer_cast(_expr.sort); + solAssert(sortSort, ""); + return m_context.constant(n.c_str(), z3Sort(*sortSort->inner)); + } + else + try + { + 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]); + else if (n == "const_array") + { + shared_ptr sortSort = std::dynamic_pointer_cast(_expr.arguments[0].sort); + solAssert(sortSort, ""); + auto arraySort = dynamic_pointer_cast(sortSort->inner); + solAssert(arraySort && arraySort->domain, ""); + return z3::const_array(z3Sort(*arraySort->domain), arguments[1]); + } + + solAssert(false, ""); } - 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]; - // Cannot reach here. solAssert(false, ""); - return arguments[0]; } -z3::sort Z3Interface::z3Sort(Sort _sort) +z3::sort Z3Interface::z3Sort(Sort const& _sort) { - switch (_sort) + switch (_sort.kind) { - case Sort::Bool: + case Kind::Bool: return m_context.bool_sort(); - case Sort::Int: + case Kind::Int: return m_context.int_sort(); + case Kind::Array: + { + auto const& arraySort = dynamic_cast(_sort); + return m_context.array_sort(z3Sort(*arraySort.domain), z3Sort(*arraySort.range)); + } default: break; } @@ -183,3 +223,11 @@ z3::sort Z3Interface::z3Sort(Sort _sort) // Cannot be reached. return m_context.int_sort(); } + +z3::sort_vector Z3Interface::z3Sort(vector const& _sorts) +{ + z3::sort_vector z3Sorts(m_context); + for (auto const& _sort: _sorts) + z3Sorts.push_back(z3Sort(*_sort)); + return z3Sorts; +} diff --git a/libsolidity/formal/Z3Interface.h b/libsolidity/formal/Z3Interface.h index 84880ff32..171fdcaa5 100644 --- a/libsolidity/formal/Z3Interface.h +++ b/libsolidity/formal/Z3Interface.h @@ -18,9 +18,7 @@ #pragma once #include - #include - #include namespace dev @@ -40,21 +38,29 @@ class Z3Interface: public SolverInterface, public boost::noncopyable void push() override; void pop() override; - void declareFunction(std::string _name, Sort _domain, Sort _codomain) override; - void declareInteger(std::string _name) override; - void declareBool(std::string _name) override; + void declareVariable(std::string const& _name, Sort const& _sort) override; void addAssertion(Expression const& _expr) override; std::pair> check(std::vector const& _expressionsToEvaluate) override; -private: z3::expr toZ3Expr(Expression const& _expr); - z3::sort z3Sort(smt::Sort _sort); - z3::context m_context; - z3::solver m_solver; + std::map constants() const { return m_constants; } + std::map functions() const { return m_functions; } + + z3::context* context() { return &m_context; } + +private: + void declareFunction(std::string const& _name, Sort const& _sort); + + z3::sort z3Sort(smt::Sort const& _sort); + z3::sort_vector z3Sort(std::vector const& _sorts); + std::map m_constants; std::map m_functions; + + z3::context m_context; + z3::solver m_solver; }; } diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h deleted file mode 100644 index a8673efa5..000000000 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - 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 . -*/ -/** - * Analysis part of inline assembly. - */ - -#pragma once - -#include -#include - -#include - -#include - -#include - -#include -#include - -#include -#include - -namespace dev -{ -namespace solidity -{ -class ErrorReporter; -namespace assembly -{ - -struct AsmAnalysisInfo; - -/** - * Performs the full analysis stage, calls the ScopeFiller internally, then resolves - * references and performs other checks. - * If all these checks pass, code generation should not throw errors. - */ -class AsmAnalyzer: public boost::static_visitor -{ -public: - explicit AsmAnalyzer( - AsmAnalysisInfo& _analysisInfo, - ErrorReporter& _errorReporter, - EVMVersion _evmVersion, - boost::optional _errorTypeForLoose, - AsmFlavour _flavour = AsmFlavour::Loose, - yul::ExternalIdentifierAccess::Resolver const& _resolver = yul::ExternalIdentifierAccess::Resolver() - ): - m_resolver(_resolver), - m_info(_analysisInfo), - m_errorReporter(_errorReporter), - m_evmVersion(_evmVersion), - m_flavour(_flavour), - m_errorTypeForLoose(_errorTypeForLoose) - {} - - bool analyze(assembly::Block const& _block); - - bool operator()(assembly::Instruction const&); - bool operator()(assembly::Literal const& _literal); - bool operator()(assembly::Identifier const&); - bool operator()(assembly::FunctionalInstruction const& _functionalInstruction); - bool operator()(assembly::Label const& _label); - bool operator()(assembly::ExpressionStatement const&); - bool operator()(assembly::StackAssignment const&); - bool operator()(assembly::Assignment const& _assignment); - bool operator()(assembly::VariableDeclaration const& _variableDeclaration); - bool operator()(assembly::FunctionDefinition const& _functionDefinition); - bool operator()(assembly::FunctionCall const& _functionCall); - bool operator()(assembly::If const& _if); - bool operator()(assembly::Switch const& _switch); - bool operator()(assembly::ForLoop const& _forLoop); - bool operator()(assembly::Block const& _block); - -private: - /// Visits the statement and expects it to deposit one item onto the stack. - bool expectExpression(Expression const& _expr); - bool expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location); - - /// Verifies that a variable to be assigned to exists and has the same size - /// as the value, @a _valueSize, unless that is equal to -1. - bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1)); - - Scope& scope(assembly::Block const* _block); - void expectValidType(std::string const& type, SourceLocation const& _location); - void warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location); - - /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler - /// exception (if the flavour is not Loose), reports an error/warning - /// (if m_errorTypeForLoose is set) or does nothing. - void checkLooseFeature(SourceLocation const& _location, std::string const& _description); - - int m_stackHeight = 0; - yul::ExternalIdentifierAccess::Resolver m_resolver; - Scope* m_currentScope = nullptr; - /// Variables that are active at the current point in assembly (as opposed to - /// "part of the scope but not yet declared") - std::set m_activeVariables; - AsmAnalysisInfo& m_info; - ErrorReporter& m_errorReporter; - EVMVersion m_evmVersion; - AsmFlavour m_flavour = AsmFlavour::Loose; - boost::optional m_errorTypeForLoose; -}; - -} -} -} diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp deleted file mode 100644 index 3a62b232d..000000000 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - 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 Christian - * @date 2016 - * Code-generating part of inline assembly. - */ - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::solidity; -using namespace dev::solidity::assembly; - -class EthAssemblyAdapter: public yul::AbstractAssembly -{ -public: - explicit EthAssemblyAdapter(eth::Assembly& _assembly): - m_assembly(_assembly) - { - } - virtual void setSourceLocation(SourceLocation const& _location) override - { - m_assembly.setSourceLocation(_location); - } - virtual int stackHeight() const override { return m_assembly.deposit(); } - virtual void appendInstruction(solidity::Instruction _instruction) override - { - m_assembly.append(_instruction); - } - virtual void appendConstant(u256 const& _constant) override - { - m_assembly.append(_constant); - } - /// Append a label. - virtual void appendLabel(LabelID _labelId) override - { - m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId)); - } - /// Append a label reference. - virtual void appendLabelReference(LabelID _labelId) override - { - m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId)); - } - virtual size_t newLabelId() override - { - return assemblyTagToIdentifier(m_assembly.newTag()); - } - virtual size_t namedLabel(std::string const& _name) override - { - return assemblyTagToIdentifier(m_assembly.namedTag(_name)); - } - virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override - { - m_assembly.appendLibraryAddress(_linkerSymbol); - } - virtual void appendJump(int _stackDiffAfter) override - { - appendInstruction(solidity::Instruction::JUMP); - m_assembly.adjustDeposit(_stackDiffAfter); - } - virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override - { - appendLabelReference(_labelId); - appendJump(_stackDiffAfter); - } - virtual void appendJumpToIf(LabelID _labelId) override - { - appendLabelReference(_labelId); - appendInstruction(solidity::Instruction::JUMPI); - } - virtual void appendBeginsub(LabelID, int) override - { - // TODO we could emulate that, though - solAssert(false, "BEGINSUB not implemented for EVM 1.0"); - } - /// Call a subroutine. - virtual void appendJumpsub(LabelID, int, int) override - { - // TODO we could emulate that, though - solAssert(false, "JUMPSUB not implemented for EVM 1.0"); - } - - /// Return from a subroutine. - virtual void appendReturnsub(int, int) override - { - // TODO we could emulate that, though - solAssert(false, "RETURNSUB not implemented for EVM 1.0"); - } - - virtual void appendAssemblySize() override - { - m_assembly.appendProgramSize(); - } - -private: - static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag) - { - u256 id = _tag.data(); - solAssert(id <= std::numeric_limits::max(), "Tag id too large."); - return LabelID(id); - } - - eth::Assembly& m_assembly; -}; - -void assembly::CodeGenerator::assemble( - Block const& _parsedData, - AsmAnalysisInfo& _analysisInfo, - eth::Assembly& _assembly, - yul::ExternalIdentifierAccess const& _identifierAccess, - bool _useNamedLabelsForFunctions -) -{ - EthAssemblyAdapter assemblyAdapter(_assembly); - yul::CodeTransform( - assemblyAdapter, - _analysisInfo, - false, - false, - _identifierAccess, - _useNamedLabelsForFunctions - )(_parsedData); -} diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h deleted file mode 100644 index a8d5e3279..000000000 --- a/libsolidity/inlineasm/AsmData.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - 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 Christian - * @date 2016 - * Parsed inline assembly to be used by the AST - */ - -#pragma once - -#include - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace dev -{ -namespace solidity -{ -namespace assembly -{ - -using YulString = dev::yul::YulString; -using Type = YulString; - -struct TypedName { SourceLocation location; YulString name; Type type; }; -using TypedNameList = std::vector; - -/// Direct EVM instruction (except PUSHi and JUMPDEST) -struct Instruction { SourceLocation location; solidity::Instruction instruction; }; -/// Literal number or string (up to 32 bytes) -enum class LiteralKind { Number, Boolean, String }; -struct Literal { SourceLocation location; LiteralKind kind; YulString value; Type type; }; -/// External / internal identifier or label reference -struct Identifier { SourceLocation location; YulString name; }; -/// Jump label ("name:") -struct Label { SourceLocation location; YulString name; }; -/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots) -struct StackAssignment { SourceLocation location; Identifier variableName; }; -/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand -/// side and requires x to occupy exactly one stack slot. -/// -/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy -/// a single stack slot and expects a single expression on the right hand returning -/// the same amount of items as the number of variables. -struct Assignment { SourceLocation location; std::vector variableNames; std::shared_ptr value; }; -/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" -struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector arguments; }; -struct FunctionCall { SourceLocation location; Identifier functionName; std::vector arguments; }; -/// Statement that contains only a single expression -struct ExpressionStatement { SourceLocation location; Expression expression; }; -/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted -struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr value; }; -/// Block that creates a scope (frees declared stack variables) -struct Block { SourceLocation location; std::vector statements; }; -/// Function definition ("function f(a, b) -> (d, e) { ... }") -struct FunctionDefinition { SourceLocation location; YulString name; TypedNameList parameters; TypedNameList returnVariables; Block body; }; -/// Conditional execution without "else" part. -struct If { SourceLocation location; std::shared_ptr condition; Block body; }; -/// Switch case or default case -struct Case { SourceLocation location; std::shared_ptr value; Block body; }; -/// Switch statement -struct Switch { SourceLocation location; std::shared_ptr expression; std::vector cases; }; -struct ForLoop { SourceLocation location; Block pre; std::shared_ptr condition; Block post; Block body; }; - -struct LocationExtractor: boost::static_visitor -{ - template SourceLocation operator()(T const& _node) const - { - return _node.location; - } -}; - -/// Extracts the source location from an inline assembly node. -template inline SourceLocation locationOf(T const& _node) -{ - return boost::apply_visitor(LocationExtractor(), _node); -} - -} -} -} diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h deleted file mode 100644 index 720489750..000000000 --- a/libsolidity/inlineasm/AsmPrinter.h +++ /dev/null @@ -1,68 +0,0 @@ -/* - 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 Christian - * @date 2017 - * Converts a parsed assembly into its textual form. - */ - -#pragma once - -#include - -#include - -#include - -namespace dev -{ -namespace solidity -{ -namespace assembly -{ - -class AsmPrinter: public boost::static_visitor -{ -public: - explicit AsmPrinter(bool _yul = false): m_yul(_yul) {} - - std::string operator()(assembly::Instruction const& _instruction); - std::string operator()(assembly::Literal const& _literal); - std::string operator()(assembly::Identifier const& _identifier); - std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction); - std::string operator()(assembly::ExpressionStatement const& _expr); - std::string operator()(assembly::Label const& _label); - std::string operator()(assembly::StackAssignment const& _assignment); - std::string operator()(assembly::Assignment const& _assignment); - std::string operator()(assembly::VariableDeclaration const& _variableDeclaration); - std::string operator()(assembly::FunctionDefinition const& _functionDefinition); - std::string operator()(assembly::FunctionCall const& _functionCall); - std::string operator()(assembly::If const& _if); - std::string operator()(assembly::Switch const& _switch); - std::string operator()(assembly::ForLoop const& _forLoop); - std::string operator()(assembly::Block const& _block); - -private: - std::string formatTypedName(TypedName _variable) const; - std::string appendTypeName(yul::YulString _type) const; - - bool m_yul = false; -}; - -} -} -} diff --git a/libsolidity/interface/ABI.cpp b/libsolidity/interface/ABI.cpp index aefb34afa..a21053d9f 100644 --- a/libsolidity/interface/ABI.cpp +++ b/libsolidity/interface/ABI.cpp @@ -1,37 +1,59 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** - * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + * Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html) */ #include + #include using namespace std; using namespace dev; using namespace dev::solidity; +namespace +{ +bool anyDataStoredInStorage(TypePointers const& _pointers) +{ + for (TypePointer const& pointer: _pointers) + if (pointer->dataStoredIn(DataLocation::Storage)) + return true; + + return false; +} +} + Json::Value ABI::generate(ContractDefinition const& _contractDef) { - Json::Value abi(Json::arrayValue); + auto compare = [](Json::Value const& _a, Json::Value const& _b) -> bool { + return make_tuple(_a["type"], _a["name"]) < make_tuple(_b["type"], _b["name"]); + }; + multiset abi(compare); for (auto it: _contractDef.interfaceFunctions()) { - auto externalFunctionType = it.second->interfaceFunctionType(); + if (_contractDef.isLibrary() && ( + it.second->stateMutability() > StateMutability::View || + anyDataStoredInStorage(it.second->parameterTypes() + it.second->returnParameterTypes()) + )) + continue; + + FunctionType const* externalFunctionType = it.second->interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "function"; @@ -43,18 +65,21 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), externalFunctionType->parameterTypes(), + it.second->parameterTypes(), _contractDef.isLibrary() ); method["outputs"] = formatTypeList( externalFunctionType->returnParameterNames(), externalFunctionType->returnParameterTypes(), + it.second->returnParameterTypes(), _contractDef.isLibrary() ); - abi.append(method); + abi.emplace(std::move(method)); } if (_contractDef.constructor()) { - auto externalFunctionType = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); + FunctionType constrType(*_contractDef.constructor(), false); + FunctionType const* externalFunctionType = constrType.interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "constructor"; @@ -63,19 +88,20 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) method["inputs"] = formatTypeList( externalFunctionType->parameterNames(), externalFunctionType->parameterTypes(), + constrType.parameterTypes(), _contractDef.isLibrary() ); - abi.append(method); + abi.emplace(std::move(method)); } if (_contractDef.fallbackFunction()) { - auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); + FunctionType const* externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "fallback"; method["payable"] = externalFunctionType->isPayable(); method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability()); - abi.append(method); + abi.emplace(std::move(method)); } for (auto const& it: _contractDef.interfaceEvents()) { @@ -86,47 +112,58 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef) Json::Value params(Json::arrayValue); for (auto const& p: it->parameters()) { - auto type = p->annotation().type->interfaceType(false); + Type const* type = p->annotation().type->interfaceType(false); solAssert(type, ""); Json::Value input; - auto param = formatType(p->name(), *type, false); + auto param = formatType(p->name(), *type, *p->annotation().type, false); param["indexed"] = p->isIndexed(); - params.append(param); + params.append(std::move(param)); } - event["inputs"] = params; - abi.append(event); + event["inputs"] = std::move(params); + abi.emplace(std::move(event)); } - return abi; + Json::Value abiJson{Json::arrayValue}; + for (auto& f: abi) + abiJson.append(std::move(f)); + return abiJson; } Json::Value ABI::formatTypeList( vector const& _names, - vector const& _types, + vector const& _encodingTypes, + vector const& _solidityTypes, bool _forLibrary ) { Json::Value params(Json::arrayValue); - solAssert(_names.size() == _types.size(), "Names and types vector size does not match"); + solAssert(_names.size() == _encodingTypes.size(), "Names and types vector size does not match"); + solAssert(_names.size() == _solidityTypes.size(), ""); for (unsigned i = 0; i < _names.size(); ++i) { - solAssert(_types[i], ""); - params.append(formatType(_names[i], *_types[i], _forLibrary)); + solAssert(_encodingTypes[i], ""); + params.append(formatType(_names[i], *_encodingTypes[i], *_solidityTypes[i], _forLibrary)); } return params; } -Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLibrary) +Json::Value ABI::formatType( + string const& _name, + Type const& _encodingType, + Type const& _solidityType, + bool _forLibrary +) { Json::Value ret; ret["name"] = _name; - string suffix = (_forLibrary && _type.dataStoredIn(DataLocation::Storage)) ? " storage" : ""; - if (_type.isValueType() || (_forLibrary && _type.dataStoredIn(DataLocation::Storage))) - ret["type"] = _type.canonicalName() + suffix; - else if (ArrayType const* arrayType = dynamic_cast(&_type)) + ret["internalType"] = _solidityType.toString(true); + string suffix = (_forLibrary && _encodingType.dataStoredIn(DataLocation::Storage)) ? " storage" : ""; + if (_encodingType.isValueType() || (_forLibrary && _encodingType.dataStoredIn(DataLocation::Storage))) + ret["type"] = _encodingType.canonicalName() + suffix; + else if (ArrayType const* arrayType = dynamic_cast(&_encodingType)) { if (arrayType->isByteArray()) - ret["type"] = _type.canonicalName() + suffix; + ret["type"] = _encodingType.canonicalName() + suffix; else { string suffix; @@ -135,7 +172,12 @@ Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLib else suffix = string("[") + arrayType->length().str() + "]"; solAssert(arrayType->baseType(), ""); - Json::Value subtype = formatType("", *arrayType->baseType(), _forLibrary); + Json::Value subtype = formatType( + "", + *arrayType->baseType(), + *dynamic_cast(_solidityType).baseType(), + _forLibrary + ); if (subtype.isMember("components")) { ret["type"] = subtype["type"].asString() + suffix; @@ -145,16 +187,16 @@ Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLib ret["type"] = subtype["type"].asString() + suffix; } } - else if (StructType const* structType = dynamic_cast(&_type)) + else if (StructType const* structType = dynamic_cast(&_encodingType)) { ret["type"] = "tuple"; ret["components"] = Json::arrayValue; for (auto const& member: structType->members(nullptr)) { solAssert(member.type, ""); - auto t = member.type->interfaceType(_forLibrary); + Type const* t = member.type->interfaceType(_forLibrary); solAssert(t, ""); - ret["components"].append(formatType(member.name, *t, _forLibrary)); + ret["components"].append(formatType(member.name, *t, *member.type, _forLibrary)); } } else diff --git a/libsolidity/interface/ABI.h b/libsolidity/interface/ABI.h index db70729db..9e1ba294e 100644 --- a/libsolidity/interface/ABI.h +++ b/libsolidity/interface/ABI.h @@ -15,14 +15,14 @@ along with solidity. If not, see . */ /** - * Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) + * Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html) */ #pragma once -#include -#include #include +#include +#include namespace dev { @@ -32,7 +32,7 @@ namespace solidity // Forward declarations class ContractDefinition; class Type; -using TypePointer = std::shared_ptr; +using TypePointer = Type const*; class ABI { @@ -45,15 +45,25 @@ class ABI /// @returns a json value suitable for a list of types in function input or output /// parameters or other places. If @a _forLibrary is true, complex types are referenced /// by name, otherwise they are anonymously expanded. + /// @a _solidityTypes is the list of original Solidity types where @a _encodingTypes is the list of + /// ABI types used for the actual encoding. static Json::Value formatTypeList( std::vector const& _names, - std::vector const& _types, + std::vector const& _encodingTypes, + std::vector const& _solidityTypes, bool _forLibrary ); - /// @returns a Json object with "name", "type" and potentially "components" keys, according - /// to the ABI specification. + /// @returns a Json object with "name", "type", "internalType" and potentially + /// "components" keys, according to the ABI specification. /// If it is possible to express the type as a single string, it is allowed to return a single string. - static Json::Value formatType(std::string const& _name, Type const& _type, bool _forLibrary); + /// @a _solidityType is the original Solidity type and @a _encodingTypes is the + /// ABI type used for the actual encoding. + static Json::Value formatType( + std::string const& _name, + Type const& _encodingType, + Type const& _solidityType, + bool _forLibrary + ); }; } diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp deleted file mode 100644 index 26496de79..000000000 --- a/libsolidity/interface/AssemblyStack.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - 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 . -*/ -/** - * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and - * eWasm as output. - */ - - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -namespace -{ -assembly::AsmFlavour languageToAsmFlavour(AssemblyStack::Language _language) -{ - switch (_language) - { - case AssemblyStack::Language::Assembly: - return assembly::AsmFlavour::Loose; - case AssemblyStack::Language::StrictAssembly: - return assembly::AsmFlavour::Strict; - case AssemblyStack::Language::Yul: - return assembly::AsmFlavour::Yul; - } - solAssert(false, ""); - return assembly::AsmFlavour::Yul; -} - -} - - -Scanner const& AssemblyStack::scanner() const -{ - solAssert(m_scanner, ""); - return *m_scanner; -} - -bool AssemblyStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source) -{ - m_errors.clear(); - m_analysisSuccessful = false; - m_scanner = make_shared(CharStream(_source), _sourceName); - m_parserResult = assembly::Parser(m_errorReporter, languageToAsmFlavour(m_language)).parse(m_scanner, false); - if (!m_errorReporter.errors().empty()) - return false; - solAssert(m_parserResult, ""); - - return analyzeParsed(); -} - -bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scanner) -{ - m_errors.clear(); - m_analysisSuccessful = false; - if (_scanner) - m_scanner = make_shared(*_scanner); - m_parserResult = make_shared(_block); - - return analyzeParsed(); -} - -bool AssemblyStack::analyzeParsed() -{ - m_analysisInfo = make_shared(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_evmVersion, boost::none, languageToAsmFlavour(m_language)); - m_analysisSuccessful = analyzer.analyze(*m_parserResult); - return m_analysisSuccessful; -} - -MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const -{ - solAssert(m_analysisSuccessful, ""); - solAssert(m_parserResult, ""); - solAssert(m_analysisInfo, ""); - - switch (_machine) - { - case Machine::EVM: - { - MachineAssemblyObject object; - eth::Assembly assembly; - assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly); - object.bytecode = make_shared(assembly.assemble()); - object.assembly = assembly.assemblyString(); - return object; - } - case Machine::EVM15: - { - MachineAssemblyObject object; - yul::EVMAssembly assembly(true); - yul::CodeTransform(assembly, *m_analysisInfo, m_language == Language::Yul, true)(*m_parserResult); - object.bytecode = make_shared(assembly.finalize()); - /// TODO: fill out text representation - return object; - } - case Machine::eWasm: - solUnimplemented("eWasm backend is not yet implemented."); - } - // unreachable - return MachineAssemblyObject(); -} - -string AssemblyStack::print() const -{ - solAssert(m_parserResult, ""); - return assembly::AsmPrinter(m_language == Language::Yul)(*m_parserResult); -} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index f856d0259..1bfb5bec4 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -24,33 +24,45 @@ #include -#include -#include -#include -#include -#include #include #include +#include +#include #include #include -#include -#include -#include #include +#include #include +#include #include -#include + +#include +#include #include -#include +#include #include #include #include +#include +#include -#include +#include #include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include #include +#include #include #include @@ -62,8 +74,30 @@ using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; +static int g_compilerStackCounts = 0; + +CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile): + m_readFile{_readFile}, + m_generateIR{false}, + m_generateEWasm{false}, + m_errorList{}, + m_errorReporter{m_errorList} +{ + // Because TypeProvider is currently a singleton API, we must ensure that + // no more than one entity is actually using it at a time. + solAssert(g_compilerStackCounts == 0, "You shall not have another CompilerStack aside me."); + ++g_compilerStackCounts; +} + +CompilerStack::~CompilerStack() +{ + --g_compilerStackCounts; + TypeProvider::reset(); +} + boost::optional CompilerStack::parseRemapping(string const& _remapping) { auto eq = find(_remapping.begin(), _remapping.end(), '='); @@ -86,56 +120,95 @@ boost::optional CompilerStack::parseRemapping(string c void CompilerStack::setRemappings(vector const& _remappings) { + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set remappings before parsing.")); for (auto const& remapping: _remappings) solAssert(!remapping.prefix.empty(), ""); m_remappings = _remappings; } -void CompilerStack::setEVMVersion(EVMVersion _version) +void CompilerStack::setEVMVersion(langutil::EVMVersion _version) { - solAssert(m_stackState < State::ParsingSuccessful, "Set EVM version after parsing."); + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set EVM version before parsing.")); m_evmVersion = _version; } -void CompilerStack::reset(bool _keepSources) +void CompilerStack::setLibraries(std::map const& _libraries) { - if (_keepSources) - { - m_stackState = SourcesSet; - for (auto sourcePair: m_sources) - sourcePair.second.reset(); - } - else + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set libraries before parsing.")); + m_libraries = _libraries; +} + +void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs) +{ + OptimiserSettings settings = _optimize ? OptimiserSettings::standard() : OptimiserSettings::minimal(); + settings.expectedExecutionsPerDeployment = _runs; + setOptimiserSettings(std::move(settings)); +} + +void CompilerStack::setOptimiserSettings(OptimiserSettings _settings) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing.")); + m_optimiserSettings = std::move(_settings); +} + +void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set use literal sources before parsing.")); + m_metadataLiteralSources = _metadataLiteralSources; +} + +void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response) +{ + if (m_stackState >= ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must add SMTLib2 responses before parsing.")); + m_smtlib2Responses[_hash] = _response; +} + +void CompilerStack::reset(bool _keepSettings) +{ + m_stackState = Empty; + m_hasError = false; + m_sources.clear(); + m_smtlib2Responses.clear(); + m_unhandledSMTLib2Queries.clear(); + if (!_keepSettings) { - m_stackState = Empty; - m_sources.clear(); + m_remappings.clear(); + m_libraries.clear(); + m_evmVersion = langutil::EVMVersion(); + m_generateIR = false; + m_generateEWasm = false; + m_optimiserSettings = OptimiserSettings::minimal(); + m_metadataLiteralSources = false; } - m_libraries.clear(); - m_evmVersion = EVMVersion(); - m_optimize = false; - m_optimizeRuns = 200; m_globalContext.reset(); m_scopes.clear(); m_sourceOrder.clear(); m_contracts.clear(); m_errorReporter.clear(); + TypeProvider::reset(); } -bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary) +void CompilerStack::setSources(StringMap _sources) { - bool existed = m_sources.count(_name) != 0; - reset(true); - m_sources[_name].scanner = make_shared(CharStream(_content), _name); - m_sources[_name].isLibrary = _isLibrary; + if (m_stackState == SourcesSet) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Cannot change sources once set.")); + if (m_stackState != Empty) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set sources before parsing.")); + for (auto source: _sources) + m_sources[source.first].scanner = make_shared(CharStream(/*content*/std::move(source.second), /*name*/source.first)); m_stackState = SourcesSet; - return existed; } bool CompilerStack::parse() { - //reset if (m_stackState != SourcesSet) - return false; + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call parse only after the SourcesSet state.")); m_errorReporter.clear(); ASTNode::resetID(); @@ -144,6 +217,12 @@ bool CompilerStack::parse() || SemVerVersion{string(SolcVersionString)}.isPrerelease()) m_errorReporter.warning("This is a pre-release compiler version, please do not use it in production."); + if (m_optimiserSettings.runYulOptimiser) + m_errorReporter.warning( + "The Yul optimiser is still experimental. " + "Do not use it in production unless correctness of generated code is verified with extensive tests." + ); + vector sourcesToParse; for (auto const& s: m_sources) sourcesToParse.push_back(s.first); @@ -152,7 +231,7 @@ bool CompilerStack::parse() string const& path = sourcesToParse[i]; Source& source = m_sources[path]; source.scanner->reset(); - source.ast = Parser(m_errorReporter).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 @@ -162,30 +241,29 @@ bool CompilerStack::parse() { string const& newPath = newSource.first; string const& newContents = newSource.second; - m_sources[newPath].scanner = make_shared(CharStream(newContents), newPath); + m_sources[newPath].scanner = make_shared(CharStream(newContents, newPath)); sourcesToParse.push_back(newPath); } } } - if (Error::containsOnlyWarnings(m_errorReporter.errors())) - { - m_stackState = ParsingSuccessful; - return true; - } - else - return false; + + m_stackState = ParsingPerformed; + if (!Error::containsOnlyWarnings(m_errorReporter.errors())) + m_hasError = true; + return !m_hasError; } bool CompilerStack::analyze() { - if (m_stackState != ParsingSuccessful) - return false; + if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed.")); resolveImports(); bool noErrors = true; - try { - SyntaxChecker syntaxChecker(m_errorReporter); + try + { + SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser); for (Source const* source: m_sourceOrder) if (!syntaxChecker.checkSyntax(*source->ast)) noErrors = false; @@ -196,7 +274,7 @@ bool CompilerStack::analyze() noErrors = false; m_globalContext = make_shared(); - NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter); + NameAndTypeResolver resolver(*m_globalContext, m_scopes, m_errorReporter); for (Source const* source: m_sourceOrder) { for (ASTPointer const& node: source->ast->nodes()) @@ -224,6 +302,7 @@ bool CompilerStack::analyze() if (!resolver.resolveFactMemberReferences(*contract)) return false; if (!resolver.resolveNamesAndTypes(*contract)) return false; + if (!resolver.resolveNamesAndTypes(*contract)) return false; // Note that we now reference contracts by their fully qualified names, and // thus contracts can only conflict if declared in the same source file. This // already causes a double-declaration error elsewhere, so we do not report @@ -232,8 +311,21 @@ bool CompilerStack::analyze() m_contracts[contract->fullyQualifiedName()].contract = contract; } - // This cannot be done in the above loop, because cross-contract types couldn't be resolved. - // A good example is `LibraryName.TypeName x;`. + // Next, we check inheritance, overrides, function collisions and other things at + // contract or function level. + // This also calculates whether a contract is abstract, which is needed by the + // type checker. + ContractLevelChecker contractLevelChecker(m_errorReporter); + for (Source const* source: m_sourceOrder) + for (ASTPointer const& node: source->ast->nodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + if (!contractLevelChecker.check(*contract)) + noErrors = false; + + // New we run full type checks that go down to the expression level. This + // cannot be done earlier, because we need cross-contract types and information + // about whether a contract is abstract for the `new` expression. + // This populates the `type` annotation for all expressions. // // Note: this does not resolve overloaded functions. In order to do that, types of arguments are needed, // which is only done one step later. @@ -293,9 +385,10 @@ bool CompilerStack::analyze() if (noErrors) { - SMTChecker smtChecker(m_errorReporter, m_smtQuery); + ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses); for (Source const* source: m_sourceOrder) - smtChecker.analyze(*source->ast, source->scanner); + modelChecker.analyze(*source->ast); + m_unhandledSMTLib2Queries += modelChecker.unhandledQueries(); } if (noErrors) @@ -308,55 +401,83 @@ bool CompilerStack::analyze() noErrors = false; } } - catch(FatalError const&) + catch (FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. noErrors = false; } - if (noErrors) - { - m_stackState = AnalysisSuccessful; - return true; - } - else - return false; + m_stackState = AnalysisPerformed; + if (!noErrors) + m_hasError = true; + + return !m_hasError; } bool CompilerStack::parseAndAnalyze() { - return parse() && analyze(); + bool success = parse(); + if (success || m_parserErrorRecovery) + success = analyze(); + return success; } -bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) const +bool CompilerStack::isRequestedSource(string const& _sourceName) const { return m_requestedContractNames.empty() || - m_requestedContractNames.count(_contract.fullyQualifiedName()) || - m_requestedContractNames.count(_contract.name()); + m_requestedContractNames.count("") || + m_requestedContractNames.count(_sourceName); +} + +bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) const +{ + /// In case nothing was specified in outputSelection. + if (m_requestedContractNames.empty()) + return true; + + for (auto const& key: vector{"", _contract.sourceUnitName()}) + { + auto const& it = m_requestedContractNames.find(key); + if (it != m_requestedContractNames.end()) + if (it->second.count(_contract.name()) || it->second.count("")) + return true; + } + + return false; } bool CompilerStack::compile() { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) if (!parseAndAnalyze()) return false; + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors.")); + // Only compile contracts individually which have been requested. - map compiledContracts; + map> otherCompilers; for (Source const* source: m_sourceOrder) for (ASTPointer const& node: source->ast->nodes()) if (auto contract = dynamic_cast(node.get())) if (isRequestedContract(*contract)) - compileContract(*contract, compiledContracts); - this->link(); + { + compileContract(*contract, otherCompilers); + if (m_generateIR || m_generateEWasm) + generateIR(*contract); + if (m_generateEWasm) + generateEWasm(*contract); + } m_stackState = CompilationSuccessful; + this->link(); return true; } void CompilerStack::link() { + solAssert(m_stackState >= CompilationSuccessful, ""); for (auto& contract: m_contracts) { contract.second.object.link(m_libraries); @@ -366,7 +487,7 @@ void CompilerStack::link() vector CompilerStack::contractNames() const { - if (m_stackState < AnalysisSuccessful) + if (m_stackState < AnalysisPerformed) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); vector contractNames; for (auto const& contract: m_contracts) @@ -374,20 +495,42 @@ vector CompilerStack::contractNames() const return contractNames; } +string const CompilerStack::lastContractName() const +{ + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + // try to find some user-supplied contract + string contractName; + for (auto const& it: m_sources) + for (ASTPointer const& node: it.second.ast->nodes()) + if (auto contract = dynamic_cast(node.get())) + contractName = contract->fullyQualifiedName(); + return contractName; +} + eth::AssemblyItems const* CompilerStack::assemblyItems(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); return currentContract.compiler ? &contract(_contractName).compiler->assemblyItems() : nullptr; } eth::AssemblyItems const* CompilerStack::runtimeAssemblyItems(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); return currentContract.compiler ? &contract(_contractName).compiler->runtimeAssemblyItems() : nullptr; } string const* CompilerStack::sourceMapping(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& c = contract(_contractName); if (!c.sourceMapping) { @@ -399,6 +542,9 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const string const* CompilerStack::runtimeSourceMapping(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& c = contract(_contractName); if (!c.runtimeSourceMapping) { @@ -410,6 +556,9 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const { + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + // Look up the contract (by its fully-qualified name) Contract const& matchContract = m_contracts.at(_contractName); // Check to see if it could collide on name @@ -429,19 +578,52 @@ std::string const CompilerStack::filesystemFriendlyName(string const& _contractN return matchContract.contract->name(); } +string const& CompilerStack::yulIR(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIR; +} + +string const& CompilerStack::yulIROptimized(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).yulIROptimized; +} + +string const& CompilerStack::eWasm(string const& _contractName) const +{ + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + + return contract(_contractName).eWasm; +} + eth::LinkerObject const& CompilerStack::object(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + return contract(_contractName).object; } eth::LinkerObject const& CompilerStack::runtimeObject(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + return contract(_contractName).runtimeObject; } -/// FIXME: cache this string +/// TODO: cache this string string CompilerStack::assemblyString(string const& _contractName, StringMap _sourceCodes) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); if (currentContract.compiler) return currentContract.compiler->assemblyString(_sourceCodes); @@ -449,9 +631,12 @@ string CompilerStack::assemblyString(string const& _contractName, StringMap _sou return string(); } -/// FIXME: cache the JSON -Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap _sourceCodes) const +/// TODO: cache the JSON +Json::Value CompilerStack::assemblyJSON(string const& _contractName, StringMap const& _sourceCodes) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + Contract const& currentContract = contract(_contractName); if (currentContract.compiler) return currentContract.compiler->assemblyJSON(_sourceCodes); @@ -478,13 +663,16 @@ map CompilerStack::sourceIndices() const Json::Value const& CompilerStack::contractABI(string const& _contractName) const { + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return contractABI(contract(_contractName)); } Json::Value const& CompilerStack::contractABI(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -497,13 +685,16 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const Json::Value const& CompilerStack::natspecUser(string const& _contractName) const { + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return natspecUser(contract(_contractName)); } Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -516,13 +707,16 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const Json::Value const& CompilerStack::natspecDev(string const& _contractName) const { + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + return natspecDev(contract(_contractName)); } Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const { - if (m_stackState < AnalysisSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); solAssert(_contract.contract, ""); @@ -535,18 +729,35 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const { + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + Json::Value methodIdentifiers(Json::objectValue); for (auto const& it: contractDefinition(_contractName).interfaceFunctions()) - methodIdentifiers[it.second->externalSignature()] = toHex(it.first.ref()); + methodIdentifiers[it.second->externalSignature()] = it.first.hex(); return methodIdentifiers; } string const& CompilerStack::metadata(string const& _contractName) const { - if (m_stackState != CompilationSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + + return metadata(contract(_contractName)); +} + +string const& CompilerStack::metadata(Contract const& _contract) const +{ + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); + + solAssert(_contract.contract, ""); + + // cache the result + if (!_contract.metadata) + _contract.metadata.reset(new string(createMetadata(_contract))); - return contract(_contractName).metadata; + return *_contract.metadata; } Scanner const& CompilerStack::scanner(string const& _sourceName) const @@ -559,7 +770,9 @@ Scanner const& CompilerStack::scanner(string const& _sourceName) const SourceUnit const& CompilerStack::ast(string const& _sourceName) const { - if (m_stackState < ParsingSuccessful) + if (m_stackState < ParsingPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing not yet performed.")); + if (!source(_sourceName).ast && !m_parserErrorRecovery) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); return *source(_sourceName).ast; @@ -567,8 +780,8 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const { - if (m_stackState != CompilationSuccessful) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + if (m_stackState < AnalysisPerformed) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful.")); return *contract(_contractName).contract; } @@ -578,6 +791,9 @@ size_t CompilerStack::functionEntryPoint( FunctionDefinition const& _function ) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + shared_ptr const& compiler = contract(_contractName).compiler; if (!compiler) return 0; @@ -597,18 +813,44 @@ tuple CompilerStack::positionFromSourceLocation(SourceLocati int startColumn; int endLine; int endColumn; - tie(startLine, startColumn) = scanner(*_sourceLocation.sourceName).translatePositionToLineColumn(_sourceLocation.start); - tie(endLine, endColumn) = scanner(*_sourceLocation.sourceName).translatePositionToLineColumn(_sourceLocation.end); + tie(startLine, startColumn) = scanner(_sourceLocation.source->name()).translatePositionToLineColumn(_sourceLocation.start); + tie(endLine, endColumn) = scanner(_sourceLocation.source->name()).translatePositionToLineColumn(_sourceLocation.end); return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn); } + +h256 const& CompilerStack::Source::keccak256() const +{ + if (keccak256HashCached == h256{}) + keccak256HashCached = dev::keccak256(scanner->source()); + return keccak256HashCached; +} + +h256 const& CompilerStack::Source::swarmHash() const +{ + if (swarmHashCached == h256{}) + swarmHashCached = dev::bzzr1Hash(scanner->source()); + return swarmHashCached; +} + +string const& CompilerStack::Source::ipfsUrl() const +{ + if (ipfsUrlCached.empty()) + if (scanner->source().size() < 1024 * 256) + ipfsUrlCached = "dweb:/ipfs/" + dev::ipfsHashBase58(scanner->source()); + return ipfsUrlCached; +} + StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath) { + solAssert(m_stackState < ParsingPerformed, ""); StringMap newSources; for (auto const& node: _ast.nodes()) if (ImportDirective const* import = dynamic_cast(node.get())) { + solAssert(!import->path().empty(), "Import path cannot be empty."); + string importPath = dev::absolutePath(import->path(), _sourcePath); // The current value of `path` is the absolute path as seen from this source file. // We first have to apply remappings before we can store the actual absolute path @@ -638,6 +880,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string string CompilerStack::applyRemapping(string const& _path, string const& _context) { + solAssert(m_stackState < ParsingPerformed, ""); // Try to find the longest prefix match in all remappings that are active in the current context. auto isPrefixOf = [](string const& _a, string const& _b) { @@ -679,6 +922,8 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context void CompilerStack::resolveImports() { + solAssert(m_stackState == ParsingPerformed, ""); + // topological sorting (depth first search) of the import graph, cutting potential cycles vector sourceOrder; set sourcesSeen; @@ -688,20 +933,21 @@ void CompilerStack::resolveImports() if (sourcesSeen.count(_source)) return; sourcesSeen.insert(_source); - for (ASTPointer const& node: _source->ast->nodes()) - if (ImportDirective const* import = dynamic_cast(node.get())) - { - string const& path = import->annotation().absolutePath; - solAssert(!path.empty(), ""); - solAssert(m_sources.count(path), ""); - import->annotation().sourceUnit = m_sources[path].ast.get(); - toposort(&m_sources[path]); - } + if (_source->ast) + for (ASTPointer const& node: _source->ast->nodes()) + if (ImportDirective const* import = dynamic_cast(node.get())) + { + string const& path = import->annotation().absolutePath; + solAssert(!path.empty(), ""); + solAssert(m_sources.count(path), ""); + import->annotation().sourceUnit = m_sources[path].ast.get(); + toposort(&m_sources[path]); + } sourceOrder.push_back(_source); }; for (auto const& sourcePair: m_sources) - if (!sourcePair.second.isLibrary) + if (isRequestedSource(sourcePair.first)) toposort(&sourcePair.second); swap(m_sourceOrder, sourceOrder); @@ -720,35 +966,32 @@ bool onlySafeExperimentalFeaturesActivated(set const& featu void CompilerStack::compileContract( ContractDefinition const& _contract, - map& _compiledContracts + map>& _otherCompilers ) { - if ( - _compiledContracts.count(&_contract) || - !_contract.annotation().unimplementedFunctions.empty() || - !_contract.constructorIsPublic() - ) + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors.")); + + if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed()) return; for (auto const* dependency: _contract.annotation().contractDependencies) - compileContract(*dependency, _compiledContracts); + compileContract(*dependency, _otherCompilers); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); - shared_ptr compiler = make_shared(m_evmVersion, m_optimize, m_optimizeRuns); + shared_ptr compiler = make_shared(m_evmVersion, m_optimiserSettings); compiledContract.compiler = compiler; - string metadata = createMetadata(compiledContract); - compiledContract.metadata = metadata; - bytes cborEncodedMetadata = createCBORMetadata( - metadata, + metadata(compiledContract), !onlySafeExperimentalFeaturesActivated(_contract.sourceUnit().annotation().experimentalFeatures) ); try { // Run optimiser and compile the contract. - compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); + compiler->compileContract(_contract, _otherCompilers, cborEncodedMetadata); } catch(eth::OptimizerException const&) { @@ -775,26 +1018,65 @@ void CompilerStack::compileContract( solAssert(false, "Assembly exception for deployed bytecode"); } - _compiledContracts[compiledContract.contract] = &compiler->assembly(); + _otherCompilers[compiledContract.contract] = compiler; } -string const CompilerStack::lastContractName() const +void CompilerStack::generateIR(ContractDefinition const& _contract) { - if (m_contracts.empty()) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); - // try to find some user-supplied contract - string contractName; - for (auto const& it: m_sources) - for (ASTPointer const& node: it.second.ast->nodes()) - if (auto contract = dynamic_cast(node.get())) - contractName = contract->fullyQualifiedName(); - return contractName; + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateIR with errors.")); + + if (!_contract.canBeDeployed()) + return; + + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); + if (!compiledContract.yulIR.empty()) + return; + + for (auto const* dependency: _contract.annotation().contractDependencies) + generateIR(*dependency); + + IRGenerator generator(m_evmVersion, m_optimiserSettings); + tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract); +} + +void CompilerStack::generateEWasm(ContractDefinition const& _contract) +{ + solAssert(m_stackState >= AnalysisPerformed, ""); + if (m_hasError) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEWasm with errors.")); + + Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); + solAssert(!compiledContract.yulIROptimized.empty(), ""); + if (!compiledContract.eWasm.empty()) + return; + + // Re-parse the Yul IR in EVM dialect + yul::AssemblyStack evmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings); + evmStack.parseAndAnalyze("", compiledContract.yulIROptimized); + + // Turn into eWasm dialect + yul::Object ewasmObject = yul::EVMToEWasmTranslator( + yul::EVMDialect::strictAssemblyForEVMObjects(m_evmVersion) + ).run(*evmStack.parserResult()); + + // Re-inject into an assembly stack for the eWasm dialect + yul::AssemblyStack ewasmStack(m_evmVersion, yul::AssemblyStack::Language::EWasm, m_optimiserSettings); + // TODO this is a hack for now - provide as structured AST! + ewasmStack.parseAndAnalyze("", "{}"); + *ewasmStack.parserResult() = move(ewasmObject); + ewasmStack.optimize(); + + //cout << yul::AsmPrinter{}(*ewasmStack.parserResult()->code) << endl; + + // Turn into eWasm text representation. + compiledContract.eWasm = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm).assembly; } CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { - if (m_contracts.empty()) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + solAssert(m_stackState >= AnalysisPerformed, ""); auto it = m_contracts.find(_contractName); if (it != m_contracts.end()) @@ -852,20 +1134,51 @@ string CompilerStack::createMetadata(Contract const& _contract) const continue; solAssert(s.second.scanner, "Scanner not available"); - meta["sources"][s.first]["keccak256"] = - "0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes()); + meta["sources"][s.first]["keccak256"] = "0x" + toHex(s.second.keccak256().asBytes()); if (m_metadataLiteralSources) meta["sources"][s.first]["content"] = s.second.scanner->source(); else { meta["sources"][s.first]["urls"] = Json::arrayValue; - meta["sources"][s.first]["urls"].append( - "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes()) - ); + meta["sources"][s.first]["urls"].append("bzz-raw://" + toHex(s.second.swarmHash().asBytes())); + meta["sources"][s.first]["urls"].append(s.second.ipfsUrl()); } } - meta["settings"]["optimizer"]["enabled"] = m_optimize; - meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; + + static_assert(sizeof(m_optimiserSettings.expectedExecutionsPerDeployment) <= sizeof(Json::LargestUInt), "Invalid word size."); + solAssert(static_cast(m_optimiserSettings.expectedExecutionsPerDeployment) < std::numeric_limits::max(), ""); + meta["settings"]["optimizer"]["runs"] = Json::Value(Json::LargestUInt(m_optimiserSettings.expectedExecutionsPerDeployment)); + + /// Backwards compatibility: If set to one of the default settings, do not provide details. + OptimiserSettings settingsWithoutRuns = m_optimiserSettings; + // reset to default + settingsWithoutRuns.expectedExecutionsPerDeployment = OptimiserSettings::minimal().expectedExecutionsPerDeployment; + if (settingsWithoutRuns == OptimiserSettings::minimal()) + meta["settings"]["optimizer"]["enabled"] = false; + else if (settingsWithoutRuns == OptimiserSettings::standard()) + meta["settings"]["optimizer"]["enabled"] = true; + else + { + Json::Value details{Json::objectValue}; + + details["orderLiterals"] = m_optimiserSettings.runOrderLiterals; + details["jumpdestRemover"] = m_optimiserSettings.runJumpdestRemover; + details["peephole"] = m_optimiserSettings.runPeephole; + details["deduplicate"] = m_optimiserSettings.runDeduplicate; + details["cse"] = m_optimiserSettings.runCSE; + details["constantOptimizer"] = m_optimiserSettings.runConstantOptimiser; + details["yul"] = m_optimiserSettings.runYulOptimiser; + if (m_optimiserSettings.runYulOptimiser) + { + details["yulDetails"] = Json::objectValue; + details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation; + } + + meta["settings"]["optimizer"]["details"] = std::move(details); + } + + if (m_metadataLiteralSources) + meta["settings"]["metadata"]["useLiteralContent"] = true; meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; @@ -888,33 +1201,107 @@ string CompilerStack::createMetadata(Contract const& _contract) const return jsonCompactPrint(meta); } -bytes CompilerStack::createCBORMetadata(string _metadata, bool _experimentalMode) +class MetadataCBOREncoder { - bytes cborEncodedHash = - // CBOR-encoding of the key "bzzr0" - bytes{0x65, 'b', 'z', 'z', 'r', '0'}+ - // CBOR-encoding of the hash - bytes{0x58, 0x20} + dev::swarmHash(_metadata).asBytes(); - bytes cborEncodedMetadata; +public: + void pushBytes(string const& key, bytes const& value) + { + m_entryCount++; + pushTextString(key); + pushByteString(value); + } + + void pushString(string const& key, string const& value) + { + m_entryCount++; + pushTextString(key); + pushTextString(value); + } + + void pushBool(string const& key, bool value) + { + m_entryCount++; + pushTextString(key); + pushBool(value); + } + + bytes serialise() const + { + unsigned size = m_data.size() + 1; + solAssert(size <= 0xffff, "Metadata too large."); + solAssert(m_entryCount <= 0x1f, "Too many map entries."); + + // CBOR fixed-length map + bytes ret{static_cast(0xa0 + m_entryCount)}; + // The already encoded key-value pairs + ret += m_data; + // 16-bit big endian length + ret += toCompactBigEndian(size, 2); + return ret; + } + +private: + void pushTextString(string const& key) + { + unsigned length = key.size(); + if (length < 24) + { + m_data += bytes{static_cast(0x60 + length)}; + m_data += key; + } + else if (length <= 256) + { + m_data += bytes{0x78, static_cast(length)}; + m_data += key; + } + else + solAssert(false, "Text string too large."); + } + void pushByteString(bytes const& key) + { + unsigned length = key.size(); + if (length < 24) + { + m_data += bytes{static_cast(0x40 + length)}; + m_data += key; + } + else if (length <= 256) + { + m_data += bytes{0x58, static_cast(length)}; + m_data += key; + } + else + solAssert(false, "Byte string too large."); + } + void pushBool(bool value) + { + if (value) + m_data += bytes{0xf5}; + else + m_data += bytes{0xf4}; + } + unsigned m_entryCount = 0; + bytes m_data; +}; + +bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode) +{ + MetadataCBOREncoder encoder; + encoder.pushBytes("bzzr1", dev::bzzr1Hash(_metadata).asBytes()); if (_experimentalMode) - cborEncodedMetadata = - // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata), "experimental": true} - bytes{0xa2} + - cborEncodedHash + - bytes{0x6c, 'e', 'x', 'p', 'e', 'r', 'i', 'm', 'e', 'n', 't', 'a', 'l', 0xf5}; + encoder.pushBool("experimental", true); + if (m_release) + encoder.pushBytes("solc", VersionCompactBytes); else - cborEncodedMetadata = - // CBOR-encoding of {"bzzr0": dev::swarmHash(metadata)} - bytes{0xa1} + - cborEncodedHash; - solAssert(cborEncodedMetadata.size() <= 0xffff, "Metadata too large"); - // 16-bit big endian length - cborEncodedMetadata += toCompactBigEndian(cborEncodedMetadata.size(), 2); - return cborEncodedMetadata; + encoder.pushString("solc", VersionStringStrict); + return encoder.serialise(); } string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + string ret; map sourceIndicesMap = sourceIndices(); int prevStart = -1; @@ -929,8 +1316,8 @@ string CompilerStack::computeSourceMapping(eth::AssemblyItems const& _items) con SourceLocation const& location = item.location(); int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1; int sourceIndex = - location.sourceName && sourceIndicesMap.count(*location.sourceName) ? - sourceIndicesMap.at(*location.sourceName) : + location.source && sourceIndicesMap.count(location.source->name()) ? + sourceIndicesMap.at(location.source->name()) : -1; char jump = '-'; if (item.getJumpType() == eth::AssemblyItem::JumpType::IntoFunction) @@ -1001,6 +1388,9 @@ Json::Value gasToJson(GasEstimator::GasConsumption const& _gas) Json::Value CompilerStack::gasEstimates(string const& _contractName) const { + if (m_stackState != CompilationSuccessful) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful.")); + if (!assemblyItems(_contractName) && !runtimeAssemblyItems(_contractName)) return Json::Value(); @@ -1081,4 +1471,4 @@ vector> CompilerStack::stateVa std::vector CompilerStack::definedStructs(std::string const& _contractName) const { return contractDefinition(_contractName).definedStructs(); -} \ No newline at end of file +} diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index eec5ea055..db6b32338 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -23,28 +23,33 @@ #pragma once -#include #include -#include +#include +#include -#include +#include +#include +#include -#include #include #include #include -#include - #include +#include +#include +#include #include +#include #include -#include #include -#include -#include + +namespace langutil +{ +class Scanner; +} namespace dev { @@ -60,7 +65,6 @@ namespace solidity { // forward declarations -class Scanner; class ASTNode; class ContractDefinition; class FunctionDefinition; @@ -68,13 +72,14 @@ class SourceUnit; class Compiler; class GlobalContext; class Natspec; -class Error; class DeclarationContainer; /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. * It holds state and can be used to either step through the compilation stages (and abort e.g. * before compilation to bytecode) or run the whole compilation in one call. + * If error recovery is active, it is possible to progress through the stages even when + * there are errors. In any case, producing code is only possible without errors. */ class CompilerStack: boost::noncopyable { @@ -82,8 +87,8 @@ class CompilerStack: boost::noncopyable enum State { Empty, SourcesSet, - ParsingSuccessful, - AnalysisSuccessful, + ParsingPerformed, + AnalysisPerformed, CompilationSuccessful }; @@ -97,50 +102,61 @@ class CompilerStack: boost::noncopyable /// Creates a new compiler stack. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. - explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): - m_readFile(_readFile), - m_errorList(), - m_errorReporter(m_errorList) {} + explicit CompilerStack(ReadCallback::Callback const& _readFile = ReadCallback::Callback()); + + ~CompilerStack(); /// @returns the list of errors that occurred during parsing and type checking. - ErrorList const& errors() const { return m_errorReporter.errors(); } + langutil::ErrorList const& errors() const { return m_errorReporter.errors(); } /// @returns the current state. State state() const { return m_stackState; } - /// Resets the compiler to a state where the sources are not parsed or even removed. - /// Sets the state to SourcesSet if @a _keepSources is true, otherwise to Empty. - /// All settings, with the exception of remappings, are reset. - void reset(bool _keepSources = false); + bool hasError() const { return m_hasError; } + + bool compilationSuccessful() const { return m_stackState >= CompilationSuccessful; } + + /// Resets the compiler to an empty state. Unless @a _keepSettings is set to true, + /// all settings are reset as well. + void reset(bool _keepSettings = false); // Parses a remapping of the format "context:prefix=target". static boost::optional parseRemapping(std::string const& _remapping); /// Sets path remappings. + /// Must be set before parsing. void setRemappings(std::vector const& _remappings); /// Sets library addresses. Addresses are cleared iff @a _libraries is missing. - /// Will not take effect before running compile. - void setLibraries(std::map const& _libraries = std::map{}) - { - m_libraries = _libraries; - } + /// Must be set before parsing. + void setLibraries(std::map const& _libraries = std::map{}); /// Changes the optimiser settings. - /// Will not take effect before running compile. - void setOptimiserSettings(bool _optimize, unsigned _runs = 200) + /// Must be set before parsing. + void setOptimiserSettings(bool _optimize, unsigned _runs = 200); + + /// Changes the optimiser settings. + /// 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_optimize = _optimize; - m_optimizeRuns = _runs; + m_parserErrorRecovery = _wantErrorRecovery; } /// Set the EVM version used before running compile. /// When called without an argument it will revert to the default version. - void setEVMVersion(EVMVersion _version = EVMVersion{}); - - /// Sets the list of requested contract names. If empty, no filtering is performed and every contract - /// found in the supplied sources is compiled. Names are cleared iff @a _contractNames is missing. - void setRequestedContractNames(std::set const& _contractNames = std::set{}) + /// Must be set before parsing. + void setEVMVersion(langutil::EVMVersion _version = langutil::EVMVersion{}); + + /// Sets the requested contract names by source. + /// If empty, no filtering is performed and every contract + /// found in the supplied sources is compiled. + /// Names are cleared iff @a _contractNames is missing. + void setRequestedContractNames(std::map> const& _contractNames = std::map>{}) { m_requestedContractNames = _contractNames; } @@ -150,13 +166,22 @@ class CompilerStack: boost::noncopyable { m_contractStandard = _contractStandard; } + /// Enable experimental generation of Yul IR code. + void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; } + + /// Enable experimental generation of eWasm code. If enabled, IR is also generated. + void enableEWasmGeneration(bool _enable = true) { m_generateEWasm = _enable; } /// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata. - void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; } + /// Must be set before parsing. + void useMetadataLiteralSources(bool _metadataLiteralSources); + + /// Sets the sources. Must be set before parsing. + void setSources(StringMap _sources); - /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. - /// @returns true if a source object by the name already existed and was replaced. - bool addSource(std::string const& _name, std::string const& _content, bool _isLibrary = false); + /// Adds a response to an SMTLib2 query (identified by the hash of the query input). + /// Must be set before parsing. + void addSMTLib2Response(h256 const& _hash, std::string const& _response); /// Parses all source units that were added /// @returns false on error. @@ -183,7 +208,7 @@ class CompilerStack: boost::noncopyable std::map sourceIndices() const; /// @returns the previously used scanner, useful for counting lines during error reporting. - Scanner const& scanner(std::string const& _sourceName) const; + langutil::Scanner const& scanner(std::string const& _sourceName) const; /// @returns the parsed source unit with the supplied name. SourceUnit const& ast(std::string const& _sourceName) const; @@ -191,7 +216,11 @@ class CompilerStack: boost::noncopyable /// Helper function for logs printing. Do only use in error cases, it's quite expensive. /// line and columns are numbered starting from 1 with following order: /// start line, start column, end line, end column - std::tuple positionFromSourceLocation(SourceLocation const& _sourceLocation) const; + std::tuple positionFromSourceLocation(langutil::SourceLocation const& _sourceLocation) const; + + /// @returns a list of unhandled queries to the SMT solver (has to be supplied in a second run + /// by calling @a addSMTLib2Response). + std::vector const& unhandledSMTLib2Queries() const { return m_unhandledSMTLib2Queries; } /// @returns a list of the contract names in the sources. std::vector contractNames() const; @@ -202,6 +231,15 @@ class CompilerStack: boost::noncopyable /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; + /// @returns the IR representation of a contract. + std::string const& yulIR(std::string const& _contractName) const; + + /// @returns the optimized IR representation of a contract. + std::string const& yulIROptimized(std::string const& _contractName) const; + + /// @returns the eWasm (text) representation of a contract. + std::string const& eWasm(std::string const& _contractName) const; + /// @returns the assembled object for a contract. eth::LinkerObject const& object(std::string const& _contractName) const; @@ -230,7 +268,7 @@ class CompilerStack: boost::noncopyable /// @returns a JSON representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - Json::Value assemblyJSON(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; + Json::Value assemblyJSON(std::string const& _contractName, StringMap const& _sourceCodes = StringMap()) const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. @@ -253,17 +291,21 @@ class CompilerStack: boost::noncopyable /// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions Json::Value gasEstimates(std::string const& _contractName) const; - std::vector> stateVariables(std::string const& _contractName) const; - std::vector definedStructs(std::string const& _contractName) const; - + /// Overwrites the release/prerelease flag. Should only be used for testing. + void overwriteReleaseFlag(bool release) { m_release = release; } private: /// The state per source unit. Filled gradually during parsing. struct Source { - std::shared_ptr scanner; + std::shared_ptr scanner; std::shared_ptr ast; - bool isLibrary = false; - void reset() { scanner.reset(); ast.reset(); } + h256 mutable keccak256HashCached; + h256 mutable swarmHashCached; + std::string mutable ipfsUrlCached; + void reset() { *this = Source(); } + h256 const& keccak256() const; + h256 const& swarmHash() const; + std::string const& ipfsUrl() const; }; /// The state per contract. Filled gradually during compilation. @@ -273,7 +315,10 @@ class CompilerStack: boost::noncopyable std::shared_ptr compiler; eth::LinkerObject object; ///< Deployment object (includes the runtime sub-object). eth::LinkerObject runtimeObject; ///< Runtime object. - std::string metadata; ///< The metadata json that will be hashed into the chain. + std::string yulIR; ///< Experimental Yul IR code. + std::string yulIROptimized; ///< Optimized experimental Yul IR code. + std::string eWasm; ///< Experimental eWasm code (text representation). + mutable std::unique_ptr metadata; ///< The metadata json that will be hashed into the chain. mutable std::unique_ptr abi; mutable std::unique_ptr userDocumentation; mutable std::unique_ptr devDocumentation; @@ -288,15 +333,27 @@ class CompilerStack: boost::noncopyable std::string applyRemapping(std::string const& _path, std::string const& _context); void resolveImports(); + /// @returns true if the source is requested to be compiled. + bool isRequestedSource(std::string const& _sourceName) const; + /// @returns true if the contract is requested to be compiled. bool isRequestedContract(ContractDefinition const& _contract) const; - /// Compile a single contract and put the result in @a _compiledContracts. + /// Compile a single contract. + /// @param _otherCompilers provides access to compilers of other contracts, to get + /// their bytecode if needed. Only filled after they have been compiled. void compileContract( ContractDefinition const& _contract, - std::map& _compiledContracts + std::map>& _otherCompilers ); + /// Generate Yul IR for a single contract. + /// The IR is stored but otherwise unused. + void generateIR(ContractDefinition const& _contract); + + /// Generate eWasm text representation for a single contract. + void generateEWasm(ContractDefinition const& _contract); + /// Links all the known library addresses in the available objects. Any unknown /// library will still be kept as an unlinked placeholder in the objects. void link(); @@ -317,7 +374,7 @@ class CompilerStack: boost::noncopyable std::string createMetadata(Contract const& _contract) const; /// @returns the metadata CBOR for the given serialised metadata JSON. - static bytes createCBORMetadata(std::string _metadata, bool _experimentalMode); + bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode); /// @returns the computer source mapping string. std::string computeSourceMapping(eth::AssemblyItems const& _items) const; @@ -334,6 +391,10 @@ class CompilerStack: boost::noncopyable /// This will generate the JSON object and store it in the Contract object if it is not present yet. Json::Value const& natspecDev(Contract const&) const; + /// @returns the Contract Metadata + /// This will generate the metadata and store it in the Contract object if it is not present yet. + std::string const& metadata(Contract const&) const; + /// @returns the offset of the entry point of the given function into the list of assembly items /// or zero if it is not found or does not exist. size_t functionEntryPoint( @@ -342,26 +403,33 @@ class CompilerStack: boost::noncopyable ) const; ReadCallback::Callback m_readFile; - ReadCallback::Callback m_smtQuery; - bool m_optimize = false; - unsigned m_optimizeRuns = 200; - EVMVersion m_evmVersion; - std::set m_requestedContractNames; + OptimiserSettings m_optimiserSettings; + langutil::EVMVersion m_evmVersion; + std::map> m_requestedContractNames; + bool m_generateIR; + bool m_generateEWasm; std::map m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum /// "context:prefix=target" std::vector m_remappings; std::map m_sources; + std::vector m_unhandledSMTLib2Queries; + std::map m_smtlib2Responses; std::shared_ptr m_globalContext; std::vector m_sourceOrder; /// This is updated during compilation. std::map> m_scopes; std::map m_contracts; boost::optional m_contractStandard; - ErrorList m_errorList; - ErrorReporter m_errorReporter; + langutil::ErrorList m_errorList; + langutil::ErrorReporter m_errorReporter; bool m_metadataLiteralSources = false; + bool m_parserErrorRecovery = false; State m_stackState = Empty; + /// Whether or not there has been an error during processing. + /// If this is true, the stack will refuse to generate code. + bool m_hasError = false; + bool m_release = VersionIsRelease; }; } diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index 1f20366ed..8ffcf9513 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -20,21 +20,25 @@ * Gas consumption estimator working alongside the AST. */ -#include "GasEstimator.h" -#include -#include -#include -#include -#include -#include -#include +#include + #include #include #include +#include +#include +#include +#include + +#include +#include +#include + using namespace std; using namespace dev; using namespace dev::eth; +using namespace langutil; using namespace dev::solidity; GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimation( diff --git a/libsolidity/interface/GasEstimator.h b/libsolidity/interface/GasEstimator.h index ea94d9888..784fada2f 100644 --- a/libsolidity/interface/GasEstimator.h +++ b/libsolidity/interface/GasEstimator.h @@ -22,14 +22,14 @@ #pragma once -#include +#include -#include #include +#include -#include -#include #include +#include +#include namespace dev { @@ -47,7 +47,7 @@ struct GasEstimator using ASTGasConsumptionSelfAccumulated = std::map>; - explicit GasEstimator(EVMVersion _evmVersion): m_evmVersion(_evmVersion) {} + explicit GasEstimator(langutil::EVMVersion _evmVersion): m_evmVersion(_evmVersion) {} /// Estimates the gas consumption for every assembly item in the given assembly and stores /// it by source location. @@ -84,7 +84,7 @@ struct GasEstimator private: /// @returns the set of AST nodes which are the finest nodes at their location. static std::set finestNodesAtLocation(std::vector const& _roots); - EVMVersion m_evmVersion; + langutil::EVMVersion m_evmVersion; }; } diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 11dde3499..e28d8703e 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Lefteris @@ -24,8 +24,9 @@ */ #include -#include + #include +#include using namespace std; using namespace dev; @@ -117,7 +118,7 @@ string Natspec::extractDoc(multimap const& _tags, string const& return value; } -Json::Value Natspec::devDocumentation(std::multimap const &_tags) +Json::Value Natspec::devDocumentation(std::multimap const& _tags) { Json::Value json(Json::objectValue); auto dev = extractDoc(_tags, "dev"); diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h index 0be4dda28..7a0c40a1a 100644 --- a/libsolidity/interface/Natspec.h +++ b/libsolidity/interface/Natspec.h @@ -25,9 +25,9 @@ #pragma once -#include -#include #include +#include +#include namespace dev { @@ -59,7 +59,7 @@ class Natspec /// @param _tags docTags that are used. /// @return A JSON representation /// of the contract's developer documentation - static Json::Value devDocumentation(std::multimap const &_tags); + static Json::Value devDocumentation(std::multimap const& _tags); }; } //solidity NS diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h new file mode 100644 index 000000000..83203865f --- /dev/null +++ b/libsolidity/interface/OptimiserSettings.h @@ -0,0 +1,110 @@ +/* + 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 Alex Beregszaszi + * @date 2017 + * Helper class for optimiser settings. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +struct OptimiserSettings +{ + /// No optimisations at all - not recommended. + static OptimiserSettings none() + { + return {}; + } + /// Minimal optimisations: Peephole and jumpdest remover + static OptimiserSettings minimal() + { + OptimiserSettings s = none(); + s.runJumpdestRemover = true; + s.runPeephole = true; + return s; + } + /// Standard optimisations. + static OptimiserSettings standard() + { + OptimiserSettings s; + s.runOrderLiterals = true; + s.runJumpdestRemover = true; + s.runPeephole = true; + s.runDeduplicate = true; + s.runCSE = true; + s.runConstantOptimiser = true; + // The only disabled ones + s.optimizeStackAllocation = false; + s.runYulOptimiser = false; + s.expectedExecutionsPerDeployment = 200; + return s; + } + /// Standard optimisations plus yul and stack optimiser. + static OptimiserSettings full() + { + OptimiserSettings s = standard(); + s.optimizeStackAllocation = true; + s.runYulOptimiser = true; + return s; + } + + bool operator==(OptimiserSettings const& _other) const + { + return + runOrderLiterals == _other.runOrderLiterals && + runJumpdestRemover == _other.runJumpdestRemover && + runPeephole == _other.runPeephole && + runDeduplicate == _other.runDeduplicate && + runCSE == _other.runCSE && + runConstantOptimiser == _other.runConstantOptimiser && + optimizeStackAllocation == _other.optimizeStackAllocation && + runYulOptimiser == _other.runYulOptimiser && + expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment; + } + + /// Move literals to the right of commutative binary operators during code generation. + /// This helps exploiting associativity. + bool runOrderLiterals = false; + /// Non-referenced jump destination remover. + bool runJumpdestRemover = false; + /// Peephole optimizer + bool runPeephole = false; + /// Assembly block deduplicator + bool runDeduplicate = false; + /// Common subexpression eliminator based on assembly items. + bool runCSE = false; + /// Constant optimizer, which tries to find better representations that satisfy the given + /// size/cost-trade-off. + bool runConstantOptimiser = false; + /// Perform more efficient stack allocation for variables during code generation from Yul to bytecode. + bool optimizeStackAllocation = false; + /// Yul optimiser with default settings. Will only run on certain parts of the code for now. + bool runYulOptimiser = false; + /// This specifies an estimate on how often each opcode in this assembly will be executed, + /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. + size_t expectedExecutionsPerDeployment = 200; +}; + +} +} diff --git a/libsolidity/interface/ReadFile.h b/libsolidity/interface/ReadFile.h index 7068629de..3b3d747ed 100644 --- a/libsolidity/interface/ReadFile.h +++ b/libsolidity/interface/ReadFile.h @@ -17,9 +17,9 @@ #pragma once -#include -#include #include +#include +#include namespace dev { diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp deleted file mode 100644 index 865907e21..000000000 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - 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 Christian - * @date 2014 - * Formatting functions for errors referencing positions and locations in the source. - */ - -#include -#include -#include - -using namespace std; - -namespace dev -{ -namespace solidity -{ - -void SourceReferenceFormatter::printSourceLocation(SourceLocation const* _location) -{ - if (!_location || !_location->sourceName) - return; // Nothing we can print here - auto const& scanner = m_scannerFromSourceName(*_location->sourceName); - int startLine; - int startColumn; - tie(startLine, startColumn) = scanner.translatePositionToLineColumn(_location->start); - int endLine; - int endColumn; - tie(endLine, endColumn) = scanner.translatePositionToLineColumn(_location->end); - if (startLine == endLine) - { - string line = scanner.lineAtPosition(_location->start); - - int locationLength = endColumn - startColumn; - if (locationLength > 150) - { - line = line.substr(0, startColumn + 35) + " ... " + line.substr(endColumn - 35); - endColumn = startColumn + 75; - locationLength = 75; - } - if (line.length() > 150) - { - int len = line.length(); - line = line.substr(max(0, startColumn - 35), min(startColumn, 35) + min(locationLength + 35, len - startColumn)); - if (startColumn + locationLength + 35 < len) - line += " ..."; - if (startColumn > 35) - { - line = " ... " + line; - startColumn = 40; - } - endColumn = startColumn + locationLength; - } - - m_stream << line << endl; - - for_each( - line.cbegin(), - line.cbegin() + startColumn, - [this](char const& ch) { m_stream << (ch == '\t' ? '\t' : ' '); } - ); - m_stream << "^"; - if (endColumn > startColumn + 2) - m_stream << string(endColumn - startColumn - 2, '-'); - if (endColumn > startColumn + 1) - m_stream << "^"; - m_stream << endl; - } - else - m_stream << - scanner.lineAtPosition(_location->start) << - endl << - string(startColumn, ' ') << - "^ (Relevant source part starts here and spans across multiple lines)." << - endl; -} - -void SourceReferenceFormatter::printSourceName(SourceLocation const* _location) -{ - if (!_location || !_location->sourceName) - return; // Nothing we can print here - auto const& scanner = m_scannerFromSourceName(*_location->sourceName); - int startLine; - int startColumn; - tie(startLine, startColumn) = scanner.translatePositionToLineColumn(_location->start); - m_stream << *_location->sourceName << ":" << (startLine + 1) << ":" << (startColumn + 1) << ": "; -} - -void SourceReferenceFormatter::printExceptionInformation( - Exception const& _exception, - string const& _name -) -{ - SourceLocation const* location = boost::get_error_info(_exception); - auto secondarylocation = boost::get_error_info(_exception); - - printSourceName(location); - - m_stream << _name; - if (string const* description = boost::get_error_info(_exception)) - m_stream << ": " << *description << endl; - else - m_stream << endl; - - printSourceLocation(location); - - if (secondarylocation && !secondarylocation->infos.empty()) - { - for (auto info: secondarylocation->infos) - { - printSourceName(&info.second); - m_stream << info.first << endl; - printSourceLocation(&info.second); - } - m_stream << endl; - } -} - -} -} diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 5a6ceec48..95d5e6939 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -21,19 +21,27 @@ */ #include -#include + #include +#include +#include #include #include #include +#include #include +#include +#include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; +using namespace yul; -namespace { +namespace +{ Json::Value formatError( bool _warning, @@ -41,7 +49,8 @@ Json::Value formatError( string const& _component, string const& _message, string const& _formattedMessage = "", - Json::Value const& _sourceLocation = Json::Value() + Json::Value const& _sourceLocation = Json::Value(), + Json::Value const& _secondarySourceLocation = Json::Value() ) { Json::Value error = Json::objectValue; @@ -52,6 +61,8 @@ Json::Value formatError( error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message; if (_sourceLocation.isObject()) error["sourceLocation"] = _sourceLocation; + if (_secondarySourceLocation.isArray()) + error["secondarySourceLocations"] = _secondarySourceLocation; return error; } @@ -63,51 +74,74 @@ Json::Value formatFatalError(string const& _type, string const& _message) return output; } +Json::Value formatSourceLocation(SourceLocation const* location) +{ + Json::Value sourceLocation; + if (location && location->source && !location->source->name().empty()) + { + sourceLocation["file"] = location->source->name(); + sourceLocation["start"] = location->start; + sourceLocation["end"] = location->end; + } + + return sourceLocation; +} + +Json::Value formatSecondarySourceLocation(SecondarySourceLocation const* _secondaryLocation) +{ + if (!_secondaryLocation) + return {}; + + Json::Value secondarySourceLocation = Json::arrayValue; + for (auto const& location: _secondaryLocation->infos) + { + Json::Value msg = formatSourceLocation(&location.second); + msg["message"] = location.first; + secondarySourceLocation.append(msg); + } + return secondarySourceLocation; +} + Json::Value formatErrorWithException( Exception const& _exception, bool const& _warning, string const& _type, string const& _component, - string const& _message, - function const& _scannerFromSourceName + string const& _message ) { string message; - string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type, _scannerFromSourceName); - - // NOTE: the below is partially a copy from SourceReferenceFormatter - SourceLocation const* location = boost::get_error_info(_exception); + string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type); if (string const* description = boost::get_error_info(_exception)) message = ((_message.length() > 0) ? (_message + ":") : "") + *description; else message = _message; - Json::Value sourceLocation; - if (location && location->sourceName) - { - sourceLocation["file"] = *location->sourceName; - sourceLocation["start"] = location->start; - sourceLocation["end"] = location->end; - } - - return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation); + return formatError( + _warning, + _type, + _component, + message, + formattedMessage, + formatSourceLocation(boost::get_error_info(_exception)), + formatSecondarySourceLocation(boost::get_error_info(_exception)) + ); } -set requestedContractNames(Json::Value const& _outputSelection) +map> requestedContractNames(Json::Value const& _outputSelection) { - set names; + map> contracts; for (auto const& sourceName: _outputSelection.getMemberNames()) { + string key = (sourceName == "*") ? "" : sourceName; for (auto const& contractName: _outputSelection[sourceName].getMemberNames()) { - /// Consider the "all sources" shortcuts as requesting everything. - if (contractName == "*" || contractName == "") - return set(); - names.insert((sourceName == "*" ? "" : sourceName) + ":" + contractName); + string value = (contractName == "*") ? "" : contractName; + contracts[key].insert(value); } } - return names; + return contracts; } /// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content. @@ -123,22 +157,19 @@ bool hashMatchesContent(string const& _hash, string const& _content) } } -StringMap createSourceList(Json::Value const& _input) -{ - StringMap sources; - Json::Value const& jsonSources = _input["sources"]; - if (jsonSources.isObject()) - for (auto const& sourceName: jsonSources.getMemberNames()) - sources[sourceName] = jsonSources[sourceName]["content"].asString(); - return sources; -} - -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesExperimental) { + static set experimental{"ir", "irOptimized", "wast", "ewasm", "ewasm.wast"}; for (auto const& artifact: _outputSelection) /// @TODO support sub-matching, e.g "evm" matches "evm.assembly" - if (artifact == "*" || artifact == _artifact) + if (artifact == _artifact) return true; + else if (artifact == "*") + { + // "ir", "irOptimized", "wast" and "ewasm.wast" can only be matched by "*" if activated. + if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental) + return true; + } return false; } @@ -155,7 +186,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _art /// /// @TODO optimise this. Perhaps flatten the structure upfront. /// -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesExperimental) { if (!_outputSelection.isObject()) return false; @@ -172,7 +203,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil if ( _outputSelection[file].isMember(contract) && _outputSelection[file][contract].isArray() && - isArtifactRequested(_outputSelection[file][contract], _artifact) + isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesExperimental) ) return true; } @@ -180,14 +211,75 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil return false; } -bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts) +bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector const& _artifacts, bool _wildcardMatchesExperimental) { for (auto const& artifact: _artifacts) - if (isArtifactRequested(_outputSelection, _file, _contract, artifact)) + if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesExperimental)) return true; return false; } +/// @returns true if any binary was requested, i.e. we actually have to perform compilation. +bool isBinaryRequested(Json::Value const& _outputSelection) +{ + if (!_outputSelection.isObject()) + return false; + + // This does not inculde "evm.methodIdentifiers" on purpose! + static vector const outputsThatRequireBinaries{ + "*", + "ir", "irOptimized", + "wast", "wasm", "ewasm.wast", "ewasm.wasm", + "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", + "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences", + "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", + "evm.bytecode.linkReferences", + "evm.gasEstimates", "evm.legacyAssembly", "evm.assembly" + }; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& output: outputsThatRequireBinaries) + if (isArtifactRequested(requests, output, false)) + return true; + return false; +} + +/// @returns true if any eWasm code was requested. Note that as an exception, '*' does not +/// yet match "ewasm.wast" or "ewasm" +bool isEWasmRequested(Json::Value const& _outputSelection) +{ + if (!_outputSelection.isObject()) + return false; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& request: requests) + if (request == "ewasm" || request == "ewasm.wast") + return true; + + return false; +} + +/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not +/// yet match "ir" or "irOptimized" +bool isIRRequested(Json::Value const& _outputSelection) +{ + if (isEWasmRequested(_outputSelection)) + return true; + + if (!_outputSelection.isObject()) + return false; + + for (auto const& fileRequests: _outputSelection) + for (auto const& requests: fileRequests) + for (auto const& request: requests) + if (request == "ir" || request == "irOptimized") + return true; + + return false; +} + Json::Value formatLinkReferences(std::map const& linkReferences) { Json::Value ret(Json::objectValue); @@ -219,23 +311,196 @@ Json::Value collectEVMObject(eth::LinkerObject const& _object, string const* _so { Json::Value output = Json::objectValue; output["object"] = _object.toHex(); - output["opcodes"] = solidity::disassemble(_object.bytecode); + output["opcodes"] = dev::eth::disassemble(_object.bytecode); output["sourceMap"] = _sourceMap ? *_sourceMap : ""; output["linkReferences"] = formatLinkReferences(_object.linkReferences); return output; } +boost::optional checkKeys(Json::Value const& _input, set const& _keys, string const& _name) +{ + if (!!_input && !_input.isObject()) + return formatFatalError("JSONError", "\"" + _name + "\" must be an object"); + + for (auto const& member: _input.getMemberNames()) + if (!_keys.count(member)) + return formatFatalError("JSONError", "Unknown key \"" + member + "\""); + + return boost::none; +} + +boost::optional checkRootKeys(Json::Value const& _input) +{ + static set keys{"auxiliaryInput", "language", "settings", "sources"}; + return checkKeys(_input, keys, "root"); +} + +boost::optional checkSourceKeys(Json::Value const& _input, string const& _name) +{ + static set keys{"content", "keccak256", "urls"}; + return checkKeys(_input, keys, "sources." + _name); +} + +boost::optional checkAuxiliaryInputKeys(Json::Value const& _input) +{ + static set keys{"smtlib2responses"}; + return checkKeys(_input, keys, "auxiliaryInput"); +} + +boost::optional checkSettingsKeys(Json::Value const& _input) +{ + static set keys{"parserErrorRecovery", "evmVersion", "libraries", "metadata", "optimizer", "outputSelection", "remappings"}; + return checkKeys(_input, keys, "settings"); +} + +boost::optional checkOptimizerKeys(Json::Value const& _input) +{ + static set keys{"details", "enabled", "runs"}; + return checkKeys(_input, keys, "settings.optimizer"); +} + +boost::optional checkOptimizerDetailsKeys(Json::Value const& _input) +{ + static set keys{"peephole", "jumpdestRemover", "orderLiterals", "deduplicate", "cse", "constantOptimizer", "yul", "yulDetails"}; + return checkKeys(_input, keys, "settings.optimizer.details"); +} + +boost::optional checkOptimizerDetail(Json::Value const& _details, std::string const& _name, bool& _setting) +{ + if (_details.isMember(_name)) + { + if (!_details[_name].isBool()) + return formatFatalError("JSONError", "\"settings.optimizer.details." + _name + "\" must be Boolean"); + _setting = _details[_name].asBool(); + } + return {}; +} + +boost::optional checkMetadataKeys(Json::Value const& _input) +{ + if (_input.isObject() && _input.isMember("useLiteralContent") && !_input["useLiteralContent"].isBool()) + return formatFatalError("JSONError", "\"settings.metadata.useLiteralContent\" must be Boolean"); + static set keys{"useLiteralContent"}; + return checkKeys(_input, keys, "settings.metadata"); } -Json::Value StandardCompiler::compileInternal(Json::Value const& _input) +boost::optional checkOutputSelection(Json::Value const& _outputSelection) +{ + if (!!_outputSelection && !_outputSelection.isObject()) + return formatFatalError("JSONError", "\"settings.outputSelection\" must be an object"); + + for (auto const& sourceName: _outputSelection.getMemberNames()) + { + auto const& sourceVal = _outputSelection[sourceName]; + + if (!sourceVal.isObject()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + sourceName + "\" must be an object" + ); + + for (auto const& contractName: sourceVal.getMemberNames()) + { + auto const& contractVal = sourceVal[contractName]; + + if (!contractVal.isArray()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + + sourceName + + "." + + contractName + + "\" must be a string array" + ); + + for (auto const& output: contractVal) + if (!output.isString()) + return formatFatalError( + "JSONError", + "\"settings.outputSelection." + + sourceName + + "." + + contractName + + "\" must be a string array" + ); + } + } + + return boost::none; +} +/// Validates the optimizer settings and returns them in a parsed object. +/// On error returns the json-formatted error message. +boost::variant parseOptimizerSettings(Json::Value const& _jsonInput) { - m_compilerStack.reset(false); + if (auto result = checkOptimizerKeys(_jsonInput)) + return *result; + + OptimiserSettings settings = OptimiserSettings::none(); + + if (_jsonInput.isMember("enabled")) + { + if (!_jsonInput["enabled"].isBool()) + return formatFatalError("JSONError", "The \"enabled\" setting must be a Boolean."); + + settings = _jsonInput["enabled"].asBool() ? OptimiserSettings::standard() : OptimiserSettings::minimal(); + } + + if (_jsonInput.isMember("runs")) + { + if (!_jsonInput["runs"].isUInt()) + return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); + settings.expectedExecutionsPerDeployment = _jsonInput["runs"].asUInt(); + } + + if (_jsonInput.isMember("details")) + { + Json::Value const& details = _jsonInput["details"]; + if (auto result = checkOptimizerDetailsKeys(details)) + return *result; + + if (auto error = checkOptimizerDetail(details, "peephole", settings.runPeephole)) + return *error; + if (auto error = checkOptimizerDetail(details, "jumpdestRemover", settings.runJumpdestRemover)) + return *error; + if (auto error = checkOptimizerDetail(details, "orderLiterals", settings.runOrderLiterals)) + return *error; + if (auto error = checkOptimizerDetail(details, "deduplicate", settings.runDeduplicate)) + return *error; + if (auto error = checkOptimizerDetail(details, "cse", settings.runCSE)) + return *error; + if (auto error = checkOptimizerDetail(details, "constantOptimizer", settings.runConstantOptimiser)) + return *error; + if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) + return *error; + if (settings.runYulOptimiser) + settings.optimizeStackAllocation = true; + if (details.isMember("yulDetails")) + { + if (!settings.runYulOptimiser) + return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled."); + + if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails")) + return *result; + if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) + return *error; + } + } + return { std::move(settings) }; +} + +} + +boost::variant StandardCompiler::parseInput(Json::Value const& _input) +{ + InputsAndSettings ret; if (!_input.isObject()) return formatFatalError("JSONError", "Input is not a JSON object."); - if (_input["language"] != "Solidity") - return formatFatalError("JSONError", "Only \"Solidity\" is supported as a language."); + if (auto result = checkRootKeys(_input)) + return *result; + + ret.language = _input["language"].asString(); Json::Value const& sources = _input["sources"]; @@ -245,14 +510,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (sources.empty()) return formatFatalError("JSONError", "No input sources specified."); - Json::Value errors = Json::arrayValue; + ret.errors = Json::arrayValue; for (auto const& sourceName: sources.getMemberNames()) { string hash; - if (!sources[sourceName].isObject()) - return formatFatalError("JSONError", "Source input is not a JSON object."); + if (auto result = checkSourceKeys(sources[sourceName], sourceName)) + return *result; if (sources[sourceName]["keccak256"].isString()) hash = sources[sourceName]["keccak256"].asString(); @@ -261,14 +526,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) { string content = sources[sourceName]["content"].asString(); if (!hash.empty() && !hashMatchesContent(hash, content)) - errors.append(formatError( + ret.errors.append(formatError( false, "IOError", "general", "Mismatch between content and supplied hash for \"" + sourceName + "\"" )); else - m_compilerStack.addSource(sourceName, content); + ret.sources[sourceName] = content; } else if (sources[sourceName]["urls"].isArray()) { @@ -286,7 +551,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) if (result.success) { if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage)) - errors.append(formatError( + ret.errors.append(formatError( false, "IOError", "general", @@ -294,7 +559,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); else { - m_compilerStack.addSource(sourceName, result.responseOrErrorMessage); + ret.sources[sourceName] = result.responseOrErrorMessage; found = true; break; } @@ -306,7 +571,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) for (auto const& failure: failures) { /// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors. - errors.append(formatError( + ret.errors.append(formatError( found ? true : false, "IOError", "general", @@ -318,56 +583,90 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) return formatFatalError("JSONError", "Invalid input source specified."); } + Json::Value const& auxInputs = _input["auxiliaryInput"]; + + if (auto result = checkAuxiliaryInputKeys(auxInputs)) + return *result; + + if (!!auxInputs) + { + Json::Value const& smtlib2Responses = auxInputs["smtlib2responses"]; + if (!!smtlib2Responses) + { + if (!smtlib2Responses.isObject()) + return formatFatalError("JSONError", "\"auxiliaryInput.smtlib2responses\" must be an object."); + + for (auto const& hashString: smtlib2Responses.getMemberNames()) + { + h256 hash; + try + { + hash = h256(hashString); + } + catch (dev::BadHexCharacter const&) + { + return formatFatalError("JSONError", "Invalid hex encoding of SMTLib2 auxiliary input."); + } + + if (!smtlib2Responses[hashString].isString()) + return formatFatalError( + "JSONError", + "\"smtlib2Responses." + hashString + "\" must be a string." + ); + + ret.smtLib2Responses[hash] = smtlib2Responses[hashString].asString(); + } + } + } + Json::Value const& settings = _input.get("settings", Json::Value()); + 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()) return formatFatalError("JSONError", "evmVersion must be a string."); - boost::optional version = EVMVersion::fromString(settings["evmVersion"].asString()); + boost::optional version = langutil::EVMVersion::fromString(settings["evmVersion"].asString()); if (!version) return formatFatalError("JSONError", "Invalid EVM version requested."); - m_compilerStack.setEVMVersion(*version); + ret.evmVersion = *version; } - vector remappings; + if (settings.isMember("remappings") && !settings["remappings"].isArray()) + return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings."); + for (auto const& remapping: settings.get("remappings", Json::Value())) { if (!remapping.isString()) - return formatFatalError("JSONError", "Remapping entry must be a string."); + return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings"); if (auto r = CompilerStack::parseRemapping(remapping.asString())) - remappings.emplace_back(std::move(*r)); + ret.remappings.emplace_back(std::move(*r)); else return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\""); } - m_compilerStack.setRemappings(remappings); if (settings.isMember("optimizer")) { - Json::Value optimizerSettings = settings["optimizer"]; - if (optimizerSettings.isMember("enabled")) - { - if (!optimizerSettings["enabled"].isBool()) - return formatFatalError("JSONError", "The \"enabled\" setting must be a boolean."); - - bool const optimize = optimizerSettings["enabled"].asBool(); - unsigned optimizeRuns = 200; - if (optimizerSettings.isMember("runs")) - { - if (!optimizerSettings["runs"].isUInt()) - return formatFatalError("JSONError", "The \"runs\" setting must be an unsigned number."); - optimizeRuns = optimizerSettings["runs"].asUInt(); - } - m_compilerStack.setOptimiserSettings(optimize, optimizeRuns); - } + auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]); + if (optimiserSettings.type() == typeid(Json::Value)) + return boost::get(std::move(optimiserSettings)); // was an error + else + ret.optimiserSettings = boost::get(std::move(optimiserSettings)); } if (settings.isMember("contractStandard")) { string const contractStandard = settings["contractStandard"].asString(); m_compilerStack.setContractStandard(contractStandard); } - - map libraries; Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue)); if (!jsonLibraries.isObject()) return formatFatalError("JSONError", "\"libraries\" is not a JSON object."); @@ -397,7 +696,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) try { // @TODO use libraries only for the given source - libraries[library] = h160(address); + ret.libraries[library] = h160(address); } catch (dev::BadHexCharacter const&) { @@ -408,21 +707,56 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) } } } - m_compilerStack.setLibraries(libraries); Json::Value metadataSettings = settings.get("metadata", Json::Value()); - m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool()); + + if (auto result = checkMetadataKeys(metadataSettings)) + return *result; + + ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool(); Json::Value outputSelection = settings.get("outputSelection", Json::Value()); - m_compilerStack.setRequestedContractNames(requestedContractNames(outputSelection)); - auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compilerStack.scanner(_sourceName); }; + if (auto jsonError = checkOutputSelection(outputSelection)) + return *jsonError; + + ret.outputSelection = std::move(outputSelection); + + return { std::move(ret) }; +} + +Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings) +{ + CompilerStack compilerStack(m_readFile); + + StringMap sourceList = std::move(_inputsAndSettings.sources); + compilerStack.setSources(sourceList); + 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); + compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources); + compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection)); + + compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection)); + + compilerStack.enableEWasmGeneration(isEWasmRequested(_inputsAndSettings.outputSelection)); + + Json::Value errors = std::move(_inputsAndSettings.errors); + + bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection); try { - m_compilerStack.compile(); + if (binariesRequested) + compilerStack.compile(); + else + compilerStack.parseAndAnalyze(); - for (auto const& error: m_compilerStack.errors()) + for (auto const& error: compilerStack.errors()) { Error const& err = dynamic_cast(*error); @@ -431,8 +765,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) err.type() == Error::Type::Warning || err.type() == Error::Type::Info, err.typeName(), "general", - "", - scannerFromSourceName + "" )); } } @@ -444,8 +777,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, _error.typeName(), "general", - "Uncaught error: ", - scannerFromSourceName + "Uncaught error: " )); } /// This should not be leaked from compile(). @@ -465,8 +797,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "CompilerError", "general", - "Compiler error (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Compiler error (" + _exception.lineInfo() + ")" )); } catch (InternalCompilerError const& _exception) @@ -476,8 +807,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "InternalCompilerError", "general", - "Internal compiler error (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Internal compiler error (" + _exception.lineInfo() + ")" )); } catch (UnimplementedFeatureError const& _exception) @@ -487,8 +817,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) false, "UnimplementedFeatureError", "general", - "Unimplemented feature (" + _exception.lineInfo() + ")", - scannerFromSourceName + "Unimplemented feature (" + _exception.lineInfo() + ")" )); } catch (Exception const& _exception) @@ -500,6 +829,15 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) "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( @@ -510,33 +848,42 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) )); } - bool const analysisSuccess = m_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful; - bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful; + bool analysisPerformed = compilerStack.state() >= CompilerStack::State::AnalysisPerformed; + bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful; + + if (compilerStack.hasError() && !_inputsAndSettings.parserErrorRecovery) + analysisPerformed = false; /// Inconsistent state - stop here to receive error reports from users - if (!compilationSuccess && errors.empty()) + if (((binariesRequested && !compilationSuccess) || !analysisPerformed) && errors.empty()) return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); Json::Value output = Json::objectValue; if (errors.size() > 0) - output["errors"] = errors; + output["errors"] = std::move(errors); + + if (!compilerStack.unhandledSMTLib2Queries().empty()) + for (string const& query: compilerStack.unhandledSMTLib2Queries()) + output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query; + + bool const wildcardMatchesExperimental = false; output["sources"] = Json::objectValue; unsigned sourceIndex = 0; - for (string const& sourceName: analysisSuccess ? m_compilerStack.sourceNames() : vector()) + for (string const& sourceName: analysisPerformed ? compilerStack.sourceNames() : vector()) { Json::Value sourceResult = Json::objectValue; sourceResult["id"] = sourceIndex++; - if (isArtifactRequested(outputSelection, sourceName, "", "ast")) - sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); - if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST")) - sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName)); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesExperimental)) + sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesExperimental)) + sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName)); output["sources"][sourceName] = sourceResult; } Json::Value contractsOutput = Json::objectValue; - for (string const& contractName: compilationSuccess ? m_compilerStack.contractNames() : vector()) + for (string const& contractName: analysisPerformed ? compilerStack.contractNames() : vector()) { size_t colon = contractName.rfind(':'); solAssert(colon != string::npos, ""); @@ -545,66 +892,169 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) // ABI, documentation and metadata Json::Value contractData(Json::objectValue); - if (isArtifactRequested(outputSelection, file, name, "abi")) - contractData["abi"] = m_compilerStack.contractABI(contractName); - if (isArtifactRequested(outputSelection, file, name, "metadata")) - contractData["metadata"] = m_compilerStack.metadata(contractName); - if (isArtifactRequested(outputSelection, file, name, "userdoc")) - contractData["userdoc"] = m_compilerStack.natspecUser(contractName); - if (isArtifactRequested(outputSelection, file, name, "devdoc")) - contractData["devdoc"] = m_compilerStack.natspecDev(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesExperimental)) + contractData["abi"] = compilerStack.contractABI(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesExperimental)) + contractData["metadata"] = compilerStack.metadata(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesExperimental)) + contractData["userdoc"] = compilerStack.natspecUser(contractName); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental)) + contractData["devdoc"] = compilerStack.natspecDev(contractName); + + // IR + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental)) + contractData["ir"] = compilerStack.yulIR(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental)) + contractData["irOptimized"] = compilerStack.yulIROptimized(contractName); + + // eWasm + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wast", wildcardMatchesExperimental)) + contractData["ewasm"]["wast"] = compilerStack.eWasm(contractName); // EVM Json::Value evmData(Json::objectValue); - // @TODO: add ir - if (isArtifactRequested(outputSelection, file, name, "evm.assembly")) - evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input)); - if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly")) - evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input)); - if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers")) - evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName); - if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates")) - evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName); - - if (isArtifactRequested( - outputSelection, + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental)) + evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental)) + evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList); + if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental)) + evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName); + if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) + evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); + + if (compilationSuccess && isArtifactRequested( + _inputsAndSettings.outputSelection, file, name, - { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" } + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesExperimental )) evmData["bytecode"] = collectEVMObject( - m_compilerStack.object(contractName), - m_compilerStack.sourceMapping(contractName) + compilerStack.object(contractName), + compilerStack.sourceMapping(contractName) ); - if (isArtifactRequested( - outputSelection, + if (compilationSuccess && isArtifactRequested( + _inputsAndSettings.outputSelection, file, name, - { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" } + { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }, + wildcardMatchesExperimental )) evmData["deployedBytecode"] = collectEVMObject( - m_compilerStack.runtimeObject(contractName), - m_compilerStack.runtimeSourceMapping(contractName) + compilerStack.runtimeObject(contractName), + compilerStack.runtimeSourceMapping(contractName) ); - contractData["evm"] = evmData; + if (!evmData.empty()) + contractData["evm"] = evmData; + + if (!contractData.empty()) + { + if (!contractsOutput.isMember(file)) + contractsOutput[file] = Json::objectValue; + contractsOutput[file][name] = contractData; + } + } + if (!contractsOutput.empty()) + output["contracts"] = contractsOutput; + + return output; +} + - if (!contractsOutput.isMember(file)) - contractsOutput[file] = Json::objectValue; +Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings) +{ + if (_inputsAndSettings.sources.size() != 1) + return formatFatalError("JSONError", "Yul mode only supports exactly one input file."); + if (!_inputsAndSettings.smtLib2Responses.empty()) + return formatFatalError("JSONError", "Yul mode does not support smtlib2responses."); + if (!_inputsAndSettings.remappings.empty()) + return formatFatalError("JSONError", "Field \"settings.remappings\" cannot be used for Yul."); + if (!_inputsAndSettings.libraries.empty()) + return formatFatalError("JSONError", "Field \"settings.libraries\" cannot be used for Yul."); + + Json::Value output = Json::objectValue; + + AssemblyStack stack( + _inputsAndSettings.evmVersion, + AssemblyStack::Language::StrictAssembly, + _inputsAndSettings.optimiserSettings + ); + string const& sourceName = _inputsAndSettings.sources.begin()->first; + string const& sourceContents = _inputsAndSettings.sources.begin()->second; - contractsOutput[file][name] = contractData; + // Inconsistent state - stop here to receive error reports from users + if (!stack.parseAndAnalyze(sourceName, sourceContents) && stack.errors().empty()) + return formatFatalError("InternalCompilerError", "No error reported, but compilation failed."); + + if (!stack.errors().empty()) + { + Json::Value errors = Json::arrayValue; + for (auto const& error: stack.errors()) + { + auto err = dynamic_pointer_cast(error); + + errors.append(formatErrorWithException( + *error, + err->type() == Error::Type::Warning, + err->typeName(), + "general", + "" + )); + } + output["errors"] = errors; + return output; } - output["contracts"] = contractsOutput; + + // TODO: move this warning to AssemblyStack + output["errors"] = Json::arrayValue; + output["errors"].append(formatError(true, "Warning", "general", "Yul is still experimental. Please use the output with care.")); + + string contractName = stack.parserResult()->name.str(); + + bool const wildcardMatchesExperimental = true; + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental)) + output["contracts"][sourceName][contractName]["ir"] = stack.print(); + + stack.optimize(); + + MachineAssemblyObject object = stack.assemble(AssemblyStack::Machine::EVM); + + if (isArtifactRequested( + _inputsAndSettings.outputSelection, + sourceName, + contractName, + { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }, + wildcardMatchesExperimental + )) + output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr); + + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental)) + output["contracts"][sourceName][contractName]["irOptimized"] = stack.print(); + if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental)) + output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly; return output; } + Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept { + YulStringRepository::reset(); + try { - return compileInternal(_input); + auto parsed = parseInput(_input); + if (parsed.type() == typeid(Json::Value)) + return boost::get(std::move(parsed)); + InputsAndSettings settings = boost::get(std::move(parsed)); + if (settings.language == "Solidity") + return compileSolidity(std::move(settings)); + else if (settings.language == "Yul") + return compileYul(std::move(settings)); + else + return formatFatalError("JSONError", "Only \"Solidity\" or \"Yul\" is supported as a language."); } catch (Json::LogicError const& _exception) { @@ -616,11 +1066,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept } catch (Exception const& _exception) { - return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception)); + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::diagnostic_information(_exception)); } catch (...) { - return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal"); + return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile"); } } diff --git a/libsolidity/interface/StandardCompiler.h b/libsolidity/interface/StandardCompiler.h index fc9c3a594..9d0320abb 100644 --- a/libsolidity/interface/StandardCompiler.h +++ b/libsolidity/interface/StandardCompiler.h @@ -24,6 +24,9 @@ #include +#include +#include + namespace dev { @@ -40,8 +43,8 @@ class StandardCompiler: boost::noncopyable /// Creates a new StandardCompiler. /// @param _readFile callback to used to read files for import statements. Must return /// and must not emit exceptions. - explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()) - : m_compilerStack(_readFile), m_readFile(_readFile) + explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()): + m_readFile(_readFile) { } @@ -53,9 +56,28 @@ class StandardCompiler: boost::noncopyable std::string compile(std::string const& _input) noexcept; private: - Json::Value compileInternal(Json::Value const& _input); + struct InputsAndSettings + { + std::string language; + Json::Value errors; + bool parserErrorRecovery = false; + std::map sources; + std::map smtLib2Responses; + langutil::EVMVersion evmVersion; + std::vector remappings; + OptimiserSettings optimiserSettings = OptimiserSettings::minimal(); + std::map libraries; + bool metadataLiteralSources = false; + Json::Value outputSelection; + }; + + /// Parses the input json (and potentially invokes the read callback) and either returns + /// it in condensed form or an error as a json object. + boost::variant parseInput(Json::Value const& _input); + + Json::Value compileSolidity(InputsAndSettings _inputsAndSettings); + Json::Value compileYul(InputsAndSettings _inputsAndSettings); - CompilerStack m_compilerStack; ReadCallback::Callback m_readFile; }; diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index a18c1c25d..475f9fe1b 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -21,11 +21,12 @@ */ #include -#include + +#include #include #include -#include #include +#include using namespace dev; using namespace dev::solidity; @@ -97,3 +98,10 @@ bytes dev::solidity::binaryVersion(const std::string& versionString) return ret; } +bytes const dev::solidity::VersionCompactBytes = { + LITY_PROJECT_VERSION_MAJOR, + LITY_PROJECT_VERSION_MINOR, + LITY_PROJECT_VERSION_PATCH +}; + +bool const dev::solidity::VersionIsRelease = string(LITY_VERSION_PRERELEASE).empty(); diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h index e3a444183..08e5e5b89 100644 --- a/libsolidity/interface/Version.h +++ b/libsolidity/interface/Version.h @@ -22,8 +22,8 @@ #pragma once -#include #include +#include namespace dev { @@ -43,6 +43,8 @@ extern std::string const LityVersionStringStrict; bytes binaryVersion(const std::string& versionString); bytes solcBinaryVersion(); bytes lityBinaryVersion(); +extern bytes const VersionCompactBytes; +extern bool const VersionIsRelease; } } diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp index d9588e5ca..0ff69f344 100644 --- a/libsolidity/parsing/DocStringParser.cpp +++ b/libsolidity/parsing/DocStringParser.cpp @@ -1,16 +1,34 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ #include -#include -#include +#include +#include +#include + +#include #include -#include using namespace std; using namespace dev; +using namespace langutil; using namespace dev::solidity; - namespace { @@ -22,12 +40,19 @@ string::const_iterator skipLineOrEOS( return (_nlPos == _end) ? _end : ++_nlPos; } -string::const_iterator firstSpaceOrTab( +string::const_iterator firstNonIdentifier( string::const_iterator _pos, string::const_iterator _end ) { - return boost::range::find_first_of(make_pair(_pos, _end), " \t"); + auto currPos = _pos; + if (currPos == _pos && isIdentifierStart(*currPos)) + { + currPos++; + while (currPos != _end && isIdentifierPart(*currPos)) + currPos++; + } + return currPos; } string::const_iterator firstWhitespaceOrNewline( @@ -118,7 +143,7 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) appendError("No param name given"); return _end; } - auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); + auto nameEndPos = firstNonIdentifier(nameStartPos, _end); auto paramName = string(nameStartPos, nameEndPos); auto descStartPos = skipWhitespace(nameEndPos, _end); diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h index 5f2819cc5..671a2f34a 100644 --- a/libsolidity/parsing/DocStringParser.h +++ b/libsolidity/parsing/DocStringParser.h @@ -22,22 +22,25 @@ #pragma once -#include #include +#include + +namespace langutil +{ +class ErrorReporter; +} namespace dev { namespace solidity { -class ErrorReporter; - class DocStringParser { public: /// Parse the given @a _docString and stores the parsed components internally. /// @returns false on error and appends the error to @a _errors. - bool parse(std::string const& _docString, ErrorReporter& _errorReporter); + bool parse(std::string const& _docString, langutil::ErrorReporter& _errorReporter); std::multimap const& tags() const { return m_docTags; } @@ -63,7 +66,7 @@ class DocStringParser /// Mapping tag name -> content. std::multimap m_docTags; DocTag* m_lastTag = nullptr; - ErrorReporter* m_errorReporter = nullptr; + langutil::ErrorReporter* m_errorReporter = nullptr; bool m_errorsOccurred = false; }; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 90d8f6e6a..f9d981a27 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -20,15 +20,21 @@ * Solidity parser. */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include using namespace std; +using namespace langutil; namespace dev { @@ -41,9 +47,9 @@ class Parser::ASTNodeFactory { public: explicit ASTNodeFactory(Parser const& _parser): - m_parser(_parser), m_location(_parser.position(), -1, _parser.sourceName()) {} + m_parser(_parser), m_location{_parser.position(), -1, _parser.source()} {} ASTNodeFactory(Parser const& _parser, ASTPointer const& _childNode): - m_parser(_parser), m_location(_childNode->location()) {} + m_parser(_parser), m_location{_childNode->location()} {} void markEndPosition() { m_location.end = m_parser.endPosition(); } void setLocation(SourceLocation const& _location) { m_location = _location; } @@ -54,12 +60,14 @@ class Parser::ASTNodeFactory template ASTPointer createNode(Args&& ... _args) { - solAssert(m_location.sourceName, ""); + solAssert(m_location.source, ""); if (m_location.end < 0) markEndPosition(); return make_shared(m_location, std::forward(_args)...); } + SourceLocation const& location() const noexcept { return m_location; } + private: Parser const& m_parser; SourceLocation m_location; @@ -103,6 +111,24 @@ ASTPointer Parser::parse(shared_ptr const& _scanner) } } +void Parser::parsePragmaVersion(SourceLocation const& _location, vector const& _tokens, vector const& _literals) +{ + SemVerMatchExpressionParser parser(_tokens, _literals); + auto matchExpression = parser.parse(); + static SemVerVersion const currentVersion{string(VersionString)}; + // FIXME: only match for major version incompatibility + if (!matchExpression.matches(currentVersion)) + // If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(), + // so we don't need to report anything here. + if (!m_parserErrorRecovery) + m_errorReporter.fatalParserError( + _location, + "Source file requires different compiler version (current compiler is " + + string(VersionString) + " - note that nightly builds are considered to be " + "strictly less than the released version" + ); +} + ASTPointer Parser::parsePragmaDirective() { RecursionGuard recursionGuard(*this); @@ -132,6 +158,16 @@ ASTPointer Parser::parsePragmaDirective() while (m_scanner->currentToken() != Token::Semicolon && m_scanner->currentToken() != Token::EOS); nodeFactory.markEndPosition(); expectToken(Token::Semicolon); + + if (literals.size() >= 2 && literals[0] == "solidity") + { + parsePragmaVersion( + nodeFactory.location(), + vector(tokens.begin() + 1, tokens.end()), + vector(literals.begin() + 1, literals.end()) + ); + } + return nodeFactory.createNode(tokens, literals); } @@ -145,7 +181,7 @@ ASTPointer Parser::parseImportDirective() expectToken(Token::Import); ASTPointer path; ASTPointer unitAlias = make_shared(); - vector, ASTPointer>> symbolAliases; + ImportDirective::SymbolAliasList symbolAliases; if (m_scanner->currentToken() == Token::StringLiteral) { @@ -163,14 +199,16 @@ ASTPointer Parser::parseImportDirective() m_scanner->next(); while (true) { - ASTPointer id = parseIdentifier(); ASTPointer alias; + SourceLocation aliasLocation = SourceLocation{position(), endPosition(), source()}; + ASTPointer id = parseIdentifier(); if (m_scanner->currentToken() == Token::As) { expectToken(Token::As); + aliasLocation = SourceLocation{position(), endPosition(), source()}; alias = expectIdentifierToken(); } - symbolAliases.push_back(make_pair(move(id), move(alias))); + symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation}); if (m_scanner->currentToken() != Token::Comma) break; m_scanner->next(); @@ -194,6 +232,8 @@ ASTPointer Parser::parseImportDirective() fatalParserError("Expected import path."); path = getLiteralAndAdvance(); } + if (path->empty()) + fatalParserError("Import path cannot be empty."); nodeFactory.markEndPosition(); expectToken(Token::Semicolon); return nodeFactory.createNode(path, unitAlias, move(symbolAliases)); @@ -224,59 +264,79 @@ 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 if (currentTokenValue == Token::Rule) subNodes.push_back(parseRule()); 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, @@ -935,10 +995,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); } @@ -946,90 +1022,112 @@ ASTPointer Parser::parseStatement() { RecursionGuard recursionGuard(*this); ASTPointer docString; - if (m_scanner->currentCommentLiteral() != "") - docString = make_shared(m_scanner->currentCommentLiteral()); ASTPointer statement; - switch (m_scanner->currentToken()) - { - 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: + try { - 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() == "_") + 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: { - statement = ASTNodeFactory(*this).createNode(docString); + 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::FireAllRules: + statement = ASTNodeFactory(*this).createNode(docString); m_scanner->next(); - } - else + 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; - case Token::FireAllRules: - statement = ASTNodeFactory(*this).createNode(docString); - m_scanner->next(); - 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; } ASTPointer Parser::parseInlineAssembly(ASTPointer const& _docString) { RecursionGuard recursionGuard(*this); - ASTNodeFactory nodeFactory(*this); + SourceLocation location{position(), -1, source()}; + expectToken(Token::Assembly); + yul::Dialect const& dialect = yul::EVMDialect::looseAssemblyForEVM(m_evmVersion); if (m_scanner->currentToken() == Token::StringLiteral) { if (m_scanner->currentLiteral() != "evmasm") fatalParserError("Only \"evmasm\" supported."); + // This can be used in the future to set the dialect. m_scanner->next(); } - assembly::Parser asmParser(m_errorReporter); - shared_ptr block = asmParser.parse(m_scanner, true); - nodeFactory.markEndPosition(); - return nodeFactory.createNode(_docString, block); + yul::Parser asmParser(m_errorReporter, dialect); + shared_ptr block = asmParser.parse(m_scanner, true); + if (block == nullptr) + BOOST_THROW_EXCEPTION(FatalError()); + + location.end = block->location.end; + return make_shared(location, _docString, dialect, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) @@ -1548,6 +1646,12 @@ ASTPointer Parser::parsePrimaryExpression() nodeFactory.markEndPosition(); expression = nodeFactory.createNode(getLiteralAndAdvance()); break; + case Token::Type: + // Inside expressions "type" is the name of a special, globally-available function. + nodeFactory.markEndPosition(); + m_scanner->next(); + expression = nodeFactory.createNode(make_shared("type")); + break; case Token::LParen: case Token::LBrack: { @@ -1579,8 +1683,8 @@ ASTPointer Parser::parsePrimaryExpression() expression = nodeFactory.createNode(components, isArray); break; } - case Token::IllegalHex: - fatalParserError("Expected even number of hex-nibbles within double-quotes."); + case Token::Illegal: + fatalParserError(to_string(m_scanner->currentError())); break; default: if (TokenTraits::isElementaryTypeName(token)) @@ -1714,7 +1818,7 @@ Parser::IndexAccessedPath Parser::parseIndexAccessedPath() index = parseExpression(); SourceLocation indexLocation = iap.path.front()->location(); indexLocation.end = endPosition(); - iap.indices.push_back(make_pair(index, indexLocation)); + iap.indices.emplace_back(index, indexLocation); expectToken(Token::RBrack); } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 0a3bc32ec..646588a56 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -1,18 +1,18 @@ /* - This file is part of solidity. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with solidity. If not, see . */ /** * @author Christian @@ -23,28 +23,42 @@ #pragma once #include -#include +#include +#include + +namespace langutil +{ +class Scanner; +} namespace dev { namespace solidity { -class Scanner; - -class Parser: public ParserBase +class Parser: public langutil::ParserBase { public: - explicit Parser(ErrorReporter& _errorReporter): ParserBase(_errorReporter) {} + explicit Parser( + langutil::ErrorReporter& _errorReporter, + langutil::EVMVersion _evmVersion, + bool _errorRecovery = false + ): + ParserBase(_errorReporter, _errorRecovery), + m_evmVersion(_evmVersion) + {} - ASTPointer parse(std::shared_ptr const& _scanner); + ASTPointer parse(std::shared_ptr const& _scanner); private: class ASTNodeFactory; struct VarDeclParserOptions { + // This is actually not needed, but due to a defect in the C++ standard, we have to. + // https://stackoverflow.com/questions/17430377 VarDeclParserOptions() {} + bool allowVar = false; bool isStateVariable = false; bool allowIndexed = false; @@ -68,6 +82,7 @@ class Parser: public ParserBase ///@{ ///@name Parsing functions for the AST nodes + void parsePragmaVersion(langutil::SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals); ASTPointer parsePragmaDirective(); ASTPointer parseImportDirective(); ContractDefinition::ContractKind parseContractKind(); @@ -82,7 +97,7 @@ class Parser: public ParserBase ASTPointer parseEnumDefinition(); ASTPointer parseEnumValue(); ASTPointer parseVariableDeclaration( - VarDeclParserOptions const& _options = VarDeclParserOptions(), + VarDeclParserOptions const& _options = {}, ASTPointer const& _lookAheadArrayType = ASTPointer() ); ASTPointer parseModifierDefinition(); @@ -96,7 +111,7 @@ class Parser: public ParserBase ASTPointer parseFunctionType(); ASTPointer parseMapping(); ASTPointer parseParameterList( - VarDeclParserOptions const& _options, + VarDeclParserOptions const& _options = {}, bool _allowEmpty = true ); ASTPointer parseBlock(ASTPointer const& _docString = {}); @@ -147,7 +162,7 @@ class Parser: public ParserBase struct IndexAccessedPath { std::vector> path; - std::vector, SourceLocation>> indices; + std::vector, langutil::SourceLocation>> indices; bool empty() const; }; @@ -179,6 +194,7 @@ class Parser: public ParserBase /// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier. bool m_insideModifier = false; + langutil::EVMVersion m_evmVersion; }; } diff --git a/libsolidity/parsing/ParserBase.cpp b/libsolidity/parsing/ParserBase.cpp deleted file mode 100644 index 1d4cb1e2d..000000000 --- a/libsolidity/parsing/ParserBase.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - 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 Christian - * @date 2016 - * Solidity parser shared functionality. - */ - -#include -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::solidity; - -std::shared_ptr const& ParserBase::sourceName() const -{ - return m_scanner->sourceName(); -} - -int ParserBase::position() const -{ - return m_scanner->currentLocation().start; -} - -int ParserBase::endPosition() const -{ - return m_scanner->currentLocation().end; -} - -Token ParserBase::currentToken() const -{ - return m_scanner->currentToken(); -} - -Token ParserBase::peekNextToken() const -{ - return m_scanner->peekNextToken(); -} - -std::string ParserBase::currentLiteral() const -{ - return m_scanner->currentLiteral(); -} - -Token ParserBase::advance() -{ - return m_scanner->next(); -} - -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)); - } - if (_advance) - m_scanner->next(); -} - -void ParserBase::increaseRecursionDepth() -{ - m_recursionDepth++; - if (m_recursionDepth >= 2560) - fatalParserError("Maximum recursion depth reached during parsing."); -} - -void ParserBase::decreaseRecursionDepth() -{ - solAssert(m_recursionDepth > 0, ""); - m_recursionDepth--; -} - -void ParserBase::parserError(string const& _description) -{ - m_errorReporter.parserError(SourceLocation(position(), endPosition(), sourceName()), _description); -} - -void ParserBase::fatalParserError(string const& _description) -{ - m_errorReporter.fatalParserError(SourceLocation(position(), endPosition(), sourceName()), _description); -} diff --git a/libsolidity/parsing/ParserBase.h b/libsolidity/parsing/ParserBase.h deleted file mode 100644 index e01f37d86..000000000 --- a/libsolidity/parsing/ParserBase.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - 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 Christian - * @date 2016 - * Solidity parser shared functionality. - */ - -#pragma once - -#include -#include - -namespace dev -{ -namespace solidity -{ - -class ErrorReporter; -class Scanner; - -class ParserBase -{ -public: - explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {} - - std::shared_ptr const& sourceName() const; - -protected: - /// Utility class that creates an error and throws an exception if the - /// recursion depth is too deep. - class RecursionGuard - { - public: - explicit RecursionGuard(ParserBase& _parser): m_parser(_parser) - { - m_parser.increaseRecursionDepth(); - } - ~RecursionGuard() { m_parser.decreaseRecursionDepth(); } - private: - ParserBase& m_parser; - }; - - /// Start position of the current token - int position() const; - /// End position of the current token - int endPosition() const; - - ///@{ - ///@name Helper functions - /// If current token value is not _value, throw exception otherwise advance token. - void expectToken(Token _value, bool _advance = true); - Token currentToken() const; - Token peekNextToken() const; - std::string currentLiteral() const; - Token advance(); - ///@} - - /// Increases the recursion depth and throws an exception if it is too deep. - void increaseRecursionDepth(); - void decreaseRecursionDepth(); - - /// Creates a @ref ParserError and annotates it with the current position and the - /// given @a _description. - void parserError(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); - - 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; -}; - -} -} diff --git a/libsolidity/parsing/Scanner.h b/libsolidity/parsing/Scanner.h deleted file mode 100644 index 14eeb66e7..000000000 --- a/libsolidity/parsing/Scanner.h +++ /dev/null @@ -1,249 +0,0 @@ -/* - 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 . - - This file is derived from the file "scanner.h", which was part of the - V8 project. The original copyright header follows: - - Copyright 2006-2012, the V8 project authors. All rights reserved. - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -/** - * @author Christian - * @date 2014 - * Solidity scanner. - */ - -#pragma once - -#include -#include -#include -#include - -namespace dev -{ -namespace solidity -{ - - -class AstRawString; -class AstValueFactory; -class ParserRecorder; - -class CharStream -{ -public: - CharStream(): m_position(0) {} - explicit CharStream(std::string const& _source): m_source(_source), m_position(0) {} - int position() const { return m_position; } - bool isPastEndOfInput(size_t _charsForward = 0) const { return (m_position + _charsForward) >= m_source.size(); } - char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; } - char advanceAndGet(size_t _chars = 1); - char rollback(size_t _amount); - - void reset() { m_position = 0; } - - std::string const& source() const { return m_source; } - - ///@{ - ///@name Error printing helper functions - /// Functions that help pretty-printing parse errors - /// Do only use in error cases, they are quite expensive. - std::string lineAtPosition(int _position) const; - std::tuple translatePositionToLineColumn(int _position) const; - ///@} - -private: - std::string m_source; - size_t m_position; -}; - - - -class Scanner -{ - friend class LiteralScope; -public: - - explicit Scanner(CharStream const& _source = CharStream(), std::string const& _sourceName = "") { reset(_source, _sourceName); } - - std::string source() const { return m_source.source(); } - - /// Resets the scanner as if newly constructed with _source and _sourceName as input. - void reset(CharStream const& _source, std::string const& _sourceName); - /// Resets scanner to the start of input. - void reset(); - - /// @returns the next token and advances input - Token next(); - - ///@{ - ///@name Information about the current token - - /// @returns the current token - Token currentToken() const - { - return m_currentToken.token; - } - ElementaryTypeNameToken currentElementaryTypeNameToken() const - { - unsigned firstSize; - unsigned secondSize; - std::tie(firstSize, secondSize) = m_currentToken.extendedTokenInfo; - return ElementaryTypeNameToken(m_currentToken.token, firstSize, secondSize); - } - - SourceLocation currentLocation() const { return m_currentToken.location; } - std::string const& currentLiteral() const { return m_currentToken.literal; } - std::tuple const& currentTokenInfo() const { return m_currentToken.extendedTokenInfo; } - ///@} - - ///@{ - ///@name Information about the current comment token - - SourceLocation currentCommentLocation() const { return m_skippedComment.location; } - std::string const& currentCommentLiteral() const { return m_skippedComment.literal; } - /// Called by the parser during FunctionDefinition parsing to clear the current comment - void clearCurrentCommentLiteral() { m_skippedComment.literal.clear(); } - - ///@} - - ///@{ - ///@name Information about the next token - - /// @returns the next token without advancing input. - Token peekNextToken() const { return m_nextToken.token; } - SourceLocation peekLocation() const { return m_nextToken.location; } - std::string const& peekLiteral() const { return m_nextToken.literal; } - ///@} - - std::shared_ptr const& sourceName() const { return m_sourceName; } - - ///@{ - ///@name Error printing helper functions - /// Functions that help pretty-printing parse errors - /// Do only use in error cases, they are quite expensive. - std::string lineAtPosition(int _position) const { return m_source.lineAtPosition(_position); } - std::tuple translatePositionToLineColumn(int _position) const { return m_source.translatePositionToLineColumn(_position); } - std::string sourceAt(SourceLocation const& _location) const - { - solAssert(!_location.isEmpty(), ""); - solAssert(m_sourceName && _location.sourceName, ""); - solAssert(*m_sourceName == *_location.sourceName, ""); - return m_source.source().substr(_location.start, _location.end - _location.start); - } - ///@} - -private: - /// Used for the current and look-ahead token and comments - struct TokenDesc - { - Token token; - SourceLocation location; - std::string literal; - std::tuple extendedTokenInfo; - }; - - ///@{ - ///@name Literal buffer support - inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); } - inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); } - inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } - void addUnicodeAsUTF8(unsigned codepoint); - ///@} - - bool advance() { m_char = m_source.advanceAndGet(); return !m_source.isPastEndOfInput(); } - void rollback(int _amount) { m_char = m_source.rollback(_amount); } - - inline Token selectToken(Token _tok) { advance(); return _tok; } - /// If the next character is _next, advance and return _then, otherwise return _else. - inline Token selectToken(char _next, Token _then, Token _else); - - bool scanHexByte(char& o_scannedByte); - bool scanUnicode(unsigned& o_codepoint); - - /// Scans a single Solidity token. - void scanToken(); - - /// Skips all whitespace and @returns true if something was skipped. - bool skipWhitespace(); - /// Skips all whitespace that are neither '\r' nor '\n'. - void skipWhitespaceExceptUnicodeLinebreak(); - Token skipSingleLineComment(); - Token skipMultiLineComment(); - - void scanDecimalDigits(); - Token scanNumber(char _charSeen = 0); - std::tuple scanIdentifierOrKeyword(); - - Token scanString(); - Token scanHexString(); - Token scanSingleLineDocComment(); - Token scanMultiLineDocComment(); - /// Scans a slash '/' and depending on the characters returns the appropriate token - Token scanSlash(); - - /// Scans an escape-sequence which is part of a string and adds the - /// decoded character to the current literal. Returns true if a pattern - /// is scanned. - bool scanEscape(); - - /// @returns true iff we are currently positioned at a unicode line break. - bool isUnicodeLinebreak(); - - /// Return the current source position. - int sourcePos() const { return m_source.position(); } - bool isSourcePastEndOfInput() const { return m_source.isPastEndOfInput(); } - - TokenDesc m_skippedComment; // desc for current skipped comment - TokenDesc m_nextSkippedComment; // desc for next skipped comment - - TokenDesc m_currentToken; // desc for current token (as returned by Next()) - TokenDesc m_nextToken; // desc for next token (one token look-ahead) - - CharStream m_source; - std::shared_ptr m_sourceName; - - /// one character look-ahead, equals 0 at end of input - char m_char; -}; - -} -} diff --git a/libsolidity/parsing/Token.h b/libsolidity/parsing/Token.h index 53205a726..abb717f2d 100644 --- a/libsolidity/parsing/Token.h +++ b/libsolidity/parsing/Token.h @@ -1,394 +1,36 @@ -// Copyright 2006-2012, the V8 project authors. All rights reserved. -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following -// disclaimer in the documentation and/or other materials provided -// with the distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -// Modifications as part of solidity under the following license: -// -// 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 . - +/* + 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 . +*/ +/** + * Solidity and Yul both share the same Token (and Scanner) API. + * + * This may (or may not) change in the future. But for the time being, we've put both + * at a shared place, and *just* import them. +*/ #pragma once -#include -#include -#include -#include +#include namespace dev { namespace solidity { +namespace TokenTraits = ::langutil::TokenTraits; -// TOKEN_LIST takes a list of 3 macros M, all of which satisfy the -// same signature M(name, string, precedence), where name is the -// symbolic token name, string is the corresponding syntactic symbol -// (or NULL, for literals), and precedence is the precedence (or 0). -// The parameters are invoked for token categories as follows: -// -// T: Non-keyword tokens -// K: Keyword tokens - -// IGNORE_TOKEN is a convenience macro that can be supplied as -// an argument (at any position) for a TOKEN_LIST call. It does -// nothing with tokens belonging to the respective category. - -#define IGNORE_TOKEN(name, string, precedence) - -#define TOKEN_LIST(T, K) \ - /* End of source indicator. */ \ - T(EOS, "EOS", 0) \ - \ - /* Punctuators (ECMA-262, section 7.7, page 15). */ \ - T(LParen, "(", 0) \ - T(RParen, ")", 0) \ - T(LBrack, "[", 0) \ - T(RBrack, "]", 0) \ - T(LBrace, "{", 0) \ - T(RBrace, "}", 0) \ - T(Colon, ":", 0) \ - T(Semicolon, ";", 0) \ - T(Period, ".", 0) \ - T(Conditional, "?", 3) \ - T(Arrow, "=>", 0) \ - \ - /* Assignment operators. */ \ - /* IsAssignmentOp() relies on this block of enum values being */ \ - /* contiguous and sorted in the same order!*/ \ - T(Assign, "=", 2) \ - /* The following have to be in exactly the same order as the simple binary operators*/ \ - T(AssignBitOr, "|=", 2) \ - T(AssignBitXor, "^=", 2) \ - T(AssignBitAnd, "&=", 2) \ - T(AssignShl, "<<=", 2) \ - T(AssignSar, ">>=", 2) \ - T(AssignShr, ">>>=", 2) \ - T(AssignAdd, "+=", 2) \ - T(AssignSub, "-=", 2) \ - T(AssignMul, "*=", 2) \ - T(AssignDiv, "/=", 2) \ - T(AssignMod, "%=", 2) \ - \ - /* Binary operators sorted by precedence. */ \ - /* IsBinaryOp() relies on this block of enum values */ \ - /* being contiguous and sorted in the same order! */ \ - T(Comma, ",", 1) \ - T(Or, "||", 4) \ - T(And, "&&", 5) \ - T(BitOr, "|", 8) \ - T(BitXor, "^", 9) \ - T(BitAnd, "&", 10) \ - T(SHL, "<<", 11) \ - T(SAR, ">>", 11) \ - T(SHR, ">>>", 11) \ - T(Add, "+", 12) \ - T(Sub, "-", 12) \ - T(Mul, "*", 13) \ - T(Div, "/", 13) \ - T(Mod, "%", 13) \ - T(Exp, "**", 14) \ - \ - /* Compare operators sorted by precedence. */ \ - /* IsCompareOp() relies on this block of enum values */ \ - /* being contiguous and sorted in the same order! */ \ - T(Equal, "==", 6) \ - T(NotEqual, "!=", 6) \ - T(LessThan, "<", 7) \ - T(GreaterThan, ">", 7) \ - T(LessThanOrEqual, "<=", 7) \ - T(GreaterThanOrEqual, ">=", 7) \ - \ - /* Unary operators. */ \ - /* IsUnaryOp() relies on this block of enum values */ \ - /* being contiguous and sorted in the same order! */ \ - T(Not, "!", 0) \ - T(BitNot, "~", 0) \ - T(Inc, "++", 0) \ - T(Dec, "--", 0) \ - K(FactInsert, "factInsert", 0) \ - K(FactDelete, "factDelete", 0) \ - K(Update, "update", 0) \ - K(Delete, "delete", 0) \ - \ - /* Keywords */ \ - K(Anonymous, "anonymous", 0) \ - K(As, "as", 0) \ - K(Assembly, "assembly", 0) \ - K(Break, "break", 0) \ - K(Constant, "constant", 0) \ - K(Constructor, "constructor", 0) \ - K(Continue, "continue", 0) \ - K(Contract, "contract", 0) \ - K(Do, "do", 0) \ - K(Else, "else", 0) \ - K(Enum, "enum", 0) \ - K(Emit, "emit", 0) \ - K(Event, "event", 0) \ - K(External, "external", 0) \ - K(For, "for", 0) \ - K(Function, "function", 0) \ - K(Hex, "hex", 0) \ - K(If, "if", 0) \ - K(Indexed, "indexed", 0) \ - K(Interface, "interface", 0) \ - K(Internal, "internal", 0) \ - K(Import, "import", 0) \ - K(Is, "is", 0) \ - K(Library, "library", 0) \ - K(Mapping, "mapping", 0) \ - K(Memory, "memory", 0) \ - K(Modifier, "modifier", 0) \ - K(New, "new", 0) \ - K(Payable, "payable", 0) \ - K(Public, "public", 0) \ - K(Pragma, "pragma", 0) \ - K(Private, "private", 0) \ - K(Pure, "pure", 0) \ - K(Return, "return", 0) \ - K(Returns, "returns", 0) \ - K(Storage, "storage", 0) \ - K(CallData, "calldata", 0) \ - K(Struct, "struct", 0) \ - K(Throw, "throw", 0) \ - K(Using, "using", 0) \ - K(Var, "var", 0) \ - K(View, "view", 0) \ - K(While, "while", 0) \ - K(Rule, "rule", 0) \ - K(When, "when", 0) \ - K(Then, "then", 0) \ - K(FireAllRules, "fireAllRules", 0) \ - K(NoLoop, "no_loop", 0) \ - K(LockOnActive, "lock_on_active", 0) \ - K(Salience, "salience", 0) \ - K(Extends, "extends", 0) \ - K(FreeGas, "freegas", 0) \ - \ - /* Ether subdenominations */ \ - K(SubWei, "wei", 0) \ - K(SubSzabo, "szabo", 0) \ - K(SubFinney, "finney", 0) \ - K(SubEther, "ether", 0) \ - K(SubSecond, "seconds", 0) \ - K(SubMinute, "minutes", 0) \ - K(SubHour, "hours", 0) \ - K(SubDay, "days", 0) \ - K(SubWeek, "weeks", 0) \ - K(SubYear, "years", 0) \ - /* type keywords*/ \ - K(Int, "int", 0) \ - K(UInt, "uint", 0) \ - K(Bytes, "bytes", 0) \ - K(Byte, "byte", 0) \ - K(String, "string", 0) \ - K(Address, "address", 0) \ - K(Bool, "bool", 0) \ - K(Fixed, "fixed", 0) \ - K(UFixed, "ufixed", 0) \ - T(IntM, "intM", 0) \ - T(UIntM, "uintM", 0) \ - K(SafeUint, "safeuint", 0) \ - T(BytesM, "bytesM", 0) \ - T(FixedMxN, "fixedMxN", 0) \ - T(UFixedMxN, "ufixedMxN", 0) \ - T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ - \ - /* Literals */ \ - K(TrueLiteral, "true", 0) \ - K(FalseLiteral, "false", 0) \ - T(Number, NULL, 0) \ - T(StringLiteral, NULL, 0) \ - T(CommentLiteral, NULL, 0) \ - \ - /* Identifiers (not keywords or future reserved words). */ \ - T(Identifier, NULL, 0) \ - \ - /* Keywords reserved for future use. */ \ - K(Abstract, "abstract", 0) \ - K(After, "after", 0) \ - K(Alias, "alias", 0) \ - K(Apply, "apply", 0) \ - K(Auto, "auto", 0) \ - K(Case, "case", 0) \ - K(Catch, "catch", 0) \ - K(CopyOf, "copyof", 0) \ - K(Default, "default", 0) \ - K(Define, "define", 0) \ - K(Final, "final", 0) \ - K(Immutable, "immutable", 0) \ - K(Implements, "implements", 0) \ - K(In, "in", 0) \ - K(Inline, "inline", 0) \ - K(Let, "let", 0) \ - K(Macro, "macro", 0) \ - K(Match, "match", 0) \ - K(Mutable, "mutable", 0) \ - K(NullLiteral, "null", 0) \ - K(Of, "of", 0) \ - K(Override, "override", 0) \ - K(Partial, "partial", 0) \ - K(Promise, "promise", 0) \ - K(Reference, "reference", 0) \ - K(Relocatable, "relocatable", 0) \ - K(Sealed, "sealed", 0) \ - K(Sizeof, "sizeof", 0) \ - K(Static, "static", 0) \ - K(Supports, "supports", 0) \ - K(Switch, "switch", 0) \ - K(Try, "try", 0) \ - K(Type, "type", 0) \ - K(Typedef, "typedef", 0) \ - K(TypeOf, "typeof", 0) \ - K(Unchecked, "unchecked", 0) \ - \ - /* Illegal token - not able to scan. */ \ - T(Illegal, "ILLEGAL", 0) \ - /* Illegal hex token */ \ - T(IllegalHex, "ILLEGAL_HEX", 0) \ - \ - /* Scanner-internal use only. */ \ - T(Whitespace, NULL, 0) - -// All token values. -// attention! msvc issue: -// http://stackoverflow.com/questions/9567868/compile-errors-after-adding-v8-to-my-project-c2143-c2059 -// @todo: avoid TOKEN_LIST macro -enum class Token : unsigned int { -#define T(name, string, precedence) name, - TOKEN_LIST(T, T) - NUM_TOKENS -#undef T -}; - -namespace TokenTraits -{ - constexpr size_t count() { return static_cast(Token::NUM_TOKENS); } - - // Predicates - constexpr bool isElementaryTypeName(Token tok) { return Token::Int <= tok && tok < Token::TypesEnd; } - constexpr bool isAssignmentOp(Token tok) { return Token::Assign <= tok && tok <= Token::AssignMod; } - constexpr bool isBinaryOp(Token op) { return Token::Comma <= op && op <= Token::Exp; } - constexpr bool isCommutativeOp(Token op) { return op == Token::BitOr || op == Token::BitXor || op == Token::BitAnd || - op == Token::Add || op == Token::Mul || op == Token::Equal || op == Token::NotEqual; } - constexpr bool isArithmeticOp(Token op) { return Token::Add <= op && op <= Token::Exp; } - constexpr bool isCompareOp(Token op) { return Token::Equal <= op && op <= Token::GreaterThanOrEqual; } - - constexpr bool isBitOp(Token op) { return (Token::BitOr <= op && op <= Token::BitAnd) || op == Token::BitNot; } - constexpr bool isBooleanOp(Token op) { return (Token::Or <= op && op <= Token::And) || op == Token::Not; } - constexpr bool isUnaryOp(Token op) { return (Token::Not <= op && op <= Token::Delete) || op == Token::Add || op == Token::Sub; } - constexpr bool isCountOp(Token op) { return op == Token::Inc || op == Token::Dec; } - constexpr bool isShiftOp(Token op) { return (Token::SHL <= op) && (op <= Token::SHR); } - constexpr bool isVariableVisibilitySpecifier(Token op) { return op == Token::Public || op == Token::Private || op == Token::Internal; } - constexpr bool isVisibilitySpecifier(Token op) { return isVariableVisibilitySpecifier(op) || op == Token::External; } - constexpr bool isLocationSpecifier(Token op) { return op == Token::Memory || op == Token::Storage || op == Token::CallData; } - - constexpr bool isStateMutabilitySpecifier(Token op, bool _allowConstant = true) - { - return (op == Token::Constant && _allowConstant) - || op == Token::Pure || op == Token::View || op == Token::Payable; - } - - constexpr bool isEtherSubdenomination(Token op) { return op == Token::SubWei || op == Token::SubSzabo || op == Token::SubFinney || op == Token::SubEther; } - constexpr bool isTimeSubdenomination(Token op) { return op == Token::SubSecond || op == Token::SubMinute || op == Token::SubHour || op == Token::SubDay || op == Token::SubWeek || op == Token::SubYear; } - constexpr bool isReservedKeyword(Token op) { return (Token::Abstract <= op && op <= Token::Unchecked); } - - constexpr bool isFreeGas(Token op) { return op == Token::FreeGas; } - constexpr bool isRuleSpecifier(Token op) { return op == Token::NoLoop || op == Token::Salience || op == Token::LockOnActive; } - - inline Token AssignmentToBinaryOp(Token op) - { - solAssert(isAssignmentOp(op) && op != Token::Assign, ""); - return static_cast(static_cast(op) + (static_cast(Token::BitOr) - static_cast(Token::AssignBitOr))); - } - - // @returns the precedence > 0 for binary and compare - // operators; returns 0 otherwise. - int precedence(Token tok); - - std::tuple fromIdentifierOrKeyword(std::string const& _literal); - - // @returns a string corresponding to the C++ token name - // (e.g. "LT" for the token LT). - char const* name(Token tok); - - // @returns a string corresponding to the JS token string - // (.e., "<" for the token LT) or NULL if the token doesn't - // have a (unique) string (e.g. an IDENTIFIER). - char const* toString(Token tok); - - std::string friendlyName(Token tok); -} - -inline std::ostream& operator<<(std::ostream& os, Token token) -{ - os << TokenTraits::friendlyName(token); - return os; -} - -class ElementaryTypeNameToken -{ -public: - ElementaryTypeNameToken(Token _token, unsigned const& _firstNumber, unsigned const& _secondNumber) - { - assertDetails(_token, _firstNumber, _secondNumber); - } - - unsigned int firstNumber() const { return m_firstNumber; } - unsigned int secondNumber() const { return m_secondNumber; } - Token token() const { return m_token; } - - ///if tokValue is set to true, then returns the actual token type name, otherwise, returns full type - std::string toString(bool const& tokenValue = false) const - { - std::string name = TokenTraits::toString(m_token); - if (tokenValue || (firstNumber() == 0 && secondNumber() == 0)) - return name; - solAssert(name.size() >= 3, "Token name size should be greater than 3. Should not reach here."); - if (m_token == Token::FixedMxN || m_token == Token::UFixedMxN) - return name.substr(0, name.size() - 3) + std::to_string(m_firstNumber) + "x" + std::to_string(m_secondNumber); - else - return name.substr(0, name.size() - 1) + std::to_string(m_firstNumber); - } - -private: - Token m_token; - unsigned int m_firstNumber; - unsigned int m_secondNumber; - /// throws if type is not properly sized - void assertDetails(Token _baseType, unsigned const& _first, unsigned const& _second); -}; - +using ::langutil::Token; +using ::langutil::ElementaryTypeNameToken; } } diff --git a/libyul/ASTDataForward.h b/libyul/ASTDataForward.h deleted file mode 100644 index 8c49e68f6..000000000 --- a/libyul/ASTDataForward.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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 . -*/ -/** - * @date 2017 - * Pull in some identifiers from the solidity::assembly namespace. - */ - -#pragma once - -#include - -namespace dev -{ -namespace yul -{ - -using Instruction = solidity::assembly::Instruction; -using Literal = solidity::assembly::Literal; -using Label = solidity::assembly::Label; -using StackAssignment = solidity::assembly::StackAssignment; -using Identifier = solidity::assembly::Identifier; -using Assignment = solidity::assembly::Assignment; -using VariableDeclaration = solidity::assembly::VariableDeclaration; -using FunctionalInstruction = solidity::assembly::FunctionalInstruction; -using FunctionDefinition = solidity::assembly::FunctionDefinition; -using FunctionCall = solidity::assembly::FunctionCall; -using If = solidity::assembly::If; -using Case = solidity::assembly::Case; -using Switch = solidity::assembly::Switch; -using ForLoop = solidity::assembly::ForLoop; -using ExpressionStatement = solidity::assembly::ExpressionStatement; -using Block = solidity::assembly::Block; - -using TypedName = solidity::assembly::TypedName; -class YulString; - -using Expression = boost::variant; -using Statement = boost::variant; - -} -} diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp similarity index 63% rename from libsolidity/inlineasm/AsmAnalysis.cpp rename to libyul/AsmAnalysis.cpp index d97d967da..9c62737d8 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -18,27 +18,33 @@ * Analyzer part of inline assembly. */ -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include +#include #include #include #include #include +#include using namespace std; using namespace dev; -using namespace dev::solidity; -using namespace dev::solidity::assembly; +using namespace langutil; +using namespace yul; +using namespace dev; -namespace { +namespace +{ set const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"}; @@ -46,10 +52,39 @@ set const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", " bool AsmAnalyzer::analyze(Block const& _block) { - if (!(ScopeFiller(m_info, m_errorReporter))(_block)) - return false; + bool success = false; + try + { + if (!(ScopeFiller(m_info, m_errorReporter))(_block)) + return false; - return (*this)(_block); + success = (*this)(_block); + if (!success) + solAssert(m_errorReporter.hasErrors(), "No success but no error."); + } + catch (FatalError const&) + { + // This FatalError con occur if the errorReporter has too many errors. + solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); + } + return success && !m_errorReporter.hasErrors(); +} + +AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object) +{ + ErrorList errorList; + langutil::ErrorReporter errors(errorList); + AsmAnalysisInfo analysisInfo; + bool success = yul::AsmAnalyzer( + analysisInfo, + errors, + Error::Type::SyntaxError, + _dialect, + {}, + _object.dataNames() + ).analyze(*_object.code); + solAssert(success && errorList.empty(), "Invalid assembly/yul code."); + return analysisInfo; } bool AsmAnalyzer::operator()(Label const& _label) @@ -60,11 +95,11 @@ bool AsmAnalyzer::operator()(Label const& _label) "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead." ); m_info.stackHeightInfo[&_label] = m_stackHeight; - warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location); + warnOnInstructions(dev::eth::Instruction::JUMPDEST, _label.location); return true; } -bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) +bool AsmAnalyzer::operator()(yul::Instruction const& _instruction) { checkLooseFeature( _instruction.location, @@ -77,11 +112,11 @@ bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction) return true; } -bool AsmAnalyzer::operator()(assembly::Literal const& _literal) +bool AsmAnalyzer::operator()(Literal const& _literal) { expectValidType(_literal.type.str(), _literal.location); ++m_stackHeight; - if (_literal.kind == assembly::LiteralKind::String && _literal.value.str().size() > 32) + if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32) { m_errorReporter.typeError( _literal.location, @@ -89,7 +124,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal) ); return false; } - else if (_literal.kind == assembly::LiteralKind::Number && bigint(_literal.value.str()) > u256(-1)) + else if (_literal.kind == LiteralKind::Number && bigint(_literal.value.str()) > u256(-1)) { m_errorReporter.typeError( _literal.location, @@ -97,16 +132,16 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal) ); return false; } - else if (_literal.kind == assembly::LiteralKind::Boolean) + else if (_literal.kind == LiteralKind::Boolean) { - solAssert(m_flavour == AsmFlavour::Yul, ""); - solAssert(_literal.value == YulString{string("true")} || _literal.value == YulString{string("false")}, ""); + solAssert(m_dialect.flavour == AsmFlavour::Yul, ""); + solAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, ""); } m_info.stackHeightInfo[&_literal] = m_stackHeight; return true; } -bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) +bool AsmAnalyzer::operator()(Identifier const& _identifier) { solAssert(!_identifier.name.empty(), ""); size_t numErrorsBefore = m_errorReporter.errors().size(); @@ -162,7 +197,7 @@ bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier) bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) { - solAssert(m_flavour != AsmFlavour::Yul, ""); + solAssert(m_dialect.flavour != AsmFlavour::Yul, ""); bool success = true; for (auto const& arg: _instr.arguments | boost::adaptors::reversed) if (!expectExpression(arg)) @@ -176,13 +211,13 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr) return success; } -bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) +bool AsmAnalyzer::operator()(ExpressionStatement const& _statement) { int initialStackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, _statement.expression); - if (m_stackHeight != initialStackHeight && (m_flavour != AsmFlavour::Loose || m_errorTypeForLoose)) + if (m_stackHeight != initialStackHeight && (m_dialect.flavour != AsmFlavour::Loose || m_errorTypeForLoose)) { - Error::Type errorType = m_flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; + Error::Type errorType = m_dialect.flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError; string msg = "Top-level expressions are not supposed to return values (this expression returns " + to_string(m_stackHeight - initialStackHeight) + @@ -197,7 +232,7 @@ bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement) return success; } -bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) +bool AsmAnalyzer::operator()(StackAssignment const& _assignment) { checkLooseFeature( _assignment.location, @@ -208,7 +243,7 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment) return success; } -bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) +bool AsmAnalyzer::operator()(Assignment const& _assignment) { solAssert(_assignment.value, ""); int const expectedItems = _assignment.variableNames.size(); @@ -234,7 +269,7 @@ bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment) return success; } -bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) +bool AsmAnalyzer::operator()(VariableDeclaration const& _varDecl) { bool success = true; int const numVariables = _varDecl.variables.size(); @@ -242,9 +277,18 @@ bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) { int const stackHeight = m_stackHeight; success = boost::apply_visitor(*this, *_varDecl.value); - if ((m_stackHeight - stackHeight) != numVariables) + int numValues = m_stackHeight - stackHeight; + if (numValues != numVariables) { - m_errorReporter.declarationError(_varDecl.location, "Variable count mismatch."); + m_errorReporter.declarationError(_varDecl.location, + "Variable count mismatch: " + + to_string(numVariables) + + " variables and " + + to_string(numValues) + + " values." + ); + // Adjust stack height to avoid misleading additional errors. + m_stackHeight += numVariables - numValues; return false; } } @@ -260,7 +304,7 @@ bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) return success; } -bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) +bool AsmAnalyzer::operator()(FunctionDefinition const& _funDef) { solAssert(!_funDef.name.empty(), ""); Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get(); @@ -282,13 +326,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef) return success; } -bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) +bool AsmAnalyzer::operator()(FunctionCall const& _funCall) { solAssert(!_funCall.functionName.name.empty(), ""); bool success = true; - size_t arguments = 0; + size_t parameters = 0; size_t returns = 0; - if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( + bool needsLiteralArguments = false; + if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name)) + { + // TODO: compare types, too + parameters = f->parameters.size(); + returns = f->returns.size(); + if (f->literalArguments) + needsLiteralArguments = true; + } + else if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( [&](Scope::Variable const&) { m_errorReporter.typeError( @@ -308,7 +361,7 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) [&](Scope::Function const& _fun) { /// TODO: compare types too - arguments = _fun.arguments.size(); + parameters = _fun.arguments.size(); returns = _fun.returns.size(); } ))) @@ -317,21 +370,38 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall) success = false; } if (success) - { - if (_funCall.arguments.size() != arguments) + if (_funCall.arguments.size() != parameters) { m_errorReporter.typeError( _funCall.functionName.location, - "Expected " + to_string(arguments) + " arguments but got " + + "Function expects " + + to_string(parameters) + + " arguments but got " + to_string(_funCall.arguments.size()) + "." ); success = false; } - } + for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) + { if (!expectExpression(arg)) success = false; - m_stackHeight += int(returns) - int(arguments); + else if (needsLiteralArguments) + { + if (arg.type() != typeid(Literal)) + m_errorReporter.typeError( + _funCall.functionName.location, + "Function expects direct literals as arguments." + ); + else if (!m_dataNames.count(boost::get(arg).value)) + m_errorReporter.typeError( + _funCall.functionName.location, + "Unknown data object \"" + boost::get(arg).value.str() + "\"." + ); + } + } + // Use argument size instead of parameter count to avoid misleading errors. + m_stackHeight += int(returns) - int(_funCall.arguments.size()); m_info.stackHeightInfo[&_funCall] = m_stackHeight; return success; } @@ -340,9 +410,11 @@ bool AsmAnalyzer::operator()(If const& _if) { bool success = true; + int const initialHeight = m_stackHeight; if (!expectExpression(*_if.condition)) success = false; - m_stackHeight--; + + m_stackHeight = initialHeight; if (!(*this)(_if.body)) success = false; @@ -358,29 +430,57 @@ bool AsmAnalyzer::operator()(Switch const& _switch) bool success = true; + int const initialHeight = m_stackHeight; if (!expectExpression(*_switch.expression)) success = false; - set> cases; + if (m_dialect.flavour == AsmFlavour::Yul) + { + YulString caseType; + bool mismatchingTypes = false; + for (auto const& _case: _switch.cases) + if (_case.value) + { + if (caseType.empty()) + caseType = _case.value->type; + else if (caseType != _case.value->type) + { + mismatchingTypes = true; + break; + } + } + if (mismatchingTypes) + m_errorReporter.typeError( + _switch.location, + "Switch cases have non-matching types." + ); + } + + set cases; for (auto const& _case: _switch.cases) { if (_case.value) { int const initialStackHeight = m_stackHeight; + bool isCaseValueValid = true; // We cannot use "expectExpression" here because *_case.value is not a // Statement and would be converted to a Statement otherwise. if (!(*this)(*_case.value)) + { + isCaseValueValid = false; success = false; + } expectDeposit(1, initialStackHeight, _case.value->location); m_stackHeight--; + // If the case value is not valid, we should not insert it into cases. + yulAssert(isCaseValueValid || m_errorReporter.hasErrors(), "Invalid case value."); /// Note: the parser ensures there is only one default case - auto val = make_tuple(_case.value->kind, _case.value->value); - if (!cases.insert(val).second) + if (isCaseValueValid && !cases.insert(valueOfLiteral(*_case.value)).second) { m_errorReporter.declarationError( _case.location, - "Duplicate case defined" + "Duplicate case defined." ); success = false; } @@ -390,17 +490,19 @@ bool AsmAnalyzer::operator()(Switch const& _switch) success = false; } - m_stackHeight--; + m_stackHeight = initialHeight; m_info.stackHeightInfo[&_switch] = m_stackHeight; return success; } -bool AsmAnalyzer::operator()(assembly::ForLoop const& _for) +bool AsmAnalyzer::operator()(ForLoop const& _for) { solAssert(_for.condition, ""); - Scope* originalScope = m_currentScope; + Scope* outerScope = m_currentScope; + + int const initialHeight = m_stackHeight; bool success = true; if (!(*this)(_for.pre)) @@ -412,19 +514,39 @@ bool AsmAnalyzer::operator()(assembly::ForLoop const& _for) if (!expectExpression(*_for.condition)) success = false; + m_stackHeight--; + + // backup outer for-loop & create new state + auto outerForLoop = m_currentForLoop; + m_currentForLoop = &_for; + if (!(*this)(_for.body)) success = false; + if (!(*this)(_for.post)) success = false; - m_stackHeight -= scope(&_for.pre).numberOfVariables(); + m_stackHeight = initialHeight; m_info.stackHeightInfo[&_for] = m_stackHeight; - m_currentScope = originalScope; + m_currentScope = outerScope; + m_currentForLoop = outerForLoop; return success; } +bool AsmAnalyzer::operator()(Break const& _break) +{ + m_info.stackHeightInfo[&_break] = m_stackHeight; + return true; +} + +bool AsmAnalyzer::operator()(Continue const& _continue) +{ + m_info.stackHeightInfo[&_continue] = m_stackHeight; + return true; +} + bool AsmAnalyzer::operator()(Block const& _block) { bool success = true; @@ -485,7 +607,7 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con return true; } -bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize) +bool AsmAnalyzer::checkAssignment(Identifier const& _variable, size_t _valueSize) { solAssert(!_variable.name.empty(), ""); bool success = true; @@ -550,7 +672,7 @@ Scope& AsmAnalyzer::scope(Block const* _block) } void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location) { - if (m_flavour != AsmFlavour::Yul) + if (m_dialect.flavour != AsmFlavour::Yul) return; if (!builtinTypes.count(type)) @@ -560,70 +682,92 @@ void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _loc ); } -void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location) +void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocation const& _location) { // We assume that returndatacopy, returndatasize and staticcall are either all available // or all not available. solAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); // Similarly we assume bitwise shifting and create2 go together. solAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), ""); + solAssert(m_dialect.flavour != AsmFlavour::Yul, ""); - if (_instr == solidity::Instruction::EXTCODEHASH) - m_errorReporter.warning( - _location, - "The \"" + - boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is not supported by the VM version \"" + - "" + m_evmVersion.name() + - "\" you are currently compiling for. " + - "It will be interpreted as an invalid instruction on this VM." - ); - else if (( - _instr == solidity::Instruction::RETURNDATACOPY || - _instr == solidity::Instruction::RETURNDATASIZE || - _instr == solidity::Instruction::STATICCALL - ) && !m_evmVersion.supportsReturndata()) - m_errorReporter.warning( + auto errorForVM = [=](string const& vmKindMessage) { + m_errorReporter.typeError( _location, "The \"" + boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available for Byzantium-compatible VMs. " + - "You are currently compiling for \"" + + + "\" instruction is " + + vmKindMessage + + " VMs " + + " (you are currently compiling for \"" + m_evmVersion.name() + - "\", where it will be interpreted as an invalid instruction." + "\")." ); + }; + + if (( + _instr == dev::eth::Instruction::RETURNDATACOPY || + _instr == dev::eth::Instruction::RETURNDATASIZE + ) && !m_evmVersion.supportsReturndata()) + { + errorForVM("only available for Byzantium-compatible"); + } + else if (_instr == dev::eth::Instruction::STATICCALL && !m_evmVersion.hasStaticCall()) + { + errorForVM("only available for Byzantium-compatible"); + } else if (( - _instr == solidity::Instruction::SHL || - _instr == solidity::Instruction::SHR || - _instr == solidity::Instruction::SAR - /*XXX: conflict with ENI // _instr == solidity::Instruction::CREATE2*/ + _instr == dev::eth::Instruction::SHL || + _instr == dev::eth::Instruction::SHR || + _instr == dev::eth::Instruction::SAR ) && !m_evmVersion.hasBitwiseShifting()) - m_errorReporter.warning( - _location, - "The \"" + - boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available for Constantinople-compatible VMs. " + - "You are currently compiling for \"" + - m_evmVersion.name() + - "\", where it will be interpreted as an invalid instruction." - ); - - if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) { - solAssert(m_flavour == AsmFlavour::Loose, ""); - m_errorReporter.error( - m_errorTypeForLoose ? *m_errorTypeForLoose : Error::Type::Warning, - _location, - "Jump instructions and labels are low-level EVM features that can lead to " - "incorrect stack access. Because of that they are discouraged. " - "Please consider using \"switch\", \"if\" or \"for\" statements instead." - ); + errorForVM("only available for Constantinople-compatible"); + } + else if (_instr == dev::eth::Instruction::CREATE2 && !m_evmVersion.hasCreate2()) + { + errorForVM("only available for Constantinople-compatible"); + } + else if (_instr == dev::eth::Instruction::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) + { + errorForVM("only available for Constantinople-compatible"); + } + else if (_instr == dev::eth::Instruction::CHAINID && !m_evmVersion.hasChainID()) + { + errorForVM("only available for Istanbul-compatible"); + } + else if (_instr == dev::eth::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance()) + { + errorForVM("only available for Istanbul-compatible"); + } + else if ( + _instr == dev::eth::Instruction::JUMP || + _instr == dev::eth::Instruction::JUMPI || + _instr == dev::eth::Instruction::JUMPDEST + ) + { + if (m_dialect.flavour == AsmFlavour::Loose) + m_errorReporter.error( + m_errorTypeForLoose ? *m_errorTypeForLoose : Error::Type::Warning, + _location, + "Jump instructions and labels are low-level EVM features that can lead to " + "incorrect stack access. Because of that they are discouraged. " + "Please consider using \"switch\", \"if\" or \"for\" statements instead." + ); + else + m_errorReporter.error( + Error::Type::SyntaxError, + _location, + "Jump instructions and labels are low-level EVM features that can lead to " + "incorrect stack access. Because of that they are disallowed in strict assembly. " + "Use functions, \"switch\", \"if\" or \"for\" statements instead." + ); } } void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description) { - if (m_flavour != AsmFlavour::Loose) + if (m_dialect.flavour != AsmFlavour::Loose) solAssert(false, _description); else if (m_errorTypeForLoose) m_errorReporter.error(*m_errorTypeForLoose, _location, _description); diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h new file mode 100644 index 000000000..facae9187 --- /dev/null +++ b/libyul/AsmAnalysis.h @@ -0,0 +1,136 @@ +/* + 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 . +*/ +/** + * Analysis part of inline assembly. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace langutil +{ +class ErrorReporter; +struct SourceLocation; +} + +namespace yul +{ + +struct AsmAnalysisInfo; + +/** + * Performs the full analysis stage, calls the ScopeFiller internally, then resolves + * references and performs other checks. + * If all these checks pass, code generation should not throw errors. + */ +class AsmAnalyzer: public boost::static_visitor +{ +public: + explicit AsmAnalyzer( + AsmAnalysisInfo& _analysisInfo, + langutil::ErrorReporter& _errorReporter, + boost::optional _errorTypeForLoose, + Dialect const& _dialect, + ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver(), + std::set const& _dataNames = {} + ): + m_resolver(_resolver), + m_info(_analysisInfo), + m_errorReporter(_errorReporter), + m_dialect(_dialect), + m_errorTypeForLoose(_errorTypeForLoose), + m_dataNames(_dataNames) + { + if (EVMDialect const* evmDialect = dynamic_cast(&m_dialect)) + m_evmVersion = evmDialect->evmVersion(); + } + + bool analyze(Block const& _block); + + /// Performs analysis on the outermost code of the given object and returns the analysis info. + /// Asserts on failure. + static AsmAnalysisInfo analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object); + + bool operator()(Instruction const&); + bool operator()(Literal const& _literal); + bool operator()(Identifier const&); + bool operator()(FunctionalInstruction const& _functionalInstruction); + bool operator()(Label const& _label); + bool operator()(ExpressionStatement const&); + bool operator()(StackAssignment const&); + bool operator()(Assignment const& _assignment); + bool operator()(VariableDeclaration const& _variableDeclaration); + bool operator()(FunctionDefinition const& _functionDefinition); + bool operator()(FunctionCall const& _functionCall); + bool operator()(If const& _if); + bool operator()(Switch const& _switch); + bool operator()(ForLoop const& _forLoop); + bool operator()(Break const&); + bool operator()(Continue const&); + bool operator()(Block const& _block); + +private: + /// Visits the statement and expects it to deposit one item onto the stack. + bool expectExpression(Expression const& _expr); + bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location); + + /// Verifies that a variable to be assigned to exists and has the same size + /// as the value, @a _valueSize, unless that is equal to -1. + bool checkAssignment(Identifier const& _assignment, size_t _valueSize = size_t(-1)); + + Scope& scope(Block const* _block); + void expectValidType(std::string const& type, langutil::SourceLocation const& _location); + void warnOnInstructions(dev::eth::Instruction _instr, langutil::SourceLocation const& _location); + + /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler + /// exception (if the flavour is not Loose), reports an error/warning + /// (if m_errorTypeForLoose is set) or does nothing. + void checkLooseFeature(langutil::SourceLocation const& _location, std::string const& _description); + + int m_stackHeight = 0; + yul::ExternalIdentifierAccess::Resolver m_resolver; + Scope* m_currentScope = nullptr; + /// Variables that are active at the current point in assembly (as opposed to + /// "part of the scope but not yet declared") + std::set m_activeVariables; + AsmAnalysisInfo& m_info; + langutil::ErrorReporter& m_errorReporter; + langutil::EVMVersion m_evmVersion; + Dialect const& m_dialect; + boost::optional m_errorTypeForLoose; + /// Names of data objects to be referenced by builtin functions with literal arguments. + std::set m_dataNames; + ForLoop const* m_currentForLoop = nullptr; +}; + +} diff --git a/libsolidity/inlineasm/AsmAnalysisInfo.h b/libyul/AsmAnalysisInfo.h similarity index 79% rename from libsolidity/inlineasm/AsmAnalysisInfo.h rename to libyul/AsmAnalysisInfo.h index bd3b28c4c..08a35ade0 100644 --- a/libsolidity/inlineasm/AsmAnalysisInfo.h +++ b/libyul/AsmAnalysisInfo.h @@ -20,7 +20,7 @@ #pragma once -#include +#include #include @@ -28,11 +28,7 @@ #include #include -namespace dev -{ -namespace solidity -{ -namespace assembly +namespace yul { struct Scope; @@ -40,13 +36,11 @@ struct Scope; struct AsmAnalysisInfo { using StackHeightInfo = std::map; - using Scopes = std::map>; + using Scopes = std::map>; Scopes scopes; StackHeightInfo stackHeightInfo; /// Virtual blocks which will be used for scopes for function arguments and return values. - std::map> virtualBlocks; + std::map> virtualBlocks; }; } -} -} diff --git a/libyul/AsmData.h b/libyul/AsmData.h new file mode 100644 index 000000000..cedff1106 --- /dev/null +++ b/libyul/AsmData.h @@ -0,0 +1,100 @@ +/* + 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 Christian + * @date 2016 + * Parsed inline assembly to be used by the AST + */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace yul +{ + +using Type = YulString; + +struct TypedName { langutil::SourceLocation location; YulString name; Type type; }; +using TypedNameList = std::vector; + +/// Direct EVM instruction (except PUSHi and JUMPDEST) +struct Instruction { langutil::SourceLocation location; dev::eth::Instruction instruction; }; +/// Literal number or string (up to 32 bytes) +enum class LiteralKind { Number, Boolean, String }; +struct Literal { langutil::SourceLocation location; LiteralKind kind; YulString value; Type type; }; +/// External / internal identifier or label reference +struct Identifier { langutil::SourceLocation location; YulString name; }; +/// Jump label ("name:") +struct Label { langutil::SourceLocation location; YulString name; }; +/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots) +struct StackAssignment { langutil::SourceLocation location; Identifier variableName; }; +/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand +/// side and requires x to occupy exactly one stack slot. +/// +/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy +/// a single stack slot and expects a single expression on the right hand returning +/// the same amount of items as the number of variables. +struct Assignment { langutil::SourceLocation location; std::vector variableNames; std::unique_ptr value; }; +/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" +struct FunctionalInstruction { langutil::SourceLocation location; dev::eth::Instruction instruction; std::vector arguments; }; +struct FunctionCall { langutil::SourceLocation location; Identifier functionName; std::vector arguments; }; +/// Statement that contains only a single expression +struct ExpressionStatement { langutil::SourceLocation location; Expression expression; }; +/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted +struct VariableDeclaration { langutil::SourceLocation location; TypedNameList variables; std::unique_ptr value; }; +/// Block that creates a scope (frees declared stack variables) +struct Block { langutil::SourceLocation location; std::vector statements; }; +/// Function definition ("function f(a, b) -> (d, e) { ... }") +struct FunctionDefinition { langutil::SourceLocation location; YulString name; TypedNameList parameters; TypedNameList returnVariables; Block body; }; +/// Conditional execution without "else" part. +struct If { langutil::SourceLocation location; std::unique_ptr condition; Block body; }; +/// Switch case or default case +struct Case { langutil::SourceLocation location; std::unique_ptr value; Block body; }; +/// Switch statement +struct Switch { langutil::SourceLocation location; std::unique_ptr expression; std::vector cases; }; +struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr condition; Block post; Block body; }; +/// Break statement (valid within for loop) +struct Break { langutil::SourceLocation location; }; +/// Continue statement (valid within for loop) +struct Continue { langutil::SourceLocation location; }; + +struct LocationExtractor: boost::static_visitor +{ + template langutil::SourceLocation operator()(T const& _node) const + { + return _node.location; + } +}; + +/// Extracts the source location from an inline assembly node. +template inline langutil::SourceLocation locationOf(T const& _node) +{ + return boost::apply_visitor(LocationExtractor(), _node); +} + +} diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libyul/AsmDataForward.h similarity index 79% rename from libsolidity/inlineasm/AsmDataForward.h rename to libyul/AsmDataForward.h index 69cf8f1d9..d01b1dffa 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libyul/AsmDataForward.h @@ -24,11 +24,7 @@ #include -namespace dev -{ -namespace solidity -{ -namespace assembly +namespace yul { struct Instruction; @@ -45,21 +41,14 @@ struct If; struct Switch; struct Case; struct ForLoop; +struct Break; +struct Continue; struct ExpressionStatement; struct Block; struct TypedName; using Expression = boost::variant; -using Statement = boost::variant; - -enum class AsmFlavour -{ - Loose, // no types, EVM instructions as function, jumps and direct stack manipulations - Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations - Yul // same as Strict mode with types -}; +using Statement = boost::variant; } -} -} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libyul/AsmParser.cpp similarity index 53% rename from libsolidity/inlineasm/AsmParser.cpp rename to libyul/AsmParser.cpp index 1f399edca..ae3056f21 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -20,9 +20,10 @@ * Solidity inline assembly parser. */ -#include -#include -#include +#include +#include +#include +#include #include @@ -31,12 +32,16 @@ using namespace std; using namespace dev; -using namespace dev::solidity; -using namespace dev::solidity::assembly; +using namespace langutil; +using namespace yul; -shared_ptr Parser::parse(std::shared_ptr const& _scanner, bool _reuseScanner) +shared_ptr Parser::parse(std::shared_ptr const& _scanner, bool _reuseScanner) { m_recursionDepth = 0; + + _scanner->supportPeriodInIdentifier(true); + ScopeGuard resetScanner([&]{ _scanner->supportPeriodInIdentifier(false); }); + try { m_scanner = _scanner; @@ -47,16 +52,37 @@ shared_ptr Parser::parse(std::shared_ptr const& _scann } catch (FatalError const&) { - if (m_errorReporter.errors().empty()) - throw; // Something is weird here, rather throw again. + solAssert(!m_errorReporter.errors().empty(), "Fatal error detected, but no error is reported."); } + return nullptr; } -assembly::Block Parser::parseBlock() +std::map const& Parser::instructions() +{ + // Allowed instructions, lowercase names. + static map s_instructions; + if (s_instructions.empty()) + { + for (auto const& instruction: dev::eth::c_instructions) + { + if ( + instruction.second == dev::eth::Instruction::JUMPDEST || + dev::eth::isPushInstruction(instruction.second) + ) + continue; + string name = instruction.first; + transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); + s_instructions[name] = instruction.second; + } + } + return s_instructions; +} + +Block Parser::parseBlock() { RecursionGuard recursionGuard(*this); - assembly::Block block = createWithLocation(); + Block block = createWithLocation(); expectToken(Token::LBrace); while (currentToken() != Token::RBrace) block.statements.emplace_back(parseStatement()); @@ -65,7 +91,7 @@ assembly::Block Parser::parseBlock() return block; } -assembly::Statement Parser::parseStatement() +Statement Parser::parseStatement() { RecursionGuard recursionGuard(*this); switch (currentToken()) @@ -78,46 +104,62 @@ assembly::Statement Parser::parseStatement() return parseBlock(); case Token::If: { - assembly::If _if = createWithLocation(); - m_scanner->next(); - _if.condition = make_shared(parseExpression()); + If _if = createWithLocation(); + advance(); + _if.condition = make_unique(parseExpression()); _if.body = parseBlock(); - return _if; + return Statement{move(_if)}; } case Token::Switch: { - assembly::Switch _switch = createWithLocation(); - m_scanner->next(); - _switch.expression = make_shared(parseExpression()); - while (m_scanner->currentToken() == Token::Case) + Switch _switch = createWithLocation(); + advance(); + _switch.expression = make_unique(parseExpression()); + while (currentToken() == Token::Case) _switch.cases.emplace_back(parseCase()); - if (m_scanner->currentToken() == Token::Default) + if (currentToken() == Token::Default) _switch.cases.emplace_back(parseCase()); - if (m_scanner->currentToken() == Token::Default) + if (currentToken() == Token::Default) fatalParserError("Only one default case allowed."); - else if (m_scanner->currentToken() == Token::Case) + else if (currentToken() == Token::Case) fatalParserError("Case not allowed after default case."); if (_switch.cases.empty()) fatalParserError("Switch statement without any cases."); _switch.location.end = _switch.cases.back().body.location.end; - return _switch; + return Statement{move(_switch)}; } case Token::For: return parseForLoop(); + case Token::Break: + { + Statement stmt{createWithLocation()}; + checkBreakContinuePosition("break"); + m_scanner->next(); + return stmt; + } + case Token::Continue: + { + Statement stmt{createWithLocation()}; + checkBreakContinuePosition("continue"); + m_scanner->next(); + return stmt; + } case Token::Assign: { - if (m_flavour != AsmFlavour::Loose) + if (m_dialect.flavour != AsmFlavour::Loose) break; - assembly::StackAssignment assignment = createWithLocation(); + StackAssignment assignment = createWithLocation(); advance(); expectToken(Token::Colon); assignment.variableName.location = location(); assignment.variableName.name = YulString(currentLiteral()); - if (instructions().count(assignment.variableName.name.str())) + if (m_dialect.builtin(assignment.variableName.name)) + fatalParserError("Identifier expected, got builtin symbol."); + else if (instructions().count(assignment.variableName.name.str())) fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); - return assignment; + return Statement{move(assignment)}; } default: break; @@ -127,6 +169,7 @@ assembly::Statement Parser::parseStatement() // literal, // identifier (might turn into label or functional assignment) ElementaryOperation elementary(parseElementaryOperation()); + switch (currentToken()) { case Token::LParen: @@ -135,127 +178,144 @@ assembly::Statement Parser::parseStatement() return ExpressionStatement{locationOf(expr), expr}; } case Token::Comma: + case Token::AssemblyAssign: { - // if a comma follows, a multiple assignment is assumed + std::vector variableNames; - if (elementary.type() != typeid(assembly::Identifier)) - fatalParserError("Label name / variable name must precede \",\" (multiple assignment)."); - assembly::Identifier const& identifier = boost::get(elementary); + while (true) + { + if (elementary.type() != typeid(Identifier)) + { + auto const token = currentToken() == Token::Comma ? "," : ":="; - Assignment assignment = createWithLocation(identifier.location); - assignment.variableNames.emplace_back(identifier); + fatalParserError( + std::string("Variable name must precede \"") + + token + + "\"" + + (currentToken() == Token::Comma ? " in multiple assignment." : " in assignment.") + ); + } + + auto const& identifier = boost::get(elementary); + + if (m_dialect.builtin(identifier.name)) + fatalParserError("Cannot assign to builtin function \"" + identifier.name.str() + "\"."); + + variableNames.emplace_back(identifier); + + if (currentToken() != Token::Comma) + break; - do - { expectToken(Token::Comma); + elementary = parseElementaryOperation(); - if (elementary.type() != typeid(assembly::Identifier)) - fatalParserError("Variable name expected in multiple assignment."); - assignment.variableNames.emplace_back(boost::get(elementary)); } - while (currentToken() == Token::Comma); - expectToken(Token::Colon); - expectToken(Token::Assign); + Assignment assignment = + createWithLocation(boost::get(elementary).location); + assignment.variableNames = std::move(variableNames); + + expectToken(Token::AssemblyAssign); assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; - return assignment; + + return Statement{std::move(assignment)}; } case Token::Colon: { - if (elementary.type() != typeid(assembly::Identifier)) - fatalParserError("Label name / variable name must precede \":\"."); - assembly::Identifier const& identifier = boost::get(elementary); + if (elementary.type() != typeid(Identifier)) + fatalParserError("Label name must precede \":\"."); + + Identifier const& identifier = boost::get(elementary); + advance(); - // identifier:=: should be parsed as identifier: =: (i.e. a label), - // while identifier:= (being followed by a non-colon) as identifier := (assignment). - if (currentToken() == Token::Assign && peekNextToken() != Token::Colon) - { - assembly::Assignment assignment = createWithLocation(identifier.location); - if (m_flavour != AsmFlavour::Yul && instructions().count(identifier.name.str())) - fatalParserError("Cannot use instruction names for identifier names."); - advance(); - assignment.variableNames.emplace_back(identifier); - assignment.value.reset(new Expression(parseExpression())); - assignment.location.end = locationOf(*assignment.value).end; - return assignment; - } - else - { - // label - if (m_flavour != AsmFlavour::Loose) - fatalParserError("Labels are not supported."); - Label label = createWithLocation