diff --git a/.circleci/README.md b/.circleci/README.md index 466aaa76a..b48adba24 100644 --- a/.circleci/README.md +++ b/.circleci/README.md @@ -11,7 +11,7 @@ docker build -t ethereum/solidity-buildpack-deps:ubuntu1904- -f Docker docker push ethereum/solidity-buildpack-deps:ubuntu1904- ``` -The current revision is stored in a [circle ci pipeline parameter](https://github.com/CircleCI-Public/api-preview-docs/blob/master/docs/pipeline-parameters.md#pipeline-parameters) called `docker-image-rev`. Please update the value assigned to this parameter at the time of a docker image update. Please verify that the value assigned to the parameter matches the revision part of the docker image tag (`` in the docker build/push snippet shown above). Otherwise, the docker image used by circle ci and the one actually pushed to docker hub will differ. +The current revisions per docker image are stored in [circle ci pipeline parameters](https://github.com/CircleCI-Public/api-preview-docs/blob/master/docs/pipeline-parameters.md#pipeline-parameters) called `-docker-image-rev` (e.g., `ubuntu-1904-docker-image-rev`). Please update the value assigned to the parameter(s) corresponding to the docker image(s) being updated at the time of the update. Please verify that the value assigned to the parameter matches the revision part of the docker image tag (`` in the docker build/push snippet shown above). Otherwise, the docker image used by circle ci and the one actually pushed to docker hub will differ. Once the docker image has been built and pushed to Dockerhub, you can find it at: diff --git a/.circleci/config.yml b/.circleci/config.yml index c2e64a764..0319ca152 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,9 +7,18 @@ # - ems: Emscripten version: 2.1 parameters: - docker-image-rev: + ubuntu-1804-docker-image-rev: type: string default: "4" + ubuntu-1904-docker-image-rev: + type: string + default: "4" + ubuntu-1904-clang-docker-image-rev: + type: string + default: "5" + ubuntu-1604-clang-ossfuzz-docker-image-rev: + type: string + default: "2" defaults: @@ -62,6 +71,11 @@ defaults: path: build/solc/solc destination: solc + # compiled tool executable target + - artifacts_tools: &artifacts_tools + path: build/tools/solidity-upgrade + destination: solidity-upgrade + # compiled executable targets - artifacts_executables: &artifacts_executables root: build @@ -108,9 +122,20 @@ defaults: name: command line tests command: ./test/cmdlineTests.sh + - test_ubuntu1604_clang: &test_ubuntu1604_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >> + 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-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >> steps: - checkout - attach_workspace: @@ -121,7 +146,7 @@ defaults: - test_ubuntu1904: &test_ubuntu1904 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >> steps: - checkout - attach_workspace: @@ -155,6 +180,11 @@ defaults: requires: - b_ubu + - workflow_ubuntu1604_clang: &workflow_ubuntu1604_clang + <<: *workflow_trigger_on_tags + requires: + - b_ubu_ossfuzz + - workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang <<: *workflow_trigger_on_tags requires: @@ -185,7 +215,7 @@ defaults: requires: - b_ems - - workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz + - workflow_ubuntu1604_ossfuzz: &workflow_ubuntu1604_ossfuzz <<: *workflow_trigger_on_tags requires: - b_ubu_ossfuzz @@ -257,6 +287,22 @@ jobs: name: Check for C++ coding style command: ./scripts/check_style.sh + chk_pylint: + docker: + - image: buildpack-deps:eoan + steps: + - checkout + - run: + name: Install pip + command: apt -q update && apt install -y python3-pip + - run: + name: Install pylint + command: python3 -m pip install pylint z3-solver pygments-lexer-solidity + # also z3-solver to make sure pylint knows about this module, pygments-lexer-solidity for docs + - run: + name: Linting Python Scripts + command: ./scripts/pylint_all.py + chk_buglist: docker: - image: circleci/node @@ -291,11 +337,10 @@ jobs: b_ubu_clang: &build_ubuntu1904_clang docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1904-clang-<< pipeline.parameters.ubuntu-1904-clang-docker-image-rev >> environment: CC: clang CXX: clang++ - CMAKE_OPTIONS: -DLLL=ON steps: - checkout - run: *run_build @@ -304,26 +349,24 @@ jobs: b_ubu: &build_ubuntu1904 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> - environment: - CMAKE_OPTIONS: -DLLL=ON + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >> steps: - checkout - run: *run_build - store_artifacts: *artifacts_solc + - store_artifacts: *artifacts_tools - persist_to_workspace: *artifacts_executables b_ubu_release: &build_ubuntu1904_release <<: *build_ubuntu1904 environment: FORCE_RELEASE: ON - CMAKE_OPTIONS: -DLLL=ON b_ubu18: &build_ubuntu1804 docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1804-<< pipeline.parameters.ubuntu-1804-docker-image-rev >> environment: - CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 -DLLL=ON + CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 CMAKE_BUILD_TYPE: RelWithDebugInfo steps: - checkout @@ -368,17 +411,18 @@ jobs: <<: *build_ubuntu1904 environment: CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF -DLLL=ON + CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF steps: - checkout - run: *run_build - b_ubu_ossfuzz: - <<: *build_ubuntu1904_clang + b_ubu_ossfuzz: &build_ubuntu1604_clang + docker: + - image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >> environment: - TERM: xterm CC: clang CXX: clang++ + TERM: xterm CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake steps: - checkout @@ -387,7 +431,7 @@ jobs: - persist_to_workspace: *artifacts_executables_ossfuzz t_ubu_ossfuzz: &t_ubu_ossfuzz - <<: *test_ubuntu1904_clang + <<: *test_ubuntu1604_clang steps: - checkout - attach_workspace: @@ -395,8 +439,8 @@ jobs: - run: name: Regression tests command: | + git clone https://github.com/ethereum/solidity-fuzzing-corpus /tmp/solidity-fuzzing-corpus 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 @@ -408,7 +452,6 @@ jobs: - image: archlinux/base environment: TERM: xterm - CMAKE_OPTIONS: -DLLL=ON steps: - run: name: Install build dependencies @@ -425,7 +468,6 @@ jobs: environment: TERM: xterm CMAKE_BUILD_TYPE: Debug - CMAKE_OPTIONS: -DLLL=ON steps: - checkout - restore_cache: @@ -445,6 +487,7 @@ jobs: - /usr/local/Homebrew - run: *run_build - store_artifacts: *artifacts_solc + - store_artifacts: *artifacts_tools - persist_to_workspace: *artifacts_build_dir t_osx_soltest: @@ -529,7 +572,7 @@ jobs: b_docs: docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >> steps: - checkout - run: *setup_prerelease_commit_hash @@ -554,7 +597,7 @@ jobs: t_ubu_cli: &t_ubu_cli docker: - - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.docker-image-rev >> + - image: ethereum/solidity-buildpack-deps:ubuntu1904-<< pipeline.parameters.ubuntu-1904-docker-image-rev >> environment: TERM: xterm steps: @@ -724,6 +767,7 @@ workflows: # DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags - chk_buglist: *workflow_trigger_on_tags - chk_proofs: *workflow_trigger_on_tags + - chk_pylint: *workflow_trigger_on_tags # build-only - b_docs: *workflow_trigger_on_tags @@ -775,7 +819,7 @@ workflows: jobs: # OSSFUZZ builds and (regression) tests - b_ubu_ossfuzz: *workflow_trigger_on_tags - - t_ubu_ossfuzz: *workflow_ubuntu1904_ossfuzz + - t_ubu_ossfuzz: *workflow_ubuntu1604_ossfuzz # Code Coverage enabled build and tests - b_ubu_codecov: *workflow_trigger_on_tags diff --git a/.circleci/docker/Dockerfile.ubuntu1604.clang.ossfuzz b/.circleci/docker/Dockerfile.ubuntu1604.clang.ossfuzz new file mode 100644 index 000000000..f7dfc7001 --- /dev/null +++ b/.circleci/docker/Dockerfile.ubuntu1604.clang.ossfuzz @@ -0,0 +1,101 @@ +# vim:syntax=dockerfile +#------------------------------------------------------------------------------ +# Dockerfile for building and testing Solidity Compiler on CI +# Target: Ubuntu 16.04 (Xenial Xerus) ossfuzz 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 gcr.io/oss-fuzz-base/base-clang as base + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update; \ + apt-get -qqy install --no-install-recommends \ + build-essential \ + software-properties-common \ + ninja-build git wget \ + libbz2-dev zlib1g-dev git curl; \ + apt-get install -qy python-pip python-sphinx; + +# Install cmake 3.14 (minimum requirement is cmake 3.10) +RUN wget https://github.com/Kitware/CMake/releases/download/v3.14.5/cmake-3.14.5-Linux-x86_64.sh; \ + chmod +x cmake-3.14.5-Linux-x86_64.sh; \ + ./cmake-3.14.5-Linux-x86_64.sh --skip-license --prefix="/usr" + +FROM base AS libraries + +# Boost +RUN git clone -b boost-1.69.0 https://github.com/boostorg/boost.git \ + /usr/src/boost; \ + cd /usr/src/boost; \ + git submodule update --init --recursive; \ + ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ + ./b2 toolset=clang cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" headers; \ + ./b2 toolset=clang cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" \ + link=static variant=release runtime-link=static \ + system 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.7 https://github.com/Z3Prover/z3.git \ + /usr/src/z3; \ + cd /usr/src/z3; \ + mkdir build; \ + cd build; \ + LDFLAGS=$CXXFLAGS cmake -DZ3_BUILD_LIBZ3_SHARED=OFF -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=Release ..; \ + make libz3 -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 3521f47a2828da9ace403e4ecc4aece1a84feb36; \ + 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 + +# EVMONE +RUN set -ex; \ + cd /usr/src; \ + git clone --branch="v0.4.0" --recurse-submodules https://github.com/ethereum/evmone.git; \ + cd evmone; \ + mkdir build; \ + cd build; \ + cmake -G Ninja -DBUILD_SHARED_LIBS=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \ + ninja; \ + ninja install/strip; \ + rm -rf /usr/src/evmone + +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.clang.ubuntu1904 b/.circleci/docker/Dockerfile.ubuntu1904.clang similarity index 95% rename from .circleci/docker/Dockerfile.clang.ubuntu1904 rename to .circleci/docker/Dockerfile.ubuntu1904.clang index 14c9b1339..cf790c84e 100644 --- a/.circleci/docker/Dockerfile.clang.ubuntu1904 +++ b/.circleci/docker/Dockerfile.ubuntu1904.clang @@ -51,9 +51,10 @@ ENV CC clang ENV CXX clang++ # Boost -RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git \ +RUN git clone -b boost-1.69.0 https://github.com/boostorg/boost.git \ /usr/src/boost; \ cd /usr/src/boost; \ + git submodule update --init --recursive; \ ./bootstrap.sh --with-toolset=clang --prefix=/usr; \ ./b2 toolset=clang headers; \ ./b2 toolset=clang variant=release \ @@ -76,7 +77,7 @@ RUN set -ex; \ git clone https://github.com/google/libprotobuf-mutator.git \ /usr/src/libprotobuf-mutator; \ cd /usr/src/libprotobuf-mutator; \ - git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \ + git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \ mkdir build; \ cd build; \ cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \ diff --git a/.gitignore b/.gitignore index bbdf9d68d..f6eeba75f 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ docs/utils/*.pyc /deps/downloads/ deps/install deps/cache +cmake-build-debug/ # vim stuff [._]*.sw[a-p] diff --git a/CMakeLists.txt b/CMakeLists.txt index a2ff58e92..534698874 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,10 +19,7 @@ if (IS_BIG_ENDIAN) message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.") endif() -option(LLL "Build LLL" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) -option(LLLC_LINK_STATIC "Link lllc executable statically on supported platforms" OFF) -option(INSTALL_LLLC "Include lllc executable in installation" ${LLL}) # Setup cccache. include(EthCcache) @@ -58,13 +55,10 @@ add_subdirectory(libevmasm) add_subdirectory(libyul) add_subdirectory(libsolidity) add_subdirectory(libsolc) +add_subdirectory(tools) if (NOT EMSCRIPTEN) add_subdirectory(solc) - if (LLL) - add_subdirectory(liblll) - add_subdirectory(lllc) - endif() endif() if (TESTS AND NOT EMSCRIPTEN) diff --git a/Changelog.md b/Changelog.md index 0f9b147f1..9b3d3de27 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,17 +9,62 @@ Compiler Features: Bugfixes: -### 0.6.2 (unreleased) +### 0.6.4 (unreleased) Language Features: - * Allow accessing external functions via contract and interface names to obtain their selector. + * Inline Assembly: Allow assigning to `_slot` of local storage variable pointers. Compiler Features: - * General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon). - * Yul Optimizer: Apply penalty when trying to rematerialize into loops. + * AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources. + Bugfixes: + * isoltest: Added new keyword `wei` to express function value in semantic tests + * Standard-JSON-Interface: Fix a bug related to empty filenames and imports. + + +### 0.6.3 (2020-02-18) + +Language Features: + * Allow contract types and enums as keys for mappings. + * Allow function selectors to be used as compile-time constants. + * Report source locations for structured documentation errors. + + +Compiler Features: + * AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions. + * Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input. + * Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode. + * Yul Optimizer: Prune functions that call each other but are otherwise unreferenced. + + +Bugfixes: + * Assembly: Added missing `source` field to legacy assembly json output to complete the source reference. + * Parser: Fix an internal error for ``abstract`` without ``contract``. + * Type Checker: Make invalid calls to uncallable types fatal errors instead of regular. + + +### 0.6.2 (2020-01-27) + +Language Features: + * Allow accessing external functions via contract and interface names to obtain their selector. + * Allow interfaces to inherit from other interfaces + * Allow gas and value to be set in external function calls using ``c.f{gas: 10000, value: 4 ether}()``. + * Allow specifying the ``salt`` for contract creations and thus the ``create2`` opcode using ``new C{salt: 0x1234, value: 1 ether}(arg1, arg2)``. + * Inline Assembly: Support literals ``true`` and ``false``. + + +Compiler Features: + * LLL: The LLL compiler has been removed. + * General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon). + * General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing. + * Yul Optimizer: Apply penalty when trying to rematerialize into loops. + + +Bugfixes: + * Commandline interface: Only activate yul optimizer if ``--optimize`` is given. + * Fixes internal compiler error on explicitly calling unimplemented base functions. Build System: diff --git a/README.md b/README.md index 8eef61b82..f5419e652 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # The Solidity Contract-Oriented Programming Language -[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +You can talk to us on [![solidity at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge). Questions, feedback and suggestions are welcome! Solidity is a statically typed, contract-oriented, high-level language for implementing smart contracts on the Ethereum platform. diff --git a/appveyor.yml b/appveyor.yml index 2e2a2fa8b..d7211e14b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -64,14 +64,14 @@ build_script: - msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal - cd %APPVEYOR_BUILD_FOLDER% - scripts\release.bat %CONFIGURATION% 2017 - - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short + - ps: $bytecodedir = git show -s --format="%cd-%H" --date="format:%Y-%m-%d-%H-%M" test_script: - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - 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) { + - ps: if ($env:priv_key -and -not $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir } - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 18b4e9fe5..cc4489e9f 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -34,6 +34,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-Wall) add_compile_options(-Wextra) add_compile_options(-Werror) + add_compile_options(-pedantic) + add_compile_options(-Wno-unknown-pragmas) + add_compile_options(-Wimplicit-fallthrough) # Configuration-specific compiler settings. set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG") @@ -51,6 +54,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.") endif () + # Use fancy colors in the compiler diagnostics + add_compile_options(-fdiagnostics-color) + # Additional Clang-specific compiler settings. elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") diff --git a/cmake/EthOptions.cmake b/cmake/EthOptions.cmake index 6dca5e1be..cd6fe8133 100644 --- a/cmake/EthOptions.cmake +++ b/cmake/EthOptions.cmake @@ -41,7 +41,6 @@ if (SUPPORT_TOOLS) endif() message("------------------------------------------------------------------ flags") message("-- OSSFUZZ ${OSSFUZZ}") - message("-- LLL ${LLL}") message("------------------------------------------------------------------------") message("") endmacro() diff --git a/cmake/toolchains/libfuzzer.cmake b/cmake/toolchains/libfuzzer.cmake index 354e6d09b..a734df975 100644 --- a/cmake/toolchains/libfuzzer.cmake +++ b/cmake/toolchains/libfuzzer.cmake @@ -7,5 +7,9 @@ set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE) 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) +# clang/libfuzzer specific flags for UBSan instrumentation +set(CMAKE_CXX_FLAGS "-O1 -fno-omit-frame-pointer -gline-tables-only -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -I /usr/local/include/c++/v1 -fsanitize=undefined -fsanitize=fuzzer-no-link -stdlib=libc++" CACHE STRING "Custom compilation flags" FORCE) +# Link statically against boost libraries +set(BOOST_FOUND ON CACHE BOOL "" FORCE) +set(Boost_USE_STATIC_LIBS ON CACHE BOOL "Link against static Boost libraries" FORCE) +set(Boost_USE_STATIC_RUNTIME ON CACHE BOOL "Link against static Boost runtime library" FORCE) diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index 383d2440e..e45ec6c21 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -63,7 +63,7 @@ This section highlights changes that affect syntax and semantics. last one only works for value types). Change every ``keccak256(a, b, c)`` to ``keccak256(abi.encodePacked(a, b, c))``. Even though it is not a breaking change, it is suggested that developers change - ``x.call(bytes4(keccak256("f(uint256)"), a, b)`` to + ``x.call(bytes4(keccak256("f(uint256)")), a, b)`` to ``x.call(abi.encodeWithSignature("f(uint256)", a, b))``. * Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return @@ -455,7 +455,7 @@ New version: uint z = someInteger(); x += z; // Throw is now disallowed. - require(x > 100); + require(x <= 100); int y = -3 >> 1; require(y == -2); do { diff --git a/docs/assembly.rst b/docs/assembly.rst index 632a892d1..bce2ed32f 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -1,41 +1,20 @@ -################# -Solidity Assembly -################# +.. _inline-assembly: + +############### +Inline Assembly +############### .. index:: ! assembly, ! asm, ! evmasm -Solidity defines an assembly language that you can use without Solidity and also -as "inline assembly" inside Solidity source code. This guide starts with describing -how to use inline assembly, how it differs from standalone assembly -(sometimes also referred to by its proper name "Yul"), and -specifies assembly itself. - -.. _inline-assembly: - -Inline Assembly -=============== You can interleave Solidity statements with inline assembly in a language close -to the one of the virtual machine. This gives you more fine-grained control, -especially when you are enhancing the language by writing libraries. +to the one of the Ethereum virtual machine. This gives you more fine-grained control, +which is especially useful when you are enhancing the language by writing libraries. -As the EVM is a stack machine, it is often hard to address the correct stack slot -and provide arguments to opcodes at the correct point on the stack. Solidity's inline -assembly helps you do this, and with other issues that arise when writing manual assembly. +The language used for inline assembly in Solidity is called `Yul `_ +and it is documented in its own section. This section will only cover +how the inline assembly code can interface with the surrounding Solidity code. -For inline assembly, the stack is actually not visible at all, but if you look -closer, there is always a very direct translation from inline assembly to -the stack based EVM opcode stream. - -Inline assembly has the following features: - -* functional-style opcodes: ``mul(1, add(2, 3))`` -* assembly-local variables: ``let x := add(2, 3) let y := mload(0x40) x := add(x, y)`` -* access to external variables: ``function f(uint x) public { assembly { x := sub(x, 1) } }`` -* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }`` -* if statements: ``if slt(x, 0) { x := sub(0, x) }`` -* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }`` -* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }`` .. warning:: Inline assembly is a way to access the Ethereum Virtual Machine @@ -43,24 +22,14 @@ Inline assembly has the following features: features and checks of Solidity. You should only use it for tasks that need it, and only if you are confident with using it. -Syntax ------- -Assembly parses comments, literals and identifiers in the same way as Solidity, so you can use the -usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inline assembly can contain -``.``. Inline assembly is marked by ``assembly { ... }`` and inside -these curly braces, you can use the following (see the later sections for more details): +An inline assembly block is marked by ``assembly { ... }``, where the code inside +the curly braces is code in the `Yul `_ language. - - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - - opcodes in functional style, e.g. ``add(1, mload(0))`` - - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) - - identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)`` - - assignments, e.g. ``x := add(y, 3)`` - - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` +The inline assembly code can access local Solidity variables as explained below. -Inline assembly manages local variables and control-flow. Because of that, -opcodes that interfere with these features are not available. This includes -the ``dup`` and ``swap`` instructions as well as ``jump`` instructions and labels. +Different inline assembly blocks share no namespace, i.e. it is not possible +to call a Yul function or access a Yul variable defined in a different inline assembly block. Example ------- @@ -146,238 +115,20 @@ efficient code, for example: } -.. _opcodes: - -Opcodes -------- - -This document does not want to be a full description of the Ethereum virtual machine, but the -following list can be used as a quick reference of its opcodes. - -If an opcode takes arguments, they are given in parentheses. -Opcodes marked with ``-`` do not return a result, -those marked with ``*`` are special in a certain way and all others return exactly one value. -Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, -Byzantium, Constantinople or Istanbul, respectively. - -In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to -but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. - -In the grammar, opcodes are represented as pre-defined identifiers ("built-in functions"). - -+-------------------------+-----+---+-----------------------------------------------------------------+ -| Instruction | | | Explanation | -+=========================+=====+===+=================================================================+ -| stop() + `-` | F | stop execution, identical to return(0, 0) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| add(x, y) | | F | x + y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sub(x, y) | | F | x - y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mul(x, y) | | F | x * y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| div(x, y) | | F | x / y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sdiv(x, y) | | F | x / y, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mod(x, y) | | F | x % y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| smod(x, y) | | F | x % y, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| exp(x, y) | | F | x to the power of y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| not(x) | | F | ~x, every bit of x is negated | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| lt(x, y) | | F | 1 if x < y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gt(x, y) | | F | 1 if x > y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| slt(x, y) | | F | 1 if x < y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sgt(x, y) | | F | 1 if x > y, 0 otherwise, for signed numbers in two's complement | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| eq(x, y) | | F | 1 if x == y, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| iszero(x) | | F | 1 if x == 0, 0 otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| and(x, y) | | F | bitwise "and" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| or(x, y) | | F | bitwise "or" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| xor(x, y) | | F | bitwise "xor" of x and y | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| shl(x, y) | | C | logical shift left y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| shr(x, y) | | C | logical shift right y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sar(x, y) | | C | signed arithmetic shift right y by x bits | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mulmod(x, y, m) | | F | (x * y) % m with arbitrary precision arithmetic | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| signextend(i, x) | | F | sign extend from (i*8+7)th bit counting from least significant | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| keccak256(p, n) | | F | keccak(mem[p...(p+n))) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pc() | | F | current position in code | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| pop(x) | `-` | F | discard value x | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mload(p) | | F | mem[p...(p+32)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mstore(p, v) | `-` | F | mem[p...(p+32)) := v | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| mstore8(p, v) | `-` | F | mem[p] := v & 0xff (only modifies a single byte) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sload(p) | | F | storage[p] | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| sstore(p, v) | `-` | F | storage[p] := v | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| msize() | | F | size of memory, i.e. largest accessed memory index | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gas() | | F | gas still available to execution | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| address() | | F | address of the current contract / execution context | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| balance(a) | | F | wei balance at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| selfbalance() | | I | equivalent to balance(address()), but cheaper | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| caller() | | F | call sender (excluding ``delegatecall``) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| callvalue() | | F | wei sent together with the current call | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldataload(p) | | F | call data starting from position p (32 bytes) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldatasize() | | F | size of call data in bytes | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| codesize() | | F | size of the code of the current contract / execution context | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodesize(a) | | F | size of the code at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| returndatasize() | | B | size of the last returndata | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| extcodehash(a) | | C | code hash of address a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | -| | | | and return the new address | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| create2(v, p, n, s) | | C | create new contract with code mem[p...(p+n)) at address | -| | | | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | -| | | | and send v wei and return the new address, where ``0xff`` is a | -| | | | 1 byte value, ``this`` is the current contract's address | -| | | | as a 20 byte value and ``s`` is a big-endian 256-bit value | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| call(g, a, v, in, | | F | call contract at address a with input mem[in...(in+insize)) | -| insize, out, outsize) | | | providing g gas and v wei and output area | -| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) | -| | | | and 1 on success | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay | -| insize, out, outsize) | | | in the context of the current contract otherwise | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` | -| insize, out, outsize) | | | and ``callvalue`` | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do | -| insize, out, outsize) | | | not allow state modifications | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| revert(p, s) | `-` | B | end execution, revert state changes, return data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| invalid() | `-` | F | end execution with invalid instruction | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log1(p, s, t1) | `-` | F | log with topic t1 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log2(p, s, t1, t2) | `-` | F | log with topics t1, t2 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log3(p, s, t1, t2, t3) | `-` | F | log with topics t1, t2, t3 and data mem[p...(p+s)) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | -| t4) | | | | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| chainid() | | I | ID of the executing chain (EIP 1344) | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| origin() | | F | transaction sender | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gasprice() | | F | gas price of the transaction | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| coinbase() | | F | current mining beneficiary | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| timestamp() | | F | timestamp of the current block in seconds since the epoch | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| number() | | F | current block number | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| difficulty() | | F | difficulty of the current block | -+-------------------------+-----+---+-----------------------------------------------------------------+ -| gaslimit() | | F | block gas limit of the current block | -+-------------------------+-----+---+-----------------------------------------------------------------+ - -Literals --------- - -You can use integer constants by typing them in decimal or hexadecimal notation and an -appropriate ``PUSHi`` instruction will automatically be generated. The following creates code -to add 2 and 3 resulting in 5 and then computes the bitwise ``AND`` with the string "abc". -The final value is assigned to a local variable called ``x``. -Strings are stored left-aligned and cannot be longer than 32 bytes. - -.. code:: - - assembly { let x := and("abc", add(3, 2)) } - - -Functional Style ------------------ - -For a sequence of opcodes, it is often hard to see what the actual -arguments for certain opcodes are. In the following example, -``3`` is added to the contents in memory at position ``0x80``. - -.. code:: - - 3 0x80 mload add 0x80 mstore - -Solidity inline assembly has a "functional style" notation where the same code -would be written as follows: - -.. code:: - - mstore(0x80, add(mload(0x80), 3)) - -If you read the code from right to left, you end up with exactly the same -sequence of constants and opcodes, but it is much clearer where the -values end up. - -If you care about the exact stack layout, just note that the -syntactically first argument for a function or opcode will be put at the -top of the stack. Access to External Variables, Functions and Libraries ----------------------------------------------------- You can access Solidity variables and other identifiers by using their name. -For variables stored in the memory data location, this pushes the address, and not the value -onto the stack. Variables stored in the storage data location are different, as they might not -occupy a full storage slot, so their "address" is composed of a slot and a byte-offset + +Local variables of value type are directly usable in inline assembly. + +Local variables that refer to memory or calldata evaluate to the +address of the variable in memory, resp. calldata, not the value itself. + +For local storage variables or state variables, a single Yul identifier +is not sufficient, since they do not necessarily occupy a single full storage slot. +Therefore, their "address" is composed of a slot and a byte-offset inside that slot. To retrieve the slot pointed to by the variable ``x``, you use ``x_slot``, and to retrieve the byte-offset you use ``x_offset``. @@ -391,7 +142,9 @@ Local Solidity variables are available for assignments, for example: uint b; function f(uint x) public view returns (uint r) { assembly { - r := mul(x, sload(b_slot)) // ignore the offset, we know it is zero + // We ignore the storage slot offset, we know it is zero + // in this special case. + r := mul(x, sload(b_slot)) } } } @@ -407,177 +160,24 @@ Local Solidity variables are available for assignments, for example: To clean signed types, you can use the ``signextend`` opcode: ``assembly { signextend(, x) }`` -Declaring Assembly-Local Variables ----------------------------------- -You can use the ``let`` keyword to declare variables that are only visible in -inline assembly and actually only in the current ``{...}``-block. What happens -is that the ``let`` instruction will create a new stack slot that is reserved -for the variable and automatically removed again when the end of the block -is reached. You need to provide an initial value for the variable which can -be just ``0``, but it can also be a complex functional-style expression. - -Since 0.6.0 the name of a declared variable may not end in ``_offset`` or ``_slot`` +Since Solidity 0.6.0 the name of a inline assembly variable may not end in ``_offset`` or ``_slot`` and it may not shadow any declaration visible in the scope of the inline assembly block (including variable, contract and function declarations). Similarly, if the name of a declared variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any declaration visible in the scope of the inline assembly block. -.. code:: - - pragma solidity >=0.4.16 <0.8.0; - - contract C { - function f(uint x) public view returns (uint b) { - assembly { - let v := add(x, 1) - mstore(0x80, v) - { - let y := add(sload(v), 1) - b := y - } // y is "deallocated" here - b := add(b, v) - } // v is "deallocated" here - } - } - - -Assignments ------------ Assignments are possible to assembly-local variables and to function-local variables. Take care that when you assign to variables that point to memory or storage, you will only change the pointer and not the data. -Variables can only be assigned expressions that result in exactly one value. -If you want to assign the values returned from a function that has -multiple return parameters, you have to provide multiple variables. +You can assign to the ``_slot`` part of a local storage variable pointer. +For these (structs, arrays or mappings), the ``_offset`` part is always zero. +It is not possible to assign to the ``_slot`` or ``_offset`` part of a state variable, +though. -.. code:: - { - let v := 0 - let g := add(v, 2) - function f() -> a, b { } - let c, d := f() - } - -If --- - -The if statement can be used for conditionally executing code. -There is no "else" part, consider using "switch" (see below) if -you need multiple alternatives. - -.. code:: - - { - if eq(value, 0) { revert(0, 0) } - } - -The curly braces for the body are required. - -Switch ------- - -You can use a switch statement as a very basic version of "if/else". -It takes the value of an expression and compares it to several constants. -The branch corresponding to the matching constant is taken. Contrary to the -error-prone behaviour of some programming languages, control flow does -not continue from one case to the next. There can be a fallback or default -case called ``default``. - -.. code:: - - { - let x := 0 - switch calldataload(4) - case 0 { - x := calldataload(0x24) - } - default { - x := calldataload(0x44) - } - sstore(0, div(x, 2)) - } - -The list of cases does not require curly braces, but the body of a -case does require them. - -Loops ------ - -Assembly supports a simple for-style loop. For-style loops have -a header containing an initializing part, a condition and a post-iteration -part. The condition has to be a functional-style expression, while -the other two are blocks. If the initializing part -declares any variables, the scope of these variables is extended into the -body (including the condition and the post-iteration part). - -The ``break`` and ``continue`` statements can be used to exit the loop -or skip to the post-part, respectively. - -The following example computes the sum of an area in memory. - -.. code:: - - { - let x := 0 - for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } { - x := add(x, mload(i)) - } - } - -For loops can also be written so that they behave like while loops: -Simply leave the initialization and post-iteration parts empty. - -.. code:: - - { - let x := 0 - let i := 0 - for { } lt(i, 0x100) { } { // while(i < 0x100) - x := add(x, mload(i)) - i := add(i, 0x20) - } - } - -Functions ---------- - -Assembly allows the definition of low-level functions. These take their -arguments (and a return PC) from the stack and also put the results onto the -stack. Calling a function looks the same way as executing a functional-style -opcode. - -Functions can be defined anywhere and are visible in the block they are -declared in. Inside a function, you cannot access local variables -defined outside of that function. - -If you call a function that returns multiple values, you have to assign -them to a tuple using ``a, b := f(x)`` or ``let a, b := f(x)``. - -The ``leave`` statement can be used to exit the current function. It -works like the ``return`` statement in other languages just that it does -not take a value to return, it just exits the functions and the function -will return whatever values are currently assigned to the return variable(s). - -The following example implements the power function by square-and-multiply. - -.. code:: - - { - function power(base, exponent) -> result { - switch exponent - case 0 { result := 1 } - case 1 { result := base } - default { - result := power(mul(base, base), div(exponent, 2)) - switch mod(exponent, 2) - case 1 { result := mul(base, result) } - } - } - } Things to Avoid --------------- @@ -593,7 +193,8 @@ Conventions in Solidity ----------------------- In contrast to EVM assembly, Solidity has types which are narrower than 256 bits, -e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256 +e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that +types can be shorter than 256 bits, and the higher-order bits are cleaned when necessary, i.e., shortly before they are written to memory or before comparisons are performed. This means that if you access such a variable @@ -629,158 +230,3 @@ first slot of the array and followed by the array elements. Statically-sized memory arrays do not have a length field, but it might be added later to allow better convertibility between statically- and dynamically-sized arrays, so do not rely on this. - - -Standalone Assembly -=================== - -The assembly language described as inline assembly above can also be used -standalone and in fact, the plan is to use it as an intermediate language -for the Solidity compiler. In this form, it tries to achieve several goals: - -1. Programs written in it should be readable, even if the code is generated by a compiler from Solidity. -2. The translation from assembly to bytecode should contain as few "surprises" as possible. -3. Control flow should be easy to detect to help in formal verification and optimization. - -In order to achieve the first and last goal, assembly provides high-level constructs -like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible -to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``, -``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow -and the last two obfuscate control flow. Furthermore, functional statements of -the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like -``7 y x add mul`` because in the first form, it is much easier to see which -operand is used for which opcode. - -The second goal is achieved by compiling the -higher level constructs to bytecode in a very regular way. -The only non-local operation performed -by the assembler is name lookup of user-defined identifiers (functions, variables, ...), -which follow very simple and regular scoping rules and cleanup of local variables from the stack. - -Scoping: An identifier that is declared (label, variable, function, assembly) -is only visible in the block where it was declared (including nested blocks -inside the current block). It is not legal to access local variables across -function borders, even if they would be in scope. Shadowing is not allowed. -Local variables cannot be accessed before they were declared, but -functions and assemblies can. Assemblies are special blocks that are used -for e.g. returning runtime code or creating contracts. No identifier from an -outer assembly is visible in a sub-assembly. - -If control flow passes over the end of a block, pop instructions are inserted -that match the number of local variables declared in that block. -Whenever a local variable is referenced, the code generator needs -to know its current relative position in the stack and thus it needs to -keep track of the current so-called stack height. Since all local variables -are removed at the end of a block, the stack height before and after the block -should be the same. If this is not the case, compilation fails. - -Using ``switch``, ``for`` and functions, it should be possible to write -complex code without using ``jump`` or ``jumpi`` manually. This makes it much -easier to analyze the control flow, which allows for improved formal -verification and optimization. - -Furthermore, if manual jumps are allowed, computing the stack height is rather complicated. -The position of all local variables on the stack needs to be known, otherwise -neither references to local variables nor removing local variables automatically -from the stack at the end of a block will work properly. - -Example: - -We will follow an example compilation from Solidity to assembly. -We consider the runtime bytecode of the following Solidity program:: - - pragma solidity >=0.4.16 <0.8.0; - - - contract C { - function f(uint x) public pure returns (uint y) { - y = 1; - for (uint i = 0; i < x; i++) - y = 2 * y; - } - } - -The following assembly will be generated:: - - { - mstore(0x40, 0x80) // store the "free memory pointer" - // function dispatcher - switch div(calldataload(0), exp(2, 226)) - case 0xb3de648b { - let r := f(calldataload(4)) - let ret := $allocate(0x20) - mstore(ret, r) - return(ret, 0x20) - } - default { revert(0, 0) } - // memory allocator - function $allocate(size) -> pos { - pos := mload(0x40) - mstore(0x40, add(pos, size)) - } - // the contract function - function f(x) -> y { - y := 1 - for { let i := 0 } lt(i, x) { i := add(i, 1) } { - y := mul(2, y) - } - } - } - - -Assembly Grammar ----------------- - -The tasks of the parser are the following: - -- Turn the byte stream into a token stream, discarding C++-style comments - (a special comment exists for source references, but we will not explain it here). -- Turn the token stream into an AST according to the grammar below -- Register identifiers with the block they are defined in (annotation to the - AST node) and note from which point on, variables can be accessed. - -The assembly lexer follows the one defined by Solidity itself. - -Whitespace is used to delimit tokens and it consists of the characters -Space, Tab and Linefeed. Comments are regular JavaScript/C++ comments and -are interpreted in the same way as Whitespace. - -Grammar:: - - AssemblyBlock = '{' AssemblyItem* '}' - AssemblyItem = - Identifier | - AssemblyBlock | - AssemblyExpression | - AssemblyLocalDefinition | - AssemblyAssignment | - AssemblyIf | - AssemblySwitch | - AssemblyFunctionDefinition | - AssemblyFor | - 'break' | - 'continue' | - 'leave' | - SubAssembly - AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral - AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral - Identifier = [a-zA-Z_$] [a-zA-Z_0-9.]* - AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')' - AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )? - AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression - IdentifierOrList = Identifier | '(' IdentifierList ')' - IdentifierList = Identifier ( ',' Identifier)* - AssemblyIf = 'if' AssemblyExpression AssemblyBlock - AssemblySwitch = 'switch' AssemblyExpression AssemblyCase* - ( 'default' AssemblyBlock )? - AssemblyCase = 'case' AssemblyExpression AssemblyBlock - AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')' - ( '->' '(' IdentifierList ')' )? AssemblyBlock - AssemblyFor = 'for' ( AssemblyBlock | AssemblyExpression ) - AssemblyExpression ( AssemblyBlock | AssemblyExpression ) AssemblyBlock - SubAssembly = 'assembly' Identifier AssemblyBlock - NumberLiteral = HexNumber | DecimalNumber - HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') - StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' - HexNumber = '0x' [0-9a-fA-F]+ - DecimalNumber = [0-9]+ diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 4c9a01c64..3bdc7d9e5 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -880,5 +880,13 @@ "0.6.1": { "bugs": [], "released": "2020-01-02" + }, + "0.6.2": { + "bugs": [], + "released": "2020-01-27" + }, + "0.6.3": { + "bugs": [], + "released": "2020-02-18" } } \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 87a6ec030..c8d60690b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,8 @@ import sys import os import re +from pygments_lexer_solidity import SolidityLexer + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -24,7 +26,6 @@ import re def setup(sphinx): thisdir = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, thisdir + '/utils') - from pygments_lexer_solidity import SolidityLexer sphinx.add_lexer('Solidity', SolidityLexer()) sphinx.add_stylesheet('css/custom.css') @@ -53,7 +54,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016-2019, Ethereum' +copyright = '2016-2020, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/contracts/constant-state-variables.rst b/docs/contracts/constant-state-variables.rst index 5a50ee6e6..327bb7b02 100644 --- a/docs/contracts/constant-state-variables.rst +++ b/docs/contracts/constant-state-variables.rst @@ -29,7 +29,7 @@ value types and strings. pragma solidity >=0.4.0 <0.8.0; contract C { - uint constant x = 32**22 + 8; - string constant text = "abc"; - bytes32 constant myHash = keccak256("abc"); + uint constant X = 32**22 + 8; + string constant TEXT = "abc"; + bytes32 constant MY_HASH = keccak256("abc"); } diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 7b04285b7..8100d1ec2 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -335,7 +335,7 @@ operations as long as there is enough gas passed on to it. :: - pragma solidity >=0.6.0 <0.8.0; + pragma solidity >0.6.1 <0.8.0; contract Test { // This function is called for all messages sent to @@ -382,7 +382,7 @@ operations as long as there is enough gas passed on to it. (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); require(success); // results in test.x becoming == 1 and test.y becoming 0. - (success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()")); + (success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()")); require(success); // results in test.x becoming == 1 and test.y becoming 1. diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst index 8c4fb25e0..1b69aba4c 100644 --- a/docs/contracts/interfaces.rst +++ b/docs/contracts/interfaces.rst @@ -8,7 +8,7 @@ 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. +- They cannot inherit from other contracts, but they can inherit from other interfaces. - All declared functions must be external. - They cannot declare a constructor. - They cannot declare state variables. @@ -37,10 +37,31 @@ they can be overridden. This does not automatically mean that an overriding func can be overridden again - this is only possible if the overriding function is marked ``virtual``. +Interfaces can inherit from other interfaces. This has the same rules as normal +inheritance. + +:: + + pragma solidity >0.6.1 <0.8.0; + + interface ParentA { + function test() external returns (uint256); + } + + interface ParentB { + function test() external returns (uint256); + } + + interface SubInterface is ParentA, ParentB { + // Must redefine test in order to assert that the parent + // meanings are compatible. + function test() external override(ParentA, ParentB) returns (uint256); + } + 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 + sure the pragma version specifies this version as a minimum. diff --git a/docs/contributing.rst b/docs/contributing.rst index 735e52e4f..34b2db383 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -328,7 +328,7 @@ Whiskers compiler in various places to aid readability, and thus maintainability and verifiability, of the code. The syntax comes with a substantial difference to Mustache. The template markers ``{{`` and ``}}`` are -replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`inline-assembly` +replaced by ``<`` and ``>`` in order to aid parsing and avoid conflicts with :ref:`yul` (The symbols ``<`` and ``>`` are invalid in inline assembly, while ``{`` and ``}`` are used to delimit blocks). Another limitation is that lists are only resolved one depth and they do not recurse. This may change in the future. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 31df68767..404b4ac9e 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -75,9 +75,10 @@ all function arguments have to be copied to memory. it is a message call as part of the overall transaction. When calling functions of other contracts, you can specify the amount of Wei or -gas sent with the call with the special options ``.value()`` and ``.gas()``, -respectively. Any Wei you send to the contract is added to the total balance -of the contract: +gas sent with the call with the special options ``{value: 10, gas: 10000}``. +Note that it is discouraged to specify gas values explicitly, since the gas costs +of opcodes can change in the future. Any Wei you send to the contract is added +to the total balance of that contract: :: @@ -90,14 +91,14 @@ of the contract: contract Consumer { InfoFeed feed; function setFeed(InfoFeed addr) public { feed = addr; } - function callFeed() public { feed.info.value(10).gas(800)(); } + function callFeed() public { feed.info{value: 10, gas: 800}(); } } You need to use the modifier ``payable`` with the ``info`` function because -otherwise, the ``.value()`` option would not be available. +otherwise, the ``value`` option would not be available. .. warning:: - Be careful that ``feed.info.value(10).gas(800)`` only locally sets the + Be careful that ``feed.info{value: 10, gas: 800}`` only locally sets the ``value`` and amount of ``gas`` sent with the function call, and the parentheses at the end perform the actual call. So in this case, the function is not called and the ``value`` and ``gas`` settings are lost. @@ -121,6 +122,11 @@ throws an exception or goes out of gas. external functions happen after any changes to state variables in your contract so your contract is not vulnerable to a reentrancy exploit. +.. note:: + Before Solidity 0.6.2, the recommended way to specify the value and gas + was to use ``f.value(x).gas(g)()``. This is still possible but deprecated + and will be removed with Solidity 0.7.0. + Named Calls and Anonymous Function Parameters --------------------------------------------- @@ -196,17 +202,81 @@ is compiled so recursive creation-dependencies are not possible. function createAndEndowD(uint arg, uint amount) public payable { // Send ether along with the creation - D newD = (new D).value(amount)(arg); + D newD = new D{value: amount}(arg); newD.x(); } } As seen in the example, it is possible to send Ether while creating -an instance of ``D`` using the ``.value()`` option, but it is not possible +an instance of ``D`` using the ``value`` option, but it is not possible to limit the amount of gas. If the creation fails (due to out-of-stack, not enough balance or other problems), an exception is thrown. +Salted contract creations / create2 +----------------------------------- + +When creating a contract, the address of the contract is computed from +the address of the creating contract and a counter that is increased with +each contract creation. + +If you specify the option ``salt`` (a bytes32 value), then contract creation will +use a different mechanism to come up with the address of the new contract: + +It will compute the address from the address of the creating contract, +the given salt value, the (creation) bytecode of the created contract and the constructor +arguments. + +In particular, the counter ("nonce") is not used. This allows for more flexibility +in creating contracts: You are able to derive the address of the +new contract before it is created. Furthermore, you can rely on this address +also in case the creating +contracts creates other contracts in the meantime. + +The main use-case here is contracts that act as judges for off-chain interactions, +which only need to be created if there is a dispute. + +:: + + pragma solidity >0.6.1 <0.8.0; + + contract D { + uint public x; + constructor(uint a) public { + x = a; + } + } + + contract C { + function createDSalted(bytes32 salt, uint arg) public { + /// This complicated expression just tells you how the address + /// can be pre-computed. It is just there for illustration. + /// You actually only need ``new D{salt: salt}(arg)``. + address predictedAddress = address(bytes20(keccak256(abi.encodePacked( + byte(0xff), + address(this), + salt, + keccak256(abi.encodePacked( + type(D).creationCode, + arg + )) + )))); + + D d = new D{salt: salt}(arg); + require(address(d) == predictedAddress); + } + } + +.. warning:: + There are some peculiarities in relation to salted creation. A contract can be + re-created at the same address after having been destroyed. Yet, it is possible + for that newly created contract to have a different deployed bytecode even + though the creation bytecode has been the same (which is a requirement because + otherwise the address would change). This is due to the fact that the compiler + can query external state that might have changed between the two creations + and incorporate that into the deployed bytecode before it is stored. + + Order of Evaluation of Expressions ================================== @@ -273,18 +343,8 @@ i.e. the following is not valid: ``(x, uint y) = (1, 2);`` Complications for Arrays and Structs ------------------------------------ -The semantics of assignments are a bit more complicated for -non-value types like arrays and structs. -Assigning *to* a state variable always creates an independent -copy. On the other hand, assigning to a local variable creates -an independent copy only for elementary types, i.e. static -types that fit into 32 bytes. If structs or arrays (including -``bytes`` and ``string``) are assigned from a state variable -to a local variable, the local variable holds a reference to -the original state variable. A second assignment to the local -variable does not modify the state but only changes the -reference. Assignments to members (or elements) of the local -variable *do* change the state. +The semantics of assignments are more complicated for non-value types like arrays and structs, +including ``bytes`` and ``string``, see :ref:`Data location and assignment behaviour ` for details. In the example below the call to ``g(x)`` has no effect on ``x`` because it creates an independent copy of the storage value in memory. However, ``h(x)`` successfully modifies ``x`` @@ -647,3 +707,13 @@ in scope in the block that follows. in a catch block or the execution of the try/catch statement itself reverts (for example due to decoding failures as noted above or due to not providing a low-level catch clause). + +.. note:: + The reason behind a failed call can be manifold. Do not assume that + the error message is coming directly from the called contract: + The error might have happened deeper down in the call chain and the + called contract just forwarded it. Also, it could be due to an + out-of-gas situation and not a deliberate error condition: + The caller always retains 63/64th of the gas in a call and thus + even if the called contract goes out of gas, the caller still + has some gas left. \ No newline at end of file diff --git a/docs/grammar.txt b/docs/grammar.txt index 06b306499..b8b2893a2 100644 --- a/docs/grammar.txt +++ b/docs/grammar.txt @@ -59,7 +59,7 @@ TypeName = ElementaryTypeName UserDefinedTypeName = Identifier ( '.' Identifier )* -Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' +Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')' ArrayTypeName = TypeName '[' Expression? ']' FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )* ( 'returns' FunctionTypeParameterList )? @@ -96,6 +96,7 @@ Expression | IndexRangeAccess | MemberAccess | FunctionCall + | Expression '{' NameValueList '}' | '(' Expression ')' | ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression | Expression '**' Expression diff --git a/docs/index.rst b/docs/index.rst index 199243018..6610b5217 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -104,4 +104,3 @@ Contents common-patterns.rst bugs.rst contributing.rst - lll.rst diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst index 721161082..d02651a2e 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -174,7 +174,8 @@ Install it using ``brew``: # eg. Install 0.4.8 brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb -Gentoo Linux also provides a solidity package that can be installed using ``emerge``: +Gentoo Linux has an `Ethereum overlay `_ that contains a solidity package. +After the overlay is setup, ``solc`` can be installed in x86_64 architectures by: .. code-block:: bash diff --git a/docs/lll.rst b/docs/lll.rst deleted file mode 100644 index 16be829e6..000000000 --- a/docs/lll.rst +++ /dev/null @@ -1,21 +0,0 @@ -### -LLL -### - -.. _lll: - -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/metadata.rst b/docs/metadata.rst index a91751858..0c8ae47f1 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -142,7 +142,7 @@ to the end of the deployed bytecode:: 0xa2 0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> 0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> - 0x00 0x32 + 0x00 0x33 So in order to retrieve the data, the end of the deployed bytecode can be checked to match that pattern and use the IPFS hash to retrieve the file. diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index c18163184..136f45508 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -89,7 +89,7 @@ as it uses ``call`` which forwards all remaining gas by default: mapping(address => uint) shares; /// Withdraw your share. function withdraw() public { - (bool success,) = msg.sender.call.value(shares[msg.sender])(""); + (bool success,) = msg.sender.call{value: shares[msg.sender]}(""); if (success) shares[msg.sender] = 0; } @@ -149,7 +149,7 @@ Sending and Receiving Ether (for example in the "details" section in Remix). - There is a way to forward more gas to the receiving contract using - ``addr.call.value(x)("")``. This is essentially the same as ``addr.transfer(x)``, + ``addr.call{value: x}("")``. This is essentially the same as ``addr.transfer(x)``, only that it forwards all remaining gas and opens up the ability for the recipient to perform more expensive actions (and it returns a failure code instead of automatically propagating the error). This might include calling back diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst index e77dc4911..605929a1f 100644 --- a/docs/types/mapping-types.rst +++ b/docs/types/mapping-types.rst @@ -7,9 +7,8 @@ Mapping Types Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``. The ``_KeyType`` can be any -built-in value type plus ``bytes`` and ``string``. User-defined -or complex types such as contract types, enums, mappings, structs or array types -apart from ``bytes`` and ``string`` are not allowed. +built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined +or complex types, such as mappings, structs or array types are not allowed. ``_ValueType`` can be any type, including mappings, arrays and structs. You can think of mappings as `hash tables `_, which are virtually initialised diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 627dc2502..1c0f399ba 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -276,17 +276,17 @@ Example:: 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:: +It is possible to adjust the supplied gas with the ``gas`` modifier:: - address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName")); + 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")); + 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")); + 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. @@ -297,7 +297,8 @@ Since byzantium ``staticcall`` can be used as well. This is basically the same a 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``. +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 @@ -635,8 +636,12 @@ External (or public) functions have the following members: * ``.address`` returns the address of the contract of the function. * ``.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. +* ``.gas(uint)`` returns a callable function object which, when called, will send + the specified amount of gas to the target function. Deprecated - use ``{gas: ...}`` instead. + 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. Deprecated - use ``{value: ...}`` instead. + See :ref:`External Function Calls ` for more information. Example that shows how to use the members:: @@ -651,6 +656,8 @@ Example that shows how to use the members:: function g() public { this.f.gas(10).value(800)(); + // New syntax: + // this.f{gas: 10, value: 800}() } } diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index c91e4e505..eb4ddf0c6 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -244,7 +244,7 @@ Input Description // "default", "strip", "debug" and "verboseDebug". // "default" does not inject compiler-generated revert strings and keeps user-supplied ones. // "strip" removes all revert strings (if possible, i.e. if literals are used) keeping side-effects - // "debug" injects strings for compiler-generated internal reverts (not yet implemented) + // "debug" injects strings for compiler-generated internal reverts, implemented for ABI encoders V1 and V2 for now. // "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented) "revertStrings": "default" } @@ -474,3 +474,240 @@ Error types 11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. 12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue. 13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible. + + +.. _compiler-tools: + +Compiler tools +************** + +solidity-upgrade +---------------- + +``solidity-upgrade`` can help you to semi-automatically upgrade your contracts +to breaking language changes. While it does not and cannot implement all +required changes for every breaking release, it still supports the ones, that +would need plenty of repetitive manual adjustments otherwise. + +.. note:: + + ``solidity-upgrade`` carries out a large part of the work, but your + contracts will most likely need further manual adjustments. We recommend + using a version control system for your files. This helps reviewing and + eventually rolling back the changes made. + +.. warning:: + + ``solidity-upgrade`` is not considered to be complete or free from bugs, so + please use with care. + +How it works +~~~~~~~~~~~~ + +You can pass (a) Solidity source file(s) to ``solidity-upgrade [files]``. If +these make use of ``import`` statement which refer to files outside the +current source file's directory, you need to specify directories that +are allowed to read and import files from, by passing +``--allow-paths [directory]``. You can ignore missing files by passing +``--ignore-missing``. + +``solidity-upgrade`` is based on ``libsolidity`` and can parse, compile and +analyse your source files, and might find applicable source upgrades in them. + +Source upgrades are considered to be small textual changes to your source code. +They are applied to an in-memory representation of the source files +given. The corresponding source file is updated by default, but you can pass +``--dry-run`` to simulate to whole upgrade process without writing to any file. + +The upgrade process itself has two phases. In the first phase source files are +parsed, and since it is not possible to upgrade source code on that level, +errors are collected and can be logged by passing ``--verbose``. No source +upgrades available at this point. + +In the second phase, all sources are compiled and all activated upgrade analysis +modules are run alongside compilation. By default, all available modules are +activated. Please read the documentation on +:ref:`available modules ` for further details. + + +This can result in compilation errors that may +be fixed by source upgrades. If no errors occur, no source upgrades are being +reported and you're done. +If errors occur and some upgrade module reported a source upgrade, the first +reported one gets applied and compilation is triggered again for all given +source files. The previous step is repeated as long as source upgrades are +reported. If errors still occur, you can log them by passing ``--verbose``. +If no errors occur, your contracts are up to date and can be compiled with +the latest version of the compiler. + +.. _upgrade-modules: + +Available upgrade modules +~~~~~~~~~~~~~~~~~~~~~~~~~ + ++-----------------+---------+--------------------------------------------------+ +| Module | Version | Description | ++=================+=========+==================================================+ +| ``constructor`` | 0.5.0 | Constructors must now be defined using the | +| | | ``constructor`` keyword. | ++-----------------+---------+--------------------------------------------------+ +| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, | +| | | defaults to ``public``. | ++-----------------+---------+--------------------------------------------------+ +| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a | +| | | contract does not implement all its functions. | ++-----------------+---------+--------------------------------------------------+ +| ``virtual`` | 0.6.0 | Functions without implementation outside an | +| | | interface have to be marked ``virtual``. | ++-----------------+---------+--------------------------------------------------+ +| ``override`` | 0.6.0 | When overriding a function or modifier, the new | +| | | keyword ``override`` must be used. | ++-----------------+---------+--------------------------------------------------+ + +Please read :doc:`0.5.0 release notes <050-breaking-changes>` and +:doc:`0.6.0 release notes <060-breaking-changes>` for further details. + +Synopsis +~~~~~~~~ + +.. code-block:: none + + Usage: solidity-upgrade [options] contract.sol + + Allowed options: + --help Show help message and exit. + --version Show version and exit. + --allow-paths path(s) + Allow a given path for imports. A list of paths can be + supplied by separating them with a comma. + --ignore-missing Ignore missing files. + --modules module(s) Only activate a specific upgrade module. A list of + modules can be supplied by separating them with a comma. + --dry-run Apply changes in-memory only and don't write to input + file. + --verbose Print logs, errors and changes. Shortens output of + upgrade patches. + --unsafe Accept *unsafe* changes. + + + +Bug Reports / Feature requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you found a bug or if you have a feature request, please +`file an issue `_ on Github. + + +Example +~~~~~~~ + +Assume you have the following contracts you want to update declared in ``Source.sol``: + +.. code-block:: none + + // This will not compile + pragma solidity >0.4.23; + + contract Updateable { + function run() public view returns (bool); + function update() public; + } + + contract Upgradable { + function run() public view returns (bool); + function upgrade(); + } + + contract Source is Updateable, Upgradable { + function Source() public {} + + function run() + public + view + returns (bool) {} + + function update() {} + function upgrade() {} + } + + +Required changes +^^^^^^^^^^^^^^^^ + +To bring the contracts up to date with the current Solidity version, the +following upgrade modules have to be executed: ``constructor``, +``visibility``, ``abstract``, ``override`` and ``virtual``. Please read the +documentation on :ref:`available modules ` for further details. + +Running the upgrade +^^^^^^^^^^^^^^^^^^^ + +In this example, all modules needed to upgrade the contracts above, +are available and all of them are activated by default. Therefore you +do not need to specify the ``--modules`` option. + +.. code-block:: none + + $ solidity-upgrade Source.sol --dry-run + +.. code-block:: none + + Running analysis (and upgrade) on given source files. + .............. + + After upgrade: + + Found 0 errors. + Found 0 upgrades. + +The above performs a dry-ran upgrade on the given file and logs statistics after all. +In this case, the upgrade was successful and no further adjustments are needed. + +Finally, you can run the upgrade and also write to the source file. + +.. code-block:: none + + $ solidity-upgrade Source.sol + +.. code-block:: none + + Running analysis (and upgrade) on given source files. + .............. + + After upgrade: + + Found 0 errors. + Found 0 upgrades. + + +Review changes +^^^^^^^^^^^^^^ + +The command above applies all changes as shown below. Please review them carefully. + +.. code-block:: none + + pragma solidity >0.4.23; + + abstract contract Updateable { + function run() public view virtual returns (bool); + function update() public virtual; + } + + abstract contract Upgradable { + function run() public view virtual returns (bool); + function upgrade() public virtual; + } + + contract Source is Updateable, Upgradable { + constructor() public {} + + function run() + public + view + override(Updateable,Upgradable) + returns (bool) {} + + function update() public override {} + function upgrade() public override {} + } diff --git a/docs/yul.rst b/docs/yul.rst index 739a83996..93bfe48a9 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -1,53 +1,74 @@ +.. _yul: + ### Yul ### -.. _yul: - .. index:: ! assembly, ! asm, ! evmasm, ! yul, julia, iulia Yul (previously also called JULIA or IULIA) is an intermediate language that can be compiled to bytecode for different backends. -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. +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 in stand-alone mode and +for "inline assembly" inside Solidity +and there is an experimental implementation of the Solidity compiler +that uses 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 `: +Motivation and High-level Description +===================================== -:: +The design of Yul tries to achieve several goals: - { - "language": "Yul", - "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, - "settings": { - "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, - "optimizer": { "enabled": true, "details": { "yul": true } } - } - } +1. Programs written in Yul should be readable, even if the code is generated by a compiler from Solidity or another high-level language. +2. Control flow should be easy to understand to help in manual inspection, formal verification and optimization. +3. The translation from Yul to bytecode should be as straightforward as possible. +4. Yul should be suitable for whole-program optimization. -And on the command line interface with the ``--strict-assembly`` parameter. +In order to achieve the first and second goal, Yul provides high-level constructs +like ``for`` loops, ``if`` and ``switch`` statements and function calls. These should +be sufficient for adequately representing the control flow for assembly programs. +Therefore, no explicit statements for ``SWAP``, ``DUP``, ``JUMP`` and ``JUMPI`` +are provided, because the first two obfuscate the data flow +and the last two obfuscate control flow. Furthermore, functional statements of +the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like +``7 y x add mul`` because in the first form, it is much easier to see which +operand is used for which opcode. -.. warning:: +Even though it was designed for stack machines, Yul does not expose the complexity of the stack itself. +The programmer or auditor should not have to worry about the stack. - 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 third goal is achieved by compiling the +higher level constructs to bytecode in a very regular way. +The only non-local operation performed +by the assembler is name lookup of user-defined identifiers (functions, variables, ...) +and cleanup of local variables from the stack. -The core components of Yul are functions, blocks, variables, literals, -for-loops, if-statements, switch-statements, expressions and assignments to variables. +To avoid confusions between concepts like values and references, +Yul is statically typed. At the same time, there is a default type +(usually the integer word of the target machine) that can always +be omitted to help readability. -Yul is typed, both variables and literals must specify the type with postfix -notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``, -``u64``, ``s64``, ``u128``, ``s128``, ``u256`` and ``s256``. +To keep the language simple and flexible, Yul does not have +any built-in operations, functions or types in its pure form. +These are added together with their semantics when specifying a dialect of Yul, +which allows to specialize Yul to the requirements of different +target platforms and feature sets. -Yul in itself does not even provide operators. If the EVM is targeted, -opcodes will be available as built-in functions, but they can be reimplemented -if the backend changes. For a list of mandatory built-in functions, see the section below. +Currently, there is only one specified dialect of Yul. This dialect uses +the EVM opcodes as builtin functions +(see below) and defines only the type ``u256``, which is the native 256-bit +type of the EVM. Because of that, we will not provide types in the examples below. -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``. + +Simple Example +============== + +The following example program is written in the EVM dialect and computes exponentiation. +It can be compiled using ``solc --strict-assembly``. The builtin functions +``mul`` and ``div`` compute product and division, respectively. .. code:: @@ -67,8 +88,8 @@ As per the warning above, the following code is untyped and can be compiled usin } It is also possible to implement the same function using a for-loop -instead of with recursion. Here, we need the EVM opcodes ``lt`` (less-than) -and ``add`` to be available. +instead of with recursion. Here, ``lt(a, b)`` computes whether ``a`` is less than ``b``. +less-than comparison. .. code:: @@ -83,10 +104,326 @@ and ``add`` to be available. } } + + + +Stand-Alone Usage +================= + +You can use Yul in its stand-alone form in the EVM dialect using the Solidity compiler. +This will use the `Yul object notation `_ so that it is possible to refer +to code as data to deploy contracts. This Yul mode is available for the commandline compiler +(use ``--strict-assembly``) and for the :ref:`standard-json interface `: + +:: + + { + "language": "Yul", + "sources": { "input.yul": { "content": "{ sstore(0, 1) }" } }, + "settings": { + "outputSelection": { "*": { "*": ["*"], "": [ "*" ] } }, + "optimizer": { "enabled": true, "details": { "yul": true } } + } + } + +.. warning:: + + Yul is in active development and bytecode generation is only fully implemented for the EVM dialect of Yul + with EVM 1.0 as target. + + +Informal Description of Yul +=========================== + +In the following, we will talk about each individual aspect +of the Yul language. In examples, we will use the default EVM dialect. + +Syntax +------ + +Yul parses comments, literals and identifiers in the same way as Solidity, +so you can e.g. use ``//`` and ``/* */`` to denote comments. +There is one exception: Identifiers in Yul can contain dots: ``.``. + +Yul can specify "objects" that consist of code, data and sub-objects. +Please see `Yul Objects `_ below for details on that. +In this section, we are only concerned with the code part of such an object. +This code part always consists of a curly-braces +delimited block. Most tools support specifying just a code block +where an object is expected. + +Inside a code block, the following elements can be used +(see the later sections for more details): + + - literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) + - calls to builtin functions, e.g. ``add(1, mload(0))`` + - variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of 0 is assigned) + - identifiers (variables), e.g. ``add(3, x)`` + - assignments, e.g. ``x := add(y, 3)`` + - blocks where local variables are scoped inside, e.g. ``{ let x := 3 { let y := add(x, 1) } }`` + - if statements, e.g. ``if lt(a, b) { sstore(0, 1) }`` + - switch statements, e.g. ``switch mload(0) case 0 { revert() } default { mstore(0, 1) }`` + - for loops, e.g. ``for { let i := 0} lt(i, 10) { i := add(i, 1) } { mstore(i, 7) }`` + - function definitions, e.g. ``function f(a, b) -> c { c := add(a, b) }``` + +Multiple syntactical elements can follow each other simply separated by +whitespace, i.e. there is no terminating ``;`` or newline required. + +Literals +-------- + +You can use integer constants in decimal or hexadecimal notation. +When compiling for the EVM, this will be translated into an +appropriate ``PUSHi`` instruction. In the following example, +``3`` and ``2`` are added resulting in 5 and then the +bitwise ``and`` with the string "abc" is computed. +The final value is assigned to a local variable called ``x``. +Strings are stored left-aligned and cannot be longer than 32 bytes. + +.. code:: + + let x := and("abc", add(3, 2)) + +Unless it is the default type, the type of a literal +has to be specified after a colon: + +.. code:: + + let x := and("abc":uint32, add(3:uint256, 2:uint256)) + + +Function Calls +-------------- + +Both built-in and user-defined functions (see below) can be called +in the same way as shown in the previous example. +If the function returns a single value, it can be directly used +inside an expression again. If it returns multiple values, +they have to be assigned to local variables. + +.. code:: + + mstore(0x80, add(mload(0x80), 3)) + // Here, the user-defined function `f` returns + // two values. The definition of the function + // is missing from the example. + let x, y := f(1, mload(0)) + +For built-in functions of the EVM, functional expressions +can be directly translated to a stream of opcodes: +You just read the expression from right to left to obtain the +opcodes. In the case of the first line in the example, this +is ``PUSH1 3 PUSH1 0x80 MLOAD ADD PUSH1 0x80 MSTORE``. + +For calls to user-defined functions, the arguments are also +put on the stack from right to left and this is the order +in which argument lists are evaluated. The return values, +though, are expected on the stack from left to right, +i.e. in this example, ``y`` is on top of the stack and ``x`` +is below it. + +Variable Declarations +--------------------- + +You can use the ``let`` keyword to declare variables. +A variable is only visible inside the +``{...}``-block it was defined in. When compiling to the EVM, +a new stack slot is created that is reserved +for the variable and automatically removed again when the end of the block +is reached. You can provide an initial value for the variable. +If you do not provide a value, the variable will be initialized to zero. + +Since variables are stored on the stack, they do not directly +influence memory or storage, but they can be used as pointers +to memory or storage locations in the built-in functions +``mstore``, ``mload``, ``sstore`` and ``sload``. +Future dialects migh introduce specific types for such pointers. + +When a variable is referenced, its current value is copied. +For the EVM, this translates to a ``DUP`` instruction. + +.. code:: + + { + let zero := 0 + let v := calldataload(zero) + { + let y := add(sload(v), 1) + v := y + } // y is "deallocated" here + sstore(v, zero) + } // v and zero are "deallocated" here + + +If the declared variable should have a type different from the default type, +you denote that following a colon. You can also declare multiple +variables in one statement when you assign from a function call +that returns multiple values. + +.. code:: + + { + let zero:uint32 := 0:uint32 + let v:uint256, t:uint32 := f() + let x, y := g() + } + +Depending on the optimiser settings, the compiler can free the stack slots +already after the variable has been used for +the last time, even though it is still in scope. + + +Assignments +----------- + +Variables can be assigned to after their definition using the +``:=`` operator. It is possible to assign multiple +variables at the same time. For this, the number and types of the +values have to match. +If you want to assign the values returned from a function that has +multiple return parameters, you have to provide multiple variables. + +.. code:: + + let v := 0 + // re-assign v + v := 2 + let t := add(v, 2) + function f() -> a, b { } + // assign multiple values + v, t := f() + + +If +-- + +The if statement can be used for conditionally executing code. +No "else" block can be defined. Consider using "switch" instead (see below) if +you need multiple alternatives. + +.. code:: + + if eq(value, 0) { revert(0, 0) } + +The curly braces for the body are required. + +Switch +------ + +You can use a switch statement as an extended version of the if statement. +It takes the value of an expression and compares it to several literal constants. +The branch corresponding to the matching constant is taken. +Contrary to other programming languages, for safety reasons, control flow does +not continue from one case to the next. There can be a fallback or default +case called ``default`` which is taken if none of the literal constants matches. + +.. code:: + + { + let x := 0 + switch calldataload(4) + case 0 { + x := calldataload(0x24) + } + default { + x := calldataload(0x44) + } + sstore(0, div(x, 2)) + } + +The list of cases is not enclosed by curly braces, but the body of a +case does require them. + +Loops +----- + +Yul supports for-loops which consist of +a header containing an initializing part, a condition, a post-iteration +part and a body. The condition has to be an expression, while +the other three are blocks. If the initializing part +declares any variables at the top level, the scope of these variables extends to all other +parts of the loop. + +The ``break`` and ``continue`` statements can be used in the body to exit the loop +or skip to the post-part, respectively. + +The following example computes the sum of an area in memory. + +.. code:: + + { + let x := 0 + for { let i := 0 } lt(i, 0x100) { i := add(i, 0x20) } { + x := add(x, mload(i)) + } + } + +For loops can also be used as a replacement for while loops: +Simply leave the initialization and post-iteration parts empty. + +.. code:: + + { + let x := 0 + let i := 0 + for { } lt(i, 0x100) { } { // while(i < 0x100) + x := add(x, mload(i)) + i := add(i, 0x20) + } + } + +Function Declarations +--------------------- + +Yul allows the definition of functions. These should not be confused with functions +in Solidity since they are never part of an external interface of a contract and +are part of a namespace separate from the one for Solidity functions. + +For the EVM, Yul functions take their +arguments (and a return PC) from the stack and also put the results onto the +stack. User-defined functions and built-in functions are called in exactly the same way. + +Functions can be defined anywhere and are visible in the block they are +declared in. Inside a function, you cannot access local variables +defined outside of that function. + +Functions declare parameters and return variables, similar to Solidity. +To return a value, you assign it to the return variable(s). + +If you call a function that returns multiple values, you have to assign +them to multiple variables using ``a, b := f(x)`` or ``let a, b := f(x)``. + +The ``leave`` statement can be used to exit the current function. It +works like the ``return`` statement in other languages just that it does +not take a value to return, it just exits the functions and the function +will return whatever values are currently assigned to the return variable(s). + +Note that the EVM dialect has a built-in function called ``return`` that +quits the full execution context (internal message call) and not just +the current yul function. + +The following example implements the power function by square-and-multiply. + +.. code:: + + { + function power(base, exponent) -> result { + switch exponent + case 0 { result := 1 } + case 1 { result := base } + default { + result := power(mul(base, base), div(exponent, 2)) + switch mod(exponent, 2) + case 1 { result := mul(base, result) } + } + } + } + Specification of Yul ==================== -This chapter describes Yul code. It is usually placed inside a Yul object, which is described in the following chapter. +This chapter describes Yul code formally. Yul code is usually placed inside Yul objects, +which are explained in their own chapter. Grammar:: @@ -128,11 +465,10 @@ Grammar:: Identifier '(' ( Expression ( ',' Expression )* )? ')' Identifier = [a-zA-Z_$] [a-zA-Z_$0-9.]* IdentifierList = Identifier ( ',' Identifier)* - TypeName = Identifier | BuiltinTypeName - BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' ) - TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )* + TypeName = Identifier + TypedIdentifierList = Identifier ( ':' TypeName )? ( ',' Identifier ( ':' TypeName )? )* Literal = - (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ':' TypeName + (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ( ':' TypeName )? NumberLiteral = HexNumber | DecimalNumber HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'') StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"' @@ -141,19 +477,23 @@ Grammar:: HexNumber = '0x' [0-9a-fA-F]+ DecimalNumber = [0-9]+ + Restrictions on the Grammar --------------------------- +Apart from those directly imposed by the grammar, the following +restrictions apply: + Switches must have at least one case (including the 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. +All case values need to have the same type and distinct values. +If all possible values of the expression type are covered, a default case is +not allowed (i.e. a switch with a ``bool`` expression that has both a +true and a false case do not allow a default case). Every expression evaluates to zero or more values. Identifiers and Literals evaluate to exactly one value and function calls evaluate to a number of values equal to the -number of return values of the function called. +number of return variables of the function called. In variable declarations and assignments, the right-hand-side expression (if present) has to evaluate to a number of values equal to the number of @@ -168,13 +508,22 @@ In all other situations, expressions have to evaluate to exactly one value. 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 ``leave`` statement can only be used inside a function. +top level). The ``continue`` and ``break`` statements cannot be used +in other parts of a loop, not even when it is scoped inside a second loop's body. + The condition part of the for-loop has to evaluate to exactly one value. + +The ``leave`` statement can only be used inside a function. + Functions cannot be defined anywhere inside for loop init blocks. Literals cannot be larger than the their type. The largest type defined is 256-bit wide. +During assignments and function calls, the types of the respective values have to match. +There is no implicit type conversion. Type conversion in general can only be achieved +if the dialect provides an appropriate built-in function that takes a value of one +type and returns a value of a different type. + Scoping Rules ------------- @@ -186,9 +535,11 @@ introduce new identifiers into these scopes. Identifiers are visible in the block they are defined in (including all sub-nodes and sub-blocks). -As an exception, identifiers defined directly in the "init" part of the for-loop -(the first block) are visible in all other parts of the for-loop -(but not outside of the loop). +As an exception, the scope of the "init" part of the or-loop +(the first block) extends across all other parts of the for loop. +This means that variables declared in the init part (but not inside a +block inside the init part) are visible in all other parts of the for-loop. + Identifiers declared in the other parts of the for loop respect the regular syntactical scoping rules. @@ -197,7 +548,7 @@ to ``{ I... for {} C { P... } { B... } }``. The parameters and return parameters of functions are visible in the -function body and their names cannot overlap. +function body and their names have to be distinct. Variables can only be referenced after their declaration. In particular, variables cannot be referenced in the right hand side of their own variable @@ -215,13 +566,14 @@ Formal Specification -------------------- We formally specify Yul by providing an evaluation function E overloaded -on the various nodes of the AST. Any functions can have side effects, so +on the various nodes of the AST. As builtin functions can have side effects, E takes two state objects and the AST node and returns two new state objects and a variable number of other values. The two state objects are the global state object (which in the context of the EVM is the memory, storage and state of the blockchain) and the local state object (the state of local variables, i.e. a segment of the stack in the EVM). + If the AST node is a statement, E returns the two state objects and a "mode", which is used for the ``break``, ``continue`` and ``leave`` statements. If the AST node is an expression, E returns the two state objects and @@ -336,248 +688,222 @@ We will use a destructuring notation for the AST nodes. E(G, L, n: DecimalNumber) = G, L, dec(n), where dec is the decimal decoding function -Type Conversion Functions -------------------------- +.. _opcodes: -Yul has no support for implicit type conversion and therefore functions exist to provide explicit conversion. -When converting a larger type to a shorter type a runtime exception can occur in case of an overflow. +EVM Dialect +----------- -Truncating conversions are supported between the following types: - - ``bool`` - - ``u32`` - - ``u64`` - - ``u256`` - - ``s256`` +The default dialect of Yul currently is the EVM dialect for the currently selected version of the EVM. +with a version of the EVM. The only type available in this dialect +is ``u256``, the 256-bit native type of the Ethereum Virtual Machine. +Since it is the default type of this dialect, it can be omitted. -For each of these a type conversion function exists having the prototype in the form of ``to(x:) -> y:``, -such as ``u32tobool(x:u32) -> y:bool``, ``u256tou32(x:u256) -> y:u32`` or ``s256tou256(x:s256) -> y:u256``. +The following table lists all builtin functions +(depending on the EVM version) and provides a short description of the +semantics of the function / opcode. +This document does not want to be a full description of the Ethereum virtual machine. +Please refer to a different document if you are interested in the precise semantics. + +Opcodes marked with ``-`` do not return a result and all others return exactly one value. +Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead, +Byzantium, Constantinople or Istanbul, respectively. + +In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to +but not including position ``b`` and ``storage[p]`` signifies the storage contents at slot ``p``. + +Since Yul manages local variables and control-flow, +opcodes that interfere with these features are not available. This includes +the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels and the ``push`` instructions. + ++-------------------------+-----+---+-----------------------------------------------------------------+ +| Instruction | | | Explanation | ++=========================+=====+===+=================================================================+ +| stop() + `-` | F | stop execution, identical to return(0, 0) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| add(x, y) | | F | x + y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sub(x, y) | | F | x - y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mul(x, y) | | F | x * y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| div(x, y) | | F | x / y or 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sdiv(x, y) | | F | x / y, for signed numbers in two's complement, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mod(x, y) | | F | x % y, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| smod(x, y) | | F | x % y, for signed numbers in two's complement, 0 if y == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| exp(x, y) | | F | x to the power of y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| not(x) | | F | bitwise "not" of x (every bit of x is negated) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| lt(x, y) | | F | 1 if x < y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gt(x, y) | | F | 1 if x > y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| slt(x, y) | | F | 1 if x < y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sgt(x, y) | | F | 1 if x > y, 0 otherwise, for signed numbers in two's complement | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| eq(x, y) | | F | 1 if x == y, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| iszero(x) | | F | 1 if x == 0, 0 otherwise | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| and(x, y) | | F | bitwise "and" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| or(x, y) | | F | bitwise "or" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| xor(x, y) | | F | bitwise "xor" of x and y | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| byte(n, x) | | F | nth byte of x, where the most significant byte is the 0th byte | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| shl(x, y) | | C | logical shift left y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| shr(x, y) | | C | logical shift right y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sar(x, y) | | C | signed arithmetic shift right y by x bits | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| addmod(x, y, m) | | F | (x + y) % m with arbitrary precision arithmetic, 0 if m == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mulmod(x, y, m) | | F | (x * y) % m with arbitrary precision arithmetic, 0 if m == 0 | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| signextend(i, x) | | F | sign extend from (i*8+7)th bit counting from least significant | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| keccak256(p, n) | | F | keccak(mem[p...(p+n))) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| pc() | | F | current position in code | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| pop(x) | `-` | F | discard value x | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mload(p) | | F | mem[p...(p+32)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mstore(p, v) | `-` | F | mem[p...(p+32)) := v | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| mstore8(p, v) | `-` | F | mem[p] := v & 0xff (only modifies a single byte) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sload(p) | | F | storage[p] | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| sstore(p, v) | `-` | F | storage[p] := v | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| msize() | | F | size of memory, i.e. largest accessed memory index | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gas() | | F | gas still available to execution | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| address() | | F | address of the current contract / execution context | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| balance(a) | | F | wei balance at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| selfbalance() | | I | equivalent to balance(address()), but cheaper | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| caller() | | F | call sender (excluding ``delegatecall``) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| callvalue() | | F | wei sent together with the current call | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldataload(p) | | F | call data starting from position p (32 bytes) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldatasize() | | F | size of call data in bytes | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| calldatacopy(t, f, s) | `-` | F | copy s bytes from calldata at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| codesize() | | F | size of the code of the current contract / execution context | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| codecopy(t, f, s) | `-` | F | copy s bytes from code at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodesize(a) | | F | size of the code at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodecopy(a, t, f, s) | `-` | F | like codecopy(t, f, s) but take code at address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| returndatasize() | | B | size of the last returndata | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| extcodehash(a) | | C | code hash of address a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei | +| | | | and return the new address | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| create2(v, p, n, s) | | C | create new contract with code mem[p...(p+n)) at address | +| | | | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | +| | | | and send v wei and return the new address, where ``0xff`` is a | +| | | | 1 byte value, ``this`` is the current contract's address | +| | | | as a 20 byte value and ``s`` is a big-endian 256-bit value | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| call(g, a, v, in, | | F | call contract at address a with input mem[in...(in+insize)) | +| insize, out, outsize) | | | providing g gas and v wei and output area | +| | | | mem[out...(out+outsize)) returning 0 on error (eg. out of gas) | +| | | | and 1 on success | +| | | | :ref:`See more ` | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| callcode(g, a, v, in, | | F | identical to ``call`` but only use the code from a and stay | +| insize, out, outsize) | | | in the context of the current contract otherwise | +| | | | :ref:`See more ` | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| delegatecall(g, a, in, | | H | identical to ``callcode`` but also keep ``caller`` | +| insize, out, outsize) | | | and ``callvalue`` | +| | | | :ref:`See more ` | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| staticcall(g, a, in, | | B | identical to ``call(g, a, 0, in, insize, out, outsize)`` but do | +| insize, out, outsize) | | | not allow state modifications | +| | | | :ref:`See more ` | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| return(p, s) | `-` | F | end execution, return data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| revert(p, s) | `-` | B | end execution, revert state changes, return data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| selfdestruct(a) | `-` | F | end execution, destroy current contract and send funds to a | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| invalid() | `-` | F | end execution with invalid instruction | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log0(p, s) | `-` | F | log without topics and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log1(p, s, t1) | `-` | F | log with topic t1 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log2(p, s, t1, t2) | `-` | F | log with topics t1, t2 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log3(p, s, t1, t2, t3) | `-` | F | log with topics t1, t2, t3 and data mem[p...(p+s)) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) | +| t4) | | | | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| chainid() | | I | ID of the executing chain (EIP 1344) | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| origin() | | F | transaction sender | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gasprice() | | F | gas price of the transaction | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| coinbase() | | F | current mining beneficiary | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| timestamp() | | F | timestamp of the current block in seconds since the epoch | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| number() | | F | current block number | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| difficulty() | | F | difficulty of the current block | ++-------------------------+-----+---+-----------------------------------------------------------------+ +| gaslimit() | | F | block gas limit of the current block | ++-------------------------+-----+---+-----------------------------------------------------------------+ + +There are three additional functions, ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, +which are used to access other parts of a Yul object. + +``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) +as arguments and return the size and offset in the data area, respectively. +For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. + +.. _yul-call-return-area: .. note:: + The ``call*`` instructions use the ``out`` and ``outsize`` parameters to define an area in memory where + the return data is placed. This area is written to depending on how many bytes the called contract returns. + If it returns more data, only the first ``outsize`` bytes are written. You can access the rest of the data + using the ``returndatacopy`` opcode. If it returns less data, then the remaining bytes are not touched at all. + You need to use the ``returndatasize`` opcode to check which part of this memory area contains the return data. + The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``), + nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``. - ``u32tobool(x:u32) -> y:bool`` can be implemented as ``y := not(iszerou256(x))`` and - ``booltou32(x:bool) -> y:u32`` can be implemented as ``switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }`` - -Low-level Functions -------------------- - -The following functions must be available: - -+---------------------------------------------------------------------------------------------------------------+ -| *Logic* | -+---------------------------------------------+-----------------------------------------------------------------+ -| not(x:bool) ‑> z:bool | logical not | -+---------------------------------------------+-----------------------------------------------------------------+ -| and(x:bool, y:bool) ‑> z:bool | logical and | -+---------------------------------------------+-----------------------------------------------------------------+ -| or(x:bool, y:bool) ‑> z:bool | logical or | -+---------------------------------------------+-----------------------------------------------------------------+ -| xor(x:bool, y:bool) ‑> z:bool | xor | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Arithmetic* | -+---------------------------------------------+-----------------------------------------------------------------+ -| addu256(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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| lts256(x:s256, y:s256) ‑> z:bool | true if x < y, false otherwise | -| | (for signed numbers in two's complement) | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| iszerou256(x:u256) ‑> z:bool | true if x == 0, false otherwise | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -| | 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)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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] | -+---------------------------------------------+-----------------------------------------------------------------+ -| sstore(p:u256, v:u256) | storage[p] := v | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Execution control* | -+---------------------------------------------+-----------------------------------------------------------------+ -| create(v:u256, p:u256, n:u256) | create new contract with code mem[p..(p+n)) and send v wei | -| | and return the new address | -+---------------------------------------------+-----------------------------------------------------------------+ -| create2(v:u256, p:u256, n:u256, s:u256) | create new contract with code mem[p...(p+n)) at address | -| | keccak256(0xff . this . s . keccak256(mem[p...(p+n))) | -| | and send v wei and return the new address, where ``0xff`` is a | -| | 8 byte value, ``this`` is the current contract's address | -| | as a 20 byte value and ``s`` is a big-endian 256-bit value | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, | -| insize:u256, out:u256, | but also keep ``caller`` | -| outsize:u256) ‑> r:u256 | and ``callvalue`` | -+---------------------------------------------+-----------------------------------------------------------------+ -| abort() | abort (equals to invalid instruction on EVM) | -+---------------------------------------------+-----------------------------------------------------------------+ -| return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| revert(p:u256, s:u256) | end execution, revert state changes, return data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| selfdestruct(a:u256) | end execution, destroy current contract and send funds to a | -+---------------------------------------------+-----------------------------------------------------------------+ -| log0(p:u256, s:u256) | log without topics and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log1(p:u256, s:u256, t1:u256) | log with topic t1 and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log2(p:u256, s:u256, t1:u256, t2:u256) | log with topics t1, t2 and data mem[p..(p+s)) | -+---------------------------------------------+-----------------------------------------------------------------+ -| log3(p:u256, s:u256, t1:u256, t2:u256, | log with topics t, t2, t3 and data mem[p..(p+s)) | -| t3:u256) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| log4(p:u256, s:u256, t1:u256, t2:u256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) | -| t3:u256, t4:u256) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| *State queries* | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockcoinbase() ‑> address:u256 | current mining beneficiary | -+---------------------------------------------+-----------------------------------------------------------------+ -| blockdifficulty() ‑> difficulty:u256 | difficulty 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| blocknumber() ‑> block:u256 | current block number | -+---------------------------------------------+-----------------------------------------------------------------+ -| blocktimestamp() ‑> timestamp:u256 | timestamp of the current block in seconds since the epoch | -+---------------------------------------------+-----------------------------------------------------------------+ -| txorigin() ‑> address:u256 | transaction sender | -+---------------------------------------------+-----------------------------------------------------------------+ -| txgasprice() ‑> price:u256 | gas price of the transaction | -+---------------------------------------------+-----------------------------------------------------------------+ -| gasleft() ‑> gas:u256 | gas still available to execution | -+---------------------------------------------+-----------------------------------------------------------------+ -| balance(a:u256) ‑> v:u256 | wei balance at address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| this() ‑> address:u256 | address of the current contract / execution context | -+---------------------------------------------+-----------------------------------------------------------------+ -| caller() ‑> address:u256 | call sender (excluding delegatecall) | -+---------------------------------------------+-----------------------------------------------------------------+ -| callvalue() ‑> v:u256 | wei sent together with the current call | -+---------------------------------------------+-----------------------------------------------------------------+ -| calldataload(p:u256) ‑> v:u256 | call data starting from position p (32 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 | -+---------------------------------------------+-----------------------------------------------------------------+ -| extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| extcodehash(a:u256) | code hash of address a | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Others* | -+---------------------------------------------+-----------------------------------------------------------------+ -| discard(unused:bool) | discard value | -+---------------------------------------------+-----------------------------------------------------------------+ -| discardu256(unused:u256) | discard value | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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) | | -+---------------------------------------------+-----------------------------------------------------------------+ -| keccak256(p:u256, s:u256) ‑> v:u256 | keccak(mem[p...(p+s))) | -+---------------------------------------------+-----------------------------------------------------------------+ -| *Object access* | | -+---------------------------------------------+-----------------------------------------------------------------+ -| 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 --------- - -Backends or targets are the translators from Yul to a specific bytecode. Each of the backends can expose functions -prefixed with the name of the backend. We reserve ``evm_`` and ``ewasm_`` prefixes for the two proposed backends. - -Backend: EVM ------------- - -The EVM target will have all the underlying EVM opcodes exposed with the `evm_` prefix. - -Backend: "EVM 1.5" ------------------- - -TBD - -Backend: eWASM --------------- - -TBD +.. _yul-object: Specification of Yul Object =========================== @@ -603,13 +929,15 @@ An example Yul Object is shown below: .. code:: - // Code consists of a single object. A single "code" node is the code of the object. + // A contract consists of a single object with sub-objects representing + // the code to be deployed or other contracts it can create. + // The single "code" node is the executable 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 - // 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. + // The current object, sub-objects and data items inside the current object + // are in scope. object "Contract1" { + // This is the constructor code of the contract. code { function allocate(size) -> ptr { ptr := mload(0x40) @@ -617,18 +945,17 @@ An example Yul Object is shown below: mstore(0x40, add(ptr, size)) } - // first create "runtime.Contract2" - let size := datasize("runtime.Contract2") + // first create "Contract2" + let size := datasize("Contract2") let offset := allocate(size) - // This will turn into a memory->memory copy for eWASM and - // a codecopy for EVM - datacopy(offset, dataoffset("runtime.Contract2"), size) + // This will turn into codecopy for EVM + datacopy(offset, dataoffset("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) + // now return the runtime object (the currently + // executing code is the constructor code) size := datasize("runtime") offset := allocate(size) // This will turn into a memory->memory copy for eWASM and @@ -649,30 +976,42 @@ An example Yul Object is shown below: // runtime code - let size := datasize("Contract2") - let offset := allocate(size) - // This will turn into a memory->memory copy for eWASM and - // a codecopy for EVM - datacopy(offset, dataoffset("Contract2"), size) - // constructor parameter is a single number 0x1234 - mstore(add(offset, size), 0x1234) - pop(create(offset, add(size, 32), 0)) + mstore(0, "Hello, World!") + return(0, 0x20) + } + } + + // Embedded object. Use case is that the outside is a factory contract, + // and Contract2 is the code to be created by the factory + object "Contract2" { + code { + // code here ... } - // Embedded object. Use case is that the outside is a factory contract, - // and Contract2 is the code to be created by the factory - object "Contract2" { + object "runtime" { code { // code here ... } - - object "runtime" { - code { - // code here ... - } - } - - data "Table1" hex"4123" } + + data "Table1" hex"4123" } } + +Yul Optimizer +============= + +The Yul optimizer operates on Yul code and uses the same language for input, output and +intermediate states. This allows for easy debugging and verification of the optimizer. + +Please see the +`documentation in the source code `_ +for more details about its internals. + +If you want to use Solidity in stand-alone Yul mode, you activate the optimizer using ``--optimize``: + +:: + + solc --strict-assembly --optimize + +In Solidity mode, the Yul optimizer is activated together with the regular optimizer. diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ad4263869..7ed44d236 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -38,62 +38,15 @@ using namespace solidity::evmasm; using namespace solidity::langutil; using namespace solidity::util; -void Assembly::append(Assembly const& _a) -{ - auto newDeposit = m_deposit + _a.deposit(); - for (AssemblyItem i: _a.m_items) - { - switch (i.type()) - { - case Tag: - case PushTag: - i.setData(i.data() + m_usedTags); - break; - case PushSub: - case PushSubSize: - i.setData(i.data() + m_subs.size()); - break; - default: - break; - } - append(i); - } - m_deposit = newDeposit; - m_usedTags += _a.m_usedTags; - // This does not transfer the names of named tags on purpose. The tags themselves are - // transferred, but their names are only available inside the assembly. - for (auto const& i: _a.m_data) - m_data.insert(i); - for (auto const& i: _a.m_strings) - m_strings.insert(i); - m_subs += _a.m_subs; - for (auto const& lib: _a.m_libraries) - m_libraries.insert(lib); -} - -void Assembly::append(Assembly const& _a, int _deposit) -{ - assertThrow(_deposit <= _a.m_deposit, InvalidDeposit, ""); - - append(_a); - while (_deposit++ < _a.m_deposit) - append(Instruction::POP); -} - AssemblyItem const& Assembly::append(AssemblyItem const& _i) { assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); m_deposit += _i.deposit(); m_items.emplace_back(_i); - if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty()) + if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid()) m_items.back().setLocation(m_currentSourceLocation); m_items.back().m_modifierDepth = m_currentModifierDepth; - return back(); -} - -void Assembly::injectStart(AssemblyItem const& _i) -{ - m_items.insert(m_items.begin(), _i); + return m_items.back(); } unsigned Assembly::bytesRequired(unsigned subTagSize) const @@ -116,7 +69,7 @@ namespace string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) { - if (_location.isEmpty() || !_location.source.get() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0) + if (!_location.hasText() || _sourceCodes.empty()) return ""; auto it = _sourceCodes.find(_location.source->name()); @@ -144,7 +97,7 @@ public: void feed(AssemblyItem const& _item) { - if (!_item.location().isEmpty() && _item.location() != m_location) + if (_item.location().isValid() && _item.location() != m_location) { flush(); m_location = _item.location(); @@ -188,12 +141,12 @@ public: void printLocation() { - if (!m_location.source && m_location.isEmpty()) + if (!m_location.isValid()) return; m_out << m_prefix << " /*"; if (m_location.source) m_out << " \"" + m_location.source->name() + "\""; - if (!m_location.isEmpty()) + if (m_location.hasText()) m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); m_out << " " << locationFromSources(m_sourceCodes, m_location); m_out << " */" << endl; @@ -244,10 +197,11 @@ string Assembly::assemblyString(StringMap const& _sourceCodes) const return tmp.str(); } -Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) +Json::Value Assembly::createJsonValue(string _name, int _source, int _begin, int _end, string _value, string _jumpType) { Json::Value value; value["name"] = _name; + value["source"] = _source; value["begin"] = _begin; value["end"] = _end; if (!_value.empty()) @@ -264,65 +218,79 @@ string Assembly::toStringInHex(u256 _value) return hexStr.str(); } -Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const +Json::Value Assembly::assemblyJSON(map const& _sourceIndices) const { Json::Value root; Json::Value& collection = root[".code"] = Json::arrayValue; for (AssemblyItem const& i: m_items) { + unsigned sourceIndex = unsigned(-1); + if (i.location().source) + { + auto iter = _sourceIndices.find(i.location().source->name()); + if (iter != _sourceIndices.end()) + sourceIndex = iter->second; + } + switch (i.type()) { case Operation: collection.append( - createJsonValue(instructionInfo(i.instruction()).name, i.location().start, i.location().end, i.getJumpTypeAsString())); + createJsonValue( + instructionInfo(i.instruction()).name, + sourceIndex, + i.location().start, + i.location().end, + i.getJumpTypeAsString()) + ); break; case Push: collection.append( - createJsonValue("PUSH", i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString())); + createJsonValue("PUSH", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString())); break; case PushString: collection.append( - createJsonValue("PUSH tag", i.location().start, i.location().end, m_strings.at((h256)i.data()))); + createJsonValue("PUSH tag", sourceIndex, i.location().start, i.location().end, m_strings.at((h256)i.data()))); break; case PushTag: if (i.data() == 0) collection.append( - createJsonValue("PUSH [ErrorTag]", i.location().start, i.location().end, "")); + createJsonValue("PUSH [ErrorTag]", sourceIndex, i.location().start, i.location().end, "")); else collection.append( - createJsonValue("PUSH [tag]", i.location().start, i.location().end, toString(i.data()))); + createJsonValue("PUSH [tag]", sourceIndex, i.location().start, i.location().end, toString(i.data()))); break; case PushSub: collection.append( - createJsonValue("PUSH [$]", i.location().start, i.location().end, toString(h256(i.data())))); + createJsonValue("PUSH [$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data())))); break; case PushSubSize: collection.append( - createJsonValue("PUSH #[$]", i.location().start, i.location().end, toString(h256(i.data())))); + createJsonValue("PUSH #[$]", sourceIndex, i.location().start, i.location().end, toString(h256(i.data())))); break; case PushProgramSize: collection.append( - createJsonValue("PUSHSIZE", i.location().start, i.location().end)); + createJsonValue("PUSHSIZE", sourceIndex, i.location().start, i.location().end)); break; case PushLibraryAddress: collection.append( - createJsonValue("PUSHLIB", i.location().start, i.location().end, m_libraries.at(h256(i.data()))) + createJsonValue("PUSHLIB", sourceIndex, i.location().start, i.location().end, m_libraries.at(h256(i.data()))) ); break; case PushDeployTimeAddress: collection.append( - createJsonValue("PUSHDEPLOYADDRESS", i.location().start, i.location().end) + createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end) ); break; case Tag: collection.append( - createJsonValue("tag", i.location().start, i.location().end, toString(i.data()))); + createJsonValue("tag", sourceIndex, i.location().start, i.location().end, toString(i.data()))); collection.append( - createJsonValue("JUMPDEST", i.location().start, i.location().end)); + createJsonValue("JUMPDEST", sourceIndex, i.location().start, i.location().end)); break; case PushData: - collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data()))); + collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data()))); break; default: assertThrow(false, InvalidOpcode, ""); @@ -340,7 +308,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const { std::stringstream hexStr; hexStr << hex << i; - data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes); + data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceIndices); } } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 9e146be01..a76538375 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -56,7 +56,6 @@ public: AssemblyItem newPushLibraryAddress(std::string const& _identifier); AssemblyItem const& append(AssemblyItem const& _i); - AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } template Assembly& operator<<(T const& _d) { append(_d); return *this; } @@ -134,21 +133,9 @@ public: /// Create a JSON representation of the assembly. Json::Value assemblyJSON( - StringMap const& _sourceCodes = StringMap() + std::map const& _sourceIndices = std::map() ) const; -public: - // These features are only used by LLL - AssemblyItem newPushString(std::string const& _data) { util::h256 h(util::keccak256(_data)); m_strings[h] = _data; return AssemblyItem(PushString, h); } - - void append(Assembly const& _a); - void append(Assembly const& _a, int _deposit); - - void injectStart(AssemblyItem const& _i); - - AssemblyItem const& back() const { return m_items.back(); } - std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((util::h256)m_items.back().data()) : std::string(); } - protected: /// 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 @@ -158,7 +145,14 @@ protected: unsigned bytesRequired(unsigned subTagSize) const; private: - static Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()); + static Json::Value createJsonValue( + std::string _name, + int _source, + int _begin, + int _end, + std::string _value = std::string(), + std::string _jumpType = std::string() + ); static std::string toStringInHex(u256 _value); protected: diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index b9b6c4acd..c238a3a3c 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -19,12 +19,14 @@ #include #include +#include #include using namespace std; using namespace solidity; using namespace solidity::evmasm; +using namespace solidity::langutil; static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide"); @@ -281,3 +283,92 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item) } return _out; } + +std::string AssemblyItem::computeSourceMapping( + AssemblyItems const& _items, + map const& _sourceIndicesMap +) +{ + string ret; + + int prevStart = -1; + int prevLength = -1; + int prevSourceIndex = -1; + size_t prevModifierDepth = -1; + char prevJump = 0; + for (auto const& item: _items) + { + if (!ret.empty()) + ret += ";"; + + SourceLocation const& location = item.location(); + int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1; + int sourceIndex = + location.source && _sourceIndicesMap.count(location.source->name()) ? + _sourceIndicesMap.at(location.source->name()) : + -1; + char jump = '-'; + if (item.getJumpType() == evmasm::AssemblyItem::JumpType::IntoFunction) + jump = 'i'; + else if (item.getJumpType() == evmasm::AssemblyItem::JumpType::OutOfFunction) + jump = 'o'; + size_t modifierDepth = item.m_modifierDepth; + + unsigned components = 5; + if (modifierDepth == prevModifierDepth) + { + components--; + if (jump == prevJump) + { + components--; + if (sourceIndex == prevSourceIndex) + { + components--; + if (length == prevLength) + { + components--; + if (location.start == prevStart) + components--; + } + } + } + } + + if (components-- > 0) + { + if (location.start != prevStart) + ret += to_string(location.start); + if (components-- > 0) + { + ret += ':'; + if (length != prevLength) + ret += to_string(length); + if (components-- > 0) + { + ret += ':'; + if (sourceIndex != prevSourceIndex) + ret += to_string(sourceIndex); + if (components-- > 0) + { + ret += ':'; + if (jump != prevJump) + ret += jump; + if (components-- > 0) + { + ret += ':'; + if (modifierDepth != prevModifierDepth) + ret += to_string(modifierDepth); + } + } + } + } + } + + prevStart = location.start; + prevLength = length; + prevSourceIndex = sourceIndex; + prevJump = jump; + prevModifierDepth = modifierDepth; + } + return ret; +} diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 31f175103..e506a9fdb 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -48,6 +48,8 @@ enum AssemblyItemType { }; class Assembly; +class AssemblyItem; +using AssemblyItems = std::vector; class AssemblyItem { @@ -122,6 +124,11 @@ public: } bool operator!=(Instruction _instr) const { return !operator==(_instr); } + static std::string computeSourceMapping( + AssemblyItems const& _items, + std::map const& _sourceIndicesMap + ); + /// @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; @@ -157,8 +164,6 @@ private: mutable std::shared_ptr m_pushedValue; }; -using AssemblyItems = std::vector; - inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength) { size_t size = 0; diff --git a/libevmasm/CommonSubexpressionEliminator.h b/libevmasm/CommonSubexpressionEliminator.h index 0e6f6ee36..ce9c898ee 100644 --- a/libevmasm/CommonSubexpressionEliminator.h +++ b/libevmasm/CommonSubexpressionEliminator.h @@ -69,8 +69,8 @@ public: /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// item that must be fed into a new instance of the eliminator. /// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect - template - _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant); + template + AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant); /// @returns the resulting items after optimization. AssemblyItems getOptimizedItems(); @@ -169,10 +169,10 @@ private: std::map m_targetStack; }; -template -_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( - _AssemblyItemIterator _iterator, - _AssemblyItemIterator _end, +template +AssemblyItemIterator CommonSubexpressionEliminator::feedItems( + AssemblyItemIterator _iterator, + AssemblyItemIterator _end, bool _msizeImportant ) { diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 358d67c3a..6a07e9905 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -19,8 +19,6 @@ #include -#include - using namespace std; using namespace solidity; using namespace solidity::util; @@ -182,7 +180,14 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ case Instruction::EXP: gas = GasCosts::expGas; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) - gas += GasCosts::expByteGas(m_evmVersion) * (32 - (h256(*value).firstBitSet() / 8)); + { + if (*value) + { + // Note: msb() counts from 0 and throws on 0 as input. + unsigned const significantByteCount = (boost::multiprecision::msb(*value) + 1 + 7) / 8; + gas += GasCosts::expByteGas(m_evmVersion) * significantByteCount; + } + } else gas += GasCosts::expByteGas(m_evmVersion) * 32; break; diff --git a/libevmasm/KnownState.cpp b/libevmasm/KnownState.cpp index b6198e994..7e447a9a1 100644 --- a/libevmasm/KnownState.cpp +++ b/libevmasm/KnownState.cpp @@ -179,7 +179,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool /// Helper function for KnownState::reduceToCommonKnowledge, removes everything from /// _this which is not in or not equal to the value in _other. -template void intersect(_Mapping& _this, _Mapping const& _other) +template void intersect(Mapping& _this, Mapping const& _other) { for (auto it = _this.begin(); it != _this.end();) if (_other.count(it->first) && _other.at(it->first) == it->second) diff --git a/libevmasm/SimplificationRule.h b/libevmasm/SimplificationRule.h index 3e40e25ed..1fa1d8ea8 100644 --- a/libevmasm/SimplificationRule.h +++ b/libevmasm/SimplificationRule.h @@ -65,7 +65,7 @@ struct EVMBuiltins template constexpr Pattern operator()(Args&&... _args) const { return {inst, {std::forward(_args)...}}; - }; + } }; struct PatternGeneratorInstance @@ -74,7 +74,7 @@ struct EVMBuiltins template constexpr Pattern operator()(Args&&... _args) const { return {instruction, {std::forward(_args)...}}; - }; + } }; diff --git a/liblangutil/CMakeLists.txt b/liblangutil/CMakeLists.txt index df86476c2..ab30ac34e 100644 --- a/liblangutil/CMakeLists.txt +++ b/liblangutil/CMakeLists.txt @@ -16,6 +16,7 @@ set(sources SemVerHandler.cpp SemVerHandler.h SourceLocation.h + SourceLocation.cpp SourceReferenceExtractor.cpp SourceReferenceExtractor.h SourceReferenceFormatter.cpp diff --git a/liblangutil/CharStream.cpp b/liblangutil/CharStream.cpp index be236893f..046aca5d3 100644 --- a/liblangutil/CharStream.cpp +++ b/liblangutil/CharStream.cpp @@ -93,10 +93,13 @@ string CharStream::lineAtPosition(int _position) const lineStart = 0; else lineStart++; - return m_source.substr( + string line = m_source.substr( lineStart, min(m_source.find('\n', lineStart), m_source.size()) - lineStart ); + if (!line.empty() && line.back() == '\r') + line.pop_back(); + return line; } tuple CharStream::translatePositionToLineColumn(int _position) const diff --git a/liblangutil/ErrorReporter.cpp b/liblangutil/ErrorReporter.cpp index 312f159c0..c6c059bc7 100644 --- a/liblangutil/ErrorReporter.cpp +++ b/liblangutil/ErrorReporter.cpp @@ -244,3 +244,12 @@ void ErrorReporter::docstringParsingError(string const& _description) _description ); } + +void ErrorReporter::docstringParsingError(SourceLocation const& _location, string const& _description) +{ + error( + Error::Type::DocstringParsingError, + _location, + _description + ); +} diff --git a/liblangutil/ErrorReporter.h b/liblangutil/ErrorReporter.h index 3acf5afee..5aca4beb9 100644 --- a/liblangutil/ErrorReporter.h +++ b/liblangutil/ErrorReporter.h @@ -107,6 +107,7 @@ public: void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description); void docstringParsingError(std::string const& _description); + void docstringParsingError(SourceLocation const& _location, std::string const& _description); ErrorList const& errors() const; diff --git a/liblangutil/Exceptions.cpp b/liblangutil/Exceptions.cpp index 59d81b6e6..f035f5044 100644 --- a/liblangutil/Exceptions.cpp +++ b/liblangutil/Exceptions.cpp @@ -51,7 +51,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip break; } - if (!_location.isEmpty()) + if (_location.isValid()) *this << errinfo_sourceLocation(_location); if (!_description.empty()) *this << util::errinfo_comment(_description); @@ -60,7 +60,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip Error::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location): Error(_type) { - if (!_location.isEmpty()) + if (_location.isValid()) *this << errinfo_sourceLocation(_location); *this << util::errinfo_comment(_description); } diff --git a/liblangutil/Exceptions.h b/liblangutil/Exceptions.h index de72eba37..575847ed9 100644 --- a/liblangutil/Exceptions.h +++ b/liblangutil/Exceptions.h @@ -40,6 +40,7 @@ struct CompilerError: virtual util::Exception {}; struct InternalCompilerError: virtual util::Exception {}; struct FatalError: virtual util::Exception {}; struct UnimplementedFeatureError: virtual util::Exception {}; +struct InvalidAstError: virtual util::Exception {}; /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ @@ -51,6 +52,9 @@ struct UnimplementedFeatureError: virtual util::Exception {}; #define solUnimplemented(DESCRIPTION) \ solUnimplementedAssert(false, DESCRIPTION) +#define astAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION) + class Error: virtual public util::Exception { public: diff --git a/liblangutil/ParserBase.cpp b/liblangutil/ParserBase.cpp index 3c0fe0b89..747cb37cf 100644 --- a/liblangutil/ParserBase.cpp +++ b/liblangutil/ParserBase.cpp @@ -28,14 +28,9 @@ using namespace std; using namespace solidity; using namespace solidity::langutil; -int ParserBase::position() const +SourceLocation ParserBase::currentLocation() const { - return m_scanner->currentLocation().start; -} - -int ParserBase::endPosition() const -{ - return m_scanner->currentLocation().end; + return m_scanner->currentLocation(); } Token ParserBase::currentToken() const @@ -101,8 +96,8 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN Token tok = m_scanner->currentToken(); if (tok != _value) { - int startPosition = position(); - SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()}; + SourceLocation errorLoc = currentLocation(); + int startPosition = errorLoc.start; while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS) m_scanner->next(); @@ -150,7 +145,7 @@ void ParserBase::decreaseRecursionDepth() void ParserBase::parserWarning(string const& _description) { - m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description); + m_errorReporter.warning(currentLocation(), _description); } void ParserBase::parserError(SourceLocation const& _location, string const& _description) @@ -160,12 +155,12 @@ void ParserBase::parserError(SourceLocation const& _location, string const& _des void ParserBase::parserError(string const& _description) { - parserError(SourceLocation{position(), endPosition(), source()}, _description); + parserError(currentLocation(), _description); } void ParserBase::fatalParserError(string const& _description) { - fatalParserError(SourceLocation{position(), endPosition(), source()}, _description); + fatalParserError(currentLocation(), _description); } void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description) diff --git a/liblangutil/ParserBase.h b/liblangutil/ParserBase.h index 08f2a9dad..575a049e2 100644 --- a/liblangutil/ParserBase.h +++ b/liblangutil/ParserBase.h @@ -62,10 +62,8 @@ protected: ParserBase& m_parser; }; - /// Start position of the current token - int position() const; - /// End position of the current token - int endPosition() const; + /// Location of the current token + SourceLocation currentLocation() const; ///@{ ///@name Helper functions diff --git a/liblangutil/Scanner.cpp b/liblangutil/Scanner.cpp index 2db12225a..9d748c570 100644 --- a/liblangutil/Scanner.cpp +++ b/liblangutil/Scanner.cpp @@ -108,18 +108,18 @@ public: m_complete(false) { if (_type == LITERAL_TYPE_COMMENT) - m_scanner->m_nextSkippedComment.literal.clear(); + m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); else - m_scanner->m_nextToken.literal.clear(); + m_scanner->m_tokens[Scanner::NextNext].literal.clear(); } ~LiteralScope() { if (!m_complete) { if (m_type == LITERAL_TYPE_COMMENT) - m_scanner->m_nextSkippedComment.literal.clear(); + m_scanner->m_skippedComments[Scanner::NextNext].literal.clear(); else - m_scanner->m_nextToken.literal.clear(); + m_scanner->m_tokens[Scanner::NextNext].literal.clear(); } } void complete() { m_complete = true; } @@ -151,6 +151,7 @@ void Scanner::reset() skipWhitespace(); next(); next(); + next(); } void Scanner::setPosition(size_t _offset) @@ -158,6 +159,7 @@ void Scanner::setPosition(size_t _offset) m_char = m_source->setPosition(_offset); scanToken(); next(); + next(); } void Scanner::supportPeriodInIdentifier(bool _value) @@ -222,13 +224,14 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint) void Scanner::rescan() { size_t rollbackTo = 0; - if (m_skippedComment.literal.empty()) - rollbackTo = m_currentToken.location.start; + if (m_skippedComments[Current].literal.empty()) + rollbackTo = m_tokens[Current].location.start; else - rollbackTo = m_skippedComment.location.start; + rollbackTo = m_skippedComments[Current].location.start; m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo); next(); next(); + next(); } // Ensure that tokens can be stored in a byte. @@ -236,11 +239,14 @@ BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100); Token Scanner::next() { - m_currentToken = m_nextToken; - m_skippedComment = m_nextSkippedComment; + m_tokens[Current] = std::move(m_tokens[Next]); + m_tokens[Next] = std::move(m_tokens[NextNext]); + m_skippedComments[Current] = std::move(m_skippedComments[Next]); + m_skippedComments[Next] = std::move(m_skippedComments[NextNext]); + scanToken(); - return m_currentToken.token; + return m_tokens[Current].token; } Token Scanner::selectToken(char _next, Token _then, Token _else) @@ -300,19 +306,24 @@ bool Scanner::tryScanEndOfLine() return false; } -Token Scanner::scanSingleLineDocComment() +int Scanner::scanSingleLineDocComment() { LiteralScope literal(this, LITERAL_TYPE_COMMENT); + int endPosition = m_source->position(); advance(); //consume the last '/' at /// skipWhitespaceExceptUnicodeLinebreak(); while (!isSourcePastEndOfInput()) { + endPosition = m_source->position(); if (tryScanEndOfLine()) { - // check if next line is also a documentation comment - skipWhitespace(); + // Check if next line is also a single-line comment. + // If any whitespaces were skipped, use source position before. + if (!skipWhitespace()) + endPosition = m_source->position(); + if (!m_source->isPastEndOfInput(3) && m_source->get(0) == '/' && m_source->get(1) == '/' && @@ -332,7 +343,7 @@ Token Scanner::scanSingleLineDocComment() advance(); } literal.complete(); - return Token::CommentLiteral; + return endPosition; } Token Scanner::skipMultiLineComment() @@ -420,11 +431,10 @@ Token Scanner::scanSlash() else if (m_char == '/') { // doxygen style /// comment - Token comment; - m_nextSkippedComment.location.start = firstSlashPosition; - comment = scanSingleLineDocComment(); - m_nextSkippedComment.location.end = sourcePos(); - m_nextSkippedComment.token = comment; + m_skippedComments[NextNext].location.start = firstSlashPosition; + m_skippedComments[NextNext].location.source = m_source; + m_skippedComments[NextNext].token = Token::CommentLiteral; + m_skippedComments[NextNext].location.end = scanSingleLineDocComment(); return Token::Whitespace; } else @@ -447,10 +457,11 @@ Token Scanner::scanSlash() } // we actually have a multiline documentation comment Token comment; - m_nextSkippedComment.location.start = firstSlashPosition; + m_skippedComments[NextNext].location.start = firstSlashPosition; + m_skippedComments[NextNext].location.source = m_source; comment = scanMultiLineDocComment(); - m_nextSkippedComment.location.end = sourcePos(); - m_nextSkippedComment.token = comment; + m_skippedComments[NextNext].location.end = sourcePos(); + m_skippedComments[NextNext].token = comment; if (comment == Token::Illegal) return Token::Illegal; // error already set else @@ -467,11 +478,8 @@ 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(); - m_nextSkippedComment.extendedTokenInfo = make_tuple(0, 0); + m_tokens[NextNext] = {}; + m_skippedComments[NextNext] = {}; Token token; // M and N are for the purposes of grabbing different type sizes @@ -480,7 +488,7 @@ void Scanner::scanToken() do { // Remember the position of the next token - m_nextToken.location.start = sourcePos(); + m_tokens[NextNext].location.start = sourcePos(); switch (m_char) { case '"': @@ -675,9 +683,10 @@ void Scanner::scanToken() // whitespace. } while (token == Token::Whitespace); - m_nextToken.location.end = sourcePos(); - m_nextToken.token = token; - m_nextToken.extendedTokenInfo = make_tuple(m, n); + m_tokens[NextNext].location.end = sourcePos(); + m_tokens[NextNext].location.source = m_source; + m_tokens[NextNext].token = token; + m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n); } bool Scanner::scanEscape() @@ -927,7 +936,7 @@ tuple Scanner::scanIdentifierOrKeyword() while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier)) addLiteralCharAndAdvance(); literal.complete(); - return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal); + return TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal); } } // namespace solidity::langutil diff --git a/liblangutil/Scanner.h b/liblangutil/Scanner.h index 20dfbf29c..5b365f134 100644 --- a/liblangutil/Scanner.h +++ b/liblangutil/Scanner.h @@ -98,6 +98,7 @@ public: std::string const& source() const noexcept { return m_source->source(); } std::shared_ptr charStream() noexcept { return m_source; } + std::shared_ptr charStream() const noexcept { return m_source; } /// Resets the scanner as if newly constructed with _source as input. void reset(CharStream _source); @@ -121,32 +122,32 @@ public: /// @returns the current token Token currentToken() const { - return m_currentToken.token; + return m_tokens[Current].token; } ElementaryTypeNameToken currentElementaryTypeNameToken() const { unsigned firstSize; unsigned secondSize; - std::tie(firstSize, secondSize) = m_currentToken.extendedTokenInfo; - return ElementaryTypeNameToken(m_currentToken.token, firstSize, secondSize); + std::tie(firstSize, secondSize) = m_tokens[Current].extendedTokenInfo; + return ElementaryTypeNameToken(m_tokens[Current].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; } + SourceLocation currentLocation() const { return m_tokens[Current].location; } + std::string const& currentLiteral() const { return m_tokens[Current].literal; } + std::tuple const& currentTokenInfo() const { return m_tokens[Current].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; } + ScannerError currentError() const noexcept { return m_tokens[Current].error; } ///@} ///@{ ///@name Information about the current comment token - SourceLocation currentCommentLocation() const { return m_skippedComment.location; } - std::string const& currentCommentLiteral() const { return m_skippedComment.literal; } + SourceLocation currentCommentLocation() const { return m_skippedComments[Current].location; } + std::string const& currentCommentLiteral() const { return m_skippedComments[Current].literal; } /// Called by the parser during FunctionDefinition parsing to clear the current comment - void clearCurrentCommentLiteral() { m_skippedComment.literal.clear(); } + void clearCurrentCommentLiteral() { m_skippedComments[Current].literal.clear(); } ///@} @@ -154,9 +155,11 @@ public: ///@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; } + Token peekNextToken() const { return m_tokens[Next].token; } + SourceLocation peekLocation() const { return m_tokens[Next].location; } + std::string const& peekLiteral() const { return m_tokens[Next].literal; } + + Token peekNextNextToken() const { return m_tokens[NextNext].token; } ///@} ///@{ @@ -165,18 +168,12 @@ public: /// 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; + m_tokens[NextNext].error = _error; return Token::Illegal; } @@ -192,8 +189,8 @@ private: ///@{ ///@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 addLiteralChar(char c) { m_tokens[NextNext].literal.push_back(c); } + inline void addCommentLiteralChar(char c) { m_skippedComments[NextNext].literal.push_back(c); } inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); } void addUnicodeAsUTF8(unsigned codepoint); ///@} @@ -233,7 +230,8 @@ private: Token scanString(); Token scanHexString(); - Token scanSingleLineDocComment(); + /// Scans a single line comment and returns its corrected end position. + int scanSingleLineDocComment(); Token scanMultiLineDocComment(); /// Scans a slash '/' and depending on the characters returns the appropriate token Token scanSlash(); @@ -252,11 +250,10 @@ private: bool m_supportPeriodInIdentifier = false; - TokenDesc m_skippedComment; // desc for current skipped comment - TokenDesc m_nextSkippedComment; // desc for next skipped comment + enum TokenIndex { Current, Next, NextNext }; - TokenDesc m_currentToken; // desc for current token (as returned by Next()) - TokenDesc m_nextToken; // desc for next token (one token look-ahead) + TokenDesc m_skippedComments[3] = {}; // desc for the current, next and nextnext skipped comment + TokenDesc m_tokens[3] = {}; // desc for the current, next and nextnext token std::shared_ptr m_source; diff --git a/liblangutil/SourceLocation.cpp b/liblangutil/SourceLocation.cpp new file mode 100644 index 000000000..8c531c625 --- /dev/null +++ b/liblangutil/SourceLocation.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 . +*/ + +#include + +#include +#include + +using namespace solidity; +namespace solidity::langutil +{ + +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex) +{ + // Expected input: "start:length:sourceindex" + enum SrcElem : size_t { Start, Length, Index }; + + std::vector pos; + + boost::algorithm::split(pos, _input, boost::is_any_of(":")); + + astAssert( + pos.size() == 3 && + _maxIndex >= static_cast(stoi(pos[Index])), + "'src'-field ill-formatted or src-index too high" + ); + + int start = stoi(pos[Start]); + int end = start + stoi(pos[Length]); + + // ASSUMPTION: only the name of source is used from here on, the m_source of the CharStream-Object can be empty + std::shared_ptr source = std::make_shared("", _sourceName); + + return SourceLocation{start, end, source}; +} + +} diff --git a/liblangutil/SourceLocation.h b/liblangutil/SourceLocation.h index d86dd206a..e6f97942e 100644 --- a/liblangutil/SourceLocation.h +++ b/liblangutil/SourceLocation.h @@ -23,13 +23,12 @@ #pragma once #include -#include // defines noexcept macro for MSVC #include + #include + #include #include -#include -#include namespace solidity::langutil { @@ -46,16 +45,44 @@ struct SourceLocation 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; } + inline bool 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); + } + + inline bool contains(SourceLocation const& _other) const + { + if (!hasText() || !_other.hasText() || source.get() != _other.source.get()) + return false; + return start <= _other.start && _other.end <= end; + } + + inline bool intersects(SourceLocation const& _other) const + { + if (!hasText() || !_other.hasText() || source.get() != _other.source.get()) + return false; + return _other.start < end && start < _other.end; + } + + bool isValid() const { return source || start != -1 || end != -1; } + + bool hasText() const + { + return + source && + 0 <= start && + start <= end && + end <= int(source->source().length()); + } std::string text() const { assertThrow(source, SourceLocationError, "Requested text from null source."); - assertThrow(!isEmpty(), SourceLocationError, "Requested text from empty source location."); + assertThrow(0 <= start, SourceLocationError, "Invalid 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); @@ -86,40 +113,20 @@ struct SourceLocation std::shared_ptr source; }; +SourceLocation const parseSourceLocation(std::string const& _input, std::string const& _sourceName, size_t _maxIndex = -1); + /// Stream output for Location (used e.g. in boost exceptions). inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location) { - if (_location.isEmpty()) + if (!_location.isValid()) return _out << "NO_LOCATION_SPECIFIED"; if (_location.source) _out << _location.source->name(); - _out << "[" << _location.start << "," << _location.end << ")"; + _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 index 44f1540ca..0817e7750 100644 --- a/liblangutil/SourceReferenceExtractor.cpp +++ b/liblangutil/SourceReferenceExtractor.cpp @@ -46,7 +46,7 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio if (!_location || !_location->source.get()) // Nothing we can extract here return SourceReference::MessageOnly(std::move(message)); - if (_location->source->source().empty()) // No source text, so we can only extract the source name + if (!_location->hasText()) // No source text, so we can only extract the source name return SourceReference::MessageOnly(std::move(message), _location->source->name()); shared_ptr const& source = _location->source; diff --git a/liblangutil/SourceReferenceFormatter.cpp b/liblangutil/SourceReferenceFormatter.cpp index 588733d27..6b1a8286a 100644 --- a/liblangutil/SourceReferenceFormatter.cpp +++ b/liblangutil/SourceReferenceFormatter.cpp @@ -69,6 +69,8 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref) { if (_ref.position.line != -1) m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": "; + else if (!_ref.sourceName.empty()) + m_stream << _ref.sourceName << ": "; } void SourceReferenceFormatter::printExceptionInformation(util::Exception const& _exception, std::string const& _category) diff --git a/liblangutil/SourceReferenceFormatterHuman.cpp b/liblangutil/SourceReferenceFormatterHuman.cpp index 6cd5620fb..669fa2007 100644 --- a/liblangutil/SourceReferenceFormatterHuman.cpp +++ b/liblangutil/SourceReferenceFormatterHuman.cpp @@ -67,13 +67,20 @@ AnsiColorized SourceReferenceFormatterHuman::diagColored() const void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref) { - if (_ref.position.line < 0) + if (_ref.sourceName.empty()) 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, ' ') << "--> "; + + if (_ref.position.line < 0) + { + m_stream << _ref.sourceName << "\n"; + return; // No line available, nothing else to print + } + m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n'; if (!_ref.multiline) diff --git a/liblll/CMakeLists.txt b/liblll/CMakeLists.txt deleted file mode 100644 index a6c8f3a9a..000000000 --- a/liblll/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -set(sources - CodeFragment.cpp - CodeFragment.h - Compiler.cpp - Compiler.h - CompilerState.cpp - CompilerState.h - Exceptions.h - Parser.cpp - Parser.h -) - -add_library(lll ${sources}) -target_link_libraries(lll PUBLIC evmasm solutil) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp deleted file mode 100644 index 556f3dcf6..000000000 --- a/liblll/CodeFragment.cpp +++ /dev/null @@ -1,758 +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 . -*/ -/** @file CodeFragment.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include -#include -#include -#include - -#include - -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif // defined(__GNUC__) - -#include - -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif // defined(__GNUC__) - - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; -using namespace solidity::lll; - -void CodeFragment::finalise(CompilerState const& _cs) -{ - // NOTE: add this as a safeguard in case the user didn't issue an - // explicit stop at the end of the sequence - m_asm.append(Instruction::STOP); - - if (_cs.usedAlloc && _cs.vars.size() && !m_finalised) - { - m_finalised = true; - m_asm.injectStart(Instruction::MSTORE8); - m_asm.injectStart((u256)((_cs.vars.size() + 2) * 32) - 1); - m_asm.injectStart((u256)1); - } -} - -namespace -{ -/// Returns true iff the instruction is valid in "inline assembly". -bool validAssemblyInstruction(string us) -{ - auto it = c_instructions.find(us); - return !( - it == c_instructions.end() || - isPushInstruction(it->second) - ); -} - -/// Returns true iff the instruction is valid as a function. -bool validFunctionalInstruction(string us) -{ - auto it = c_instructions.find(us); - return !( - it == c_instructions.end() || - isPushInstruction(it->second) || - isDupInstruction(it->second) || - isSwapInstruction(it->second) || - it->second == Instruction::JUMPDEST - ); -} -} - -CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM): - m_readFile(_readFile) -{ -/* - std::cout << "CodeFragment. Locals:"; - for (auto const& i: _s.defs) - std::cout << i.first << ":" << i.second.m_asm.out(); - std::cout << "Args:"; - for (auto const& i: _s.args) - std::cout << i.first << ":" << i.second.m_asm.out(); - std::cout << "Outers:"; - for (auto const& i: _s.outers) - std::cout << i.first << ":" << i.second.m_asm.out(); - debugOutAST(std::cout, _t); - std::cout << endl << flush; -*/ - switch (_t.which()) - { - case sp::utree_type::list_type: - constructOperation(_t, _s); - break; - case sp::utree_type::string_type: - { - auto sr = _t.get, sp::utree_type::string_type>>(); - string s(sr.begin(), sr.end()); - m_asm.append(s); - break; - } - case sp::utree_type::symbol_type: - { - auto sr = _t.get, sp::utree_type::symbol_type>>(); - string s(sr.begin(), sr.end()); - string us = boost::algorithm::to_upper_copy(s); - if (_allowASM && c_instructions.count(us) && validAssemblyInstruction(us)) - m_asm.append(c_instructions.at(us)); - else if (_s.defs.count(s)) - m_asm.append(_s.defs.at(s).m_asm); - else if (_s.args.count(s)) - m_asm.append(_s.args.at(s).m_asm); - else if (_s.outers.count(s)) - m_asm.append(_s.outers.at(s).m_asm); - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) - { - auto it = _s.vars.find(s); - if (it == _s.vars.end()) - error(std::string("Symbol not found: ") + s); - m_asm.append((u256)it->second.first); - } - else - error(s); - - break; - } - case sp::utree_type::any_type: - { - bigint i = *_t.get(); - if (i < 0 || i > bigint(u256(0) - 1)) - error(toString(i)); - m_asm.append((u256)i); - break; - } - default: - error("Unexpected fragment type"); - break; - } -} - -void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) -{ - if (_t.tag() == 0 && _t.empty()) - error(); - else if (_t.tag() == 0 && _t.front().which() != sp::utree_type::symbol_type) - error(); - else - { - string s; - string us; - switch (_t.tag()) - { - case 0: - { - auto sr = _t.front().get, sp::utree_type::symbol_type>>(); - s = string(sr.begin(), sr.end()); - us = boost::algorithm::to_upper_copy(s); - break; - } - case 1: - us = "MLOAD"; - break; - case 2: - us = "SLOAD"; - break; - case 3: - us = "MSTORE"; - break; - case 4: - us = "SSTORE"; - break; - case 5: - us = "SEQ"; - break; - case 6: - us = "CALLDATALOAD"; - break; - default:; - } - - auto firstAsString = [&]() - { - auto i = *++_t.begin(); - if (i.tag()) - error(toString(i)); - if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - return string(sr.begin(), sr.end()); - } - else if (i.which() == sp::utree_type::symbol_type) - { - auto sr = i.get, sp::utree_type::symbol_type>>(); - return _s.getDef(string(sr.begin(), sr.end())).m_asm.backString(); - } - return string(); - }; - - auto varAddress = [&](string const& n, bool createMissing = false) - { - if (n.empty()) - error("Empty variable name not allowed"); - auto it = _s.vars.find(n); - if (it == _s.vars.end()) - { - if (createMissing) - { - // Create new variable - bool ok; - tie(it, ok) = _s.vars.insert(make_pair(n, make_pair(_s.stackSize, 32))); - _s.stackSize += 32; - } - else - error(std::string("Symbol not found: ") + n); - } - return it->second.first; - }; - - // Operations who args are not standard stack-pushers. - bool nonStandard = true; - if (us == "ASM") - { - int c = 0; - for (auto const& i: _t) - if (c++) - { - auto fragment = CodeFragment(i, _s, m_readFile, true).m_asm; - if ((m_asm.deposit() + fragment.deposit()) < 0) - error("The assembly instruction resulted in stack underflow"); - m_asm.append(fragment); - } - } - else if (us == "INCLUDE") - { - if (_t.size() != 2) - error(us); - string fileName = firstAsString(); - if (fileName.empty()) - error("Empty file name provided"); - if (!m_readFile) - error("Import callback not present"); - string contents = m_readFile(fileName); - if (contents.empty()) - error(std::string("File not found (or empty): ") + fileName); - 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; - for (auto const& i: _t) - if (c++ == 2) - m_asm.append(CodeFragment(i, _s, m_readFile, false).m_asm); - 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) - error(us); - 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") - { - string n; - unsigned ii = 0; - if (_t.size() != 3 && _t.size() != 4) - error(us); - vector args; - for (auto const& i: _t) - { - if (ii == 1) - { - if (i.tag()) - error(toString(i)); - if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - n = string(sr.begin(), sr.end()); - } - else if (i.which() == sp::utree_type::symbol_type) - { - auto sr = i.get, sp::utree_type::symbol_type>>(); - n = _s.getDef(string(sr.begin(), sr.end())).m_asm.backString(); - } - } - else if (ii == 2) - if (_t.size() == 3) - { - /// NOTE: some compilers could do the assignment first if this is done in a single line - CodeFragment code = CodeFragment(i, _s, m_readFile); - _s.defs[n] = code; - } - else - for (auto const& j: i) - { - if (j.tag() || j.which() != sp::utree_type::symbol_type) - error(); - auto sr = j.get, sp::utree_type::symbol_type>>(); - args.emplace_back(sr.begin(), sr.end()); - } - else if (ii == 3) - { - auto k = make_pair(n, args.size()); - _s.macros[k].code = i; - _s.macros[k].env = _s.outers; - _s.macros[k].args = args; - for (auto const& i: _s.args) - _s.macros[k].env[i.first] = i.second; - for (auto const& i: _s.defs) - _s.macros[k].env[i.first] = i.second; - } - ++ii; - } - } - else if (us == "LIT") - { - if (_t.size() < 3) - error(us); - unsigned ii = 0; - CodeFragment pos; - bytes data; - for (auto const& i: _t) - { - if (ii == 0) - { - ii++; - continue; - } - else if (ii == 1) - { - pos = CodeFragment(i, _s, m_readFile); - if (pos.m_asm.deposit() != 1) - error(toString(i)); - } - else if (i.tag() != 0) - { - error(toString(i)); - } - else if (i.which() == sp::utree_type::string_type) - { - auto sr = i.get, sp::utree_type::string_type>>(); - data.insert(data.end(), (uint8_t const *)sr.begin(), (uint8_t const*)sr.end()); - } - else if (i.which() == sp::utree_type::any_type) - { - bigint bi = *i.get(); - if (bi < 0) - error(toString(i)); - else - { - bytes tmp = toCompactBigEndian(bi); - data.insert(data.end(), tmp.begin(), tmp.end()); - } - } - else - { - error(toString(i)); - } - - ii++; - } - m_asm.append((u256)data.size()); - m_asm.append(Instruction::DUP1); - m_asm.append(data); - m_asm.append(pos.m_asm, 1); - m_asm.append(Instruction::CODECOPY); - } - else - nonStandard = false; - - if (nonStandard) - return; - - std::map const c_arith = { - { "+", Instruction::ADD }, - { "-", Instruction::SUB }, - { "*", Instruction::MUL }, - { "/", Instruction::DIV }, - { "%", Instruction::MOD }, - { "&", Instruction::AND }, - { "|", Instruction::OR }, - { "^", Instruction::XOR } - }; - std::map> const c_binary = { - { "<", { Instruction::LT, false } }, - { "<=", { Instruction::GT, true } }, - { ">", { Instruction::GT, false } }, - { ">=", { Instruction::LT, true } }, - { "S<", { Instruction::SLT, false } }, - { "S<=", { Instruction::SGT, true } }, - { "S>", { Instruction::SGT, false } }, - { "S>=", { Instruction::SLT, true } }, - { "=", { Instruction::EQ, false } }, - { "!=", { Instruction::EQ, true } } - }; - std::map const c_unary = { - { "!", Instruction::ISZERO }, - { "~", Instruction::NOT } - }; - - vector code; - CompilerState ns = _s; - ns.vars.clear(); - ns.usedAlloc = false; - int c = _t.tag() ? 1 : 0; - for (auto const& i: _t) - if (c++) - { - if (us == "LLL" && c == 1) - code.emplace_back(i, ns, m_readFile); - else - 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); }; - auto requireMaxSize = [&](unsigned s) { if (code.size() > s) error(us); }; - auto requireDeposit = [&](unsigned i, int s) { if (code[i].m_asm.deposit() != s) error(us); }; - - if (_s.macros.count(make_pair(s, code.size()))) - { - Macro const& m = _s.macros.at(make_pair(s, code.size())); - CompilerState cs = _s; - for (auto const& i: m.env) - cs.outers[i.first] = i.second; - for (auto const& i: cs.defs) - cs.outers[i.first] = i.second; - cs.defs.clear(); - for (unsigned i = 0; i < m.args.size(); ++i) - { - //requireDeposit(i, 1); - cs.args[m.args[i]] = code[i]; - } - m_asm.append(CodeFragment(m.code, cs, m_readFile).m_asm); - for (auto const& i: cs.defs) - _s.defs[i.first] = i.second; - for (auto const& i: cs.macros) - _s.macros.insert(i); - } - else if (c_instructions.count(us) && validFunctionalInstruction(us)) - { - auto it = c_instructions.find(us); - requireSize(instructionInfo(it->second).args); - - for (unsigned i = code.size(); i; --i) - m_asm.append(code[i - 1].m_asm, 1); - m_asm.append(it->second); - } - else if (c_arith.count(us)) - { - auto it = c_arith.find(us); - requireMinSize(1); - for (unsigned i = code.size(); i; --i) - { - requireDeposit(i - 1, 1); - m_asm.append(code[i - 1].m_asm, 1); - } - for (unsigned i = 1; i < code.size(); ++i) - m_asm.append(it->second); - } - else if (c_binary.count(us)) - { - auto it = c_binary.find(us); - requireSize(2); - requireDeposit(0, 1); - requireDeposit(1, 1); - m_asm.append(code[1].m_asm, 1); - m_asm.append(code[0].m_asm, 1); - m_asm.append(it->second.first); - if (it->second.second) - m_asm.append(Instruction::ISZERO); - } - else if (c_unary.count(us)) - { - auto it = c_unary.find(us); - requireSize(1); - requireDeposit(0, 1); - m_asm.append(code[0].m_asm, 1); - m_asm.append(it->second); - } - else if (us == "IF") - { - requireSize(3); - requireDeposit(0, 1); - int minDep = min(code[1].m_asm.deposit(), code[2].m_asm.deposit()); - - m_asm.append(code[0].m_asm); - auto mainBranch = m_asm.appendJumpI(); - - /// The else branch. - int startDeposit = m_asm.deposit(); - m_asm.append(code[2].m_asm, minDep); - auto end = m_asm.appendJump(); - int deposit = m_asm.deposit(); - m_asm.setDeposit(startDeposit); - - /// The main branch. - m_asm << mainBranch.tag(); - m_asm.append(code[1].m_asm, minDep); - m_asm << end.tag(); - if (m_asm.deposit() != deposit) - error(us); - } - else if (us == "WHEN" || us == "UNLESS") - { - requireSize(2); - requireDeposit(0, 1); - - m_asm.append(code[0].m_asm); - if (us == "WHEN") - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[1].m_asm, 0); - m_asm << end.tag(); - } - else if (us == "WHILE" || us == "UNTIL") - { - requireSize(2); - requireDeposit(0, 1); - - auto begin = m_asm.append(m_asm.newTag()); - m_asm.append(code[0].m_asm); - if (us == "WHILE") - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[1].m_asm, 0); - m_asm.appendJump(begin); - m_asm << end.tag(); - } - else if (us == "FOR") - { - requireSize(4); - requireDeposit(1, 1); - - m_asm.append(code[0].m_asm, 0); - auto begin = m_asm.append(m_asm.newTag()); - m_asm.append(code[1].m_asm); - m_asm.append(Instruction::ISZERO); - auto end = m_asm.appendJumpI(); - m_asm.append(code[3].m_asm, 0); - m_asm.append(code[2].m_asm, 0); - m_asm.appendJump(begin); - m_asm << end.tag(); - } - else if (us == "SWITCH") - { - requireMinSize(1); - - bool hasDefault = (code.size() % 2 == 1); - int startDeposit = m_asm.deposit(); - int targetDeposit = hasDefault ? code[code.size() - 1].m_asm.deposit() : 0; - - // The conditions - evmasm::AssemblyItems jumpTags; - for (unsigned i = 0; i < code.size() - 1; i += 2) - { - requireDeposit(i, 1); - m_asm.append(code[i].m_asm); - jumpTags.push_back(m_asm.appendJumpI()); - } - - // The default, if present - if (hasDefault) - m_asm.append(code[code.size() - 1].m_asm); - - // The targets - appending in reverse makes the top case the most efficient. - if (code.size() > 1) - { - auto end = m_asm.appendJump(); - for (int i = 2 * (code.size() / 2 - 1); i >= 0; i -= 2) - { - m_asm << jumpTags[i / 2].tag(); - requireDeposit(i + 1, targetDeposit); - m_asm.append(code[i + 1].m_asm); - if (i != 0) - m_asm.appendJump(end); - } - m_asm << end.tag(); - } - - m_asm.setDeposit(startDeposit + targetDeposit); - } - else if (us == "ALLOC") - { - requireSize(1); - requireDeposit(0, 1); - - // (alloc N): - // - Evaluates to (msize) before the allocation - the start of the allocated memory - // - Does not allocate memory when N is zero - // - Size of memory allocated is N bytes rounded up to a multiple of 32 - // - Uses MLOAD to expand MSIZE to avoid modifying memory. - - 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(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(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::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(end); - m_asm.append(Instruction::POP); // Discard duplicate N - - _s.usedAlloc = true; - } - else if (us == "LLL") - { - requireMinSize(2); - requireMaxSize(3); - requireDeposit(1, 1); - - auto subPush = m_asm.appendSubroutine(make_shared(code[0].assembly(ns))); - m_asm.append(Instruction::DUP1); - if (code.size() == 3) - { - requireDeposit(2, 1); - m_asm.append(code[2].m_asm, 1); - m_asm.append(Instruction::LT); - m_asm.append(Instruction::ISZERO); - m_asm.append(Instruction::MUL); - m_asm.append(Instruction::DUP1); - } - m_asm.append(subPush); - m_asm.append(code[1].m_asm, 1); - m_asm.append(Instruction::CODECOPY); - } - else if (us == "&&" || us == "||") - { - requireMinSize(1); - for (unsigned i = 0; i < code.size(); ++i) - requireDeposit(i, 1); - - auto end = m_asm.newTag(); - if (code.size() > 1) - { - m_asm.append((u256)(us == "||" ? 1 : 0)); - for (unsigned i = 1; i < code.size(); ++i) - { - // Check if true - predicate - m_asm.append(code[i - 1].m_asm, 1); - if (us == "&&") - m_asm.append(Instruction::ISZERO); - m_asm.appendJumpI(end); - } - m_asm.append(Instruction::POP); - } - - // Check if true - predicate - m_asm.append(code.back().m_asm, 1); - - // At end now. - m_asm.append(end); - } - else if (us == "SEQ") - { - unsigned ii = 0; - for (auto const& i: code) - if (++ii < code.size()) - m_asm.append(i.m_asm, 0); - else - m_asm.append(i.m_asm); - } - else if (us == "RAW") - { - for (auto const& i: code) - m_asm.append(i.m_asm); - // Leave only the last item on stack. - while (m_asm.deposit() > 1) - m_asm.append(Instruction::POP); - } - else if (us == "BYTECODESIZE") - { - m_asm.appendProgramSize(); - } - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) - m_asm.append((u256)varAddress(s)); - else - error("Unsupported keyword: '" + us + "'"); - } -} - -CodeFragment CodeFragment::compile(string _src, CompilerState& _s, ReadCallback const& _readFile) -{ - CodeFragment ret; - sp::utree o; - parseTreeLLL(std::move(_src), o); - if (!o.empty()) - ret = CodeFragment(o, _s, _readFile); - _s.treesToKill.push_back(o); - return ret; -} diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h deleted file mode 100644 index 894c91821..000000000 --- a/liblll/CodeFragment.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 . -*/ -/** @file CodeFragment.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace boost { namespace spirit { class utree; } } -namespace sp = boost::spirit; - -namespace solidity::lll -{ - -struct CompilerState; - -class CodeFragment -{ -public: - using ReadCallback = std::function; - - CodeFragment() = default; - CodeFragment(sp::utree const& _t, CompilerState& _s, ReadCallback const& _readFile, bool _allowASM = false); - - static CodeFragment compile(std::string _src, CompilerState& _s, ReadCallback const& _readFile); - - /// Consolidates data and compiles code. - evmasm::Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; } - -private: - void finalise(CompilerState const& _cs); - - template static void error() { BOOST_THROW_EXCEPTION(T() ); } - template static void error(std::string const& reason) { - auto err = T(); - err << util::errinfo_comment(reason); - BOOST_THROW_EXCEPTION(err); - } - void constructOperation(sp::utree const& _t, CompilerState& _s); - - bool m_finalised = false; - evmasm::Assembly m_asm; - ReadCallback m_readFile; -}; - -static CodeFragment const NullCodeFragment; - -} diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp deleted file mode 100644 index a1ccf47d6..000000000 --- a/liblll/Compiler.cpp +++ /dev/null @@ -1,126 +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 . -*/ -/** @file Compiler.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::lll; - -bytes solidity::lll::compileLLL(string _src, langutil::EVMVersion _evmVersion, bool _opt, std::vector* _errors, ReadCallback const& _readFile) -{ - try - { - CompilerState cs; - cs.populateStandard(); - auto assembly = CodeFragment::compile(std::move(_src), cs, _readFile).assembly(cs); - if (_opt) - assembly = assembly.optimise(true, _evmVersion, true, 200); - bytes ret = assembly.assemble().bytecode; - for (auto i: cs.treesToKill) - killBigints(i); - return ret; - } - catch (Exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse error."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (std::exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse exception."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (...) - { - if (_errors) - _errors->emplace_back("Internal compiler exception."); - } - return bytes(); -} - -std::string solidity::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(std::move(_src), cs, _readFile).assembly(cs); - if (_opt) - assembly = assembly.optimise(true, _evmVersion, true, 200); - string ret = assembly.assemblyString(); - for (auto i: cs.treesToKill) - killBigints(i); - return ret; - } - catch (Exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse error."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (std::exception const& _e) - { - if (_errors) - { - _errors->emplace_back("Parse exception."); - _errors->emplace_back(boost::diagnostic_information(_e)); - } - } - catch (...) - { - if (_errors) - _errors->emplace_back("Internal compiler exception."); - } - return string(); -} - -string solidity::lll::parseLLL(string _src) -{ - sp::utree o; - - try - { - parseTreeLLL(std::move(_src), o); - } - catch (...) - { - killBigints(o); - return string(); - } - - ostringstream ret; - debugOutAST(ret, o); - killBigints(o); - return ret.str(); -} diff --git a/liblll/Compiler.h b/liblll/Compiler.h deleted file mode 100644 index 8d2989ce5..000000000 --- a/liblll/Compiler.h +++ /dev/null @@ -1,40 +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 . -*/ -/** @file Compiler.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include - -#include - -#include -#include - -namespace solidity::lll -{ - -using ReadCallback = std::function; - -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 deleted file mode 100644 index b35763ca5..000000000 --- a/liblll/CompilerState.cpp +++ /dev/null @@ -1,86 +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 . -*/ -/** @file CompilerState.cpp - * @author Gav Wood - * @date 2014 - */ - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::lll; - -CompilerState::CompilerState() -{ -} - -CodeFragment const& CompilerState::getDef(std::string const& _s) const -{ - if (defs.count(_s)) - return defs.at(_s); - else if (args.count(_s)) - return args.at(_s); - else if (outers.count(_s)) - return outers.at(_s); - else - return NullCodeFragment; -} - -void CompilerState::populateStandard() -{ - 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))))))))" - "(def 'allgas (- (gas) 21))" - "(def 'send (to value) (call allgas to value 0 0 0 0))" - "(def 'send (gaslimit to value) (call gaslimit to value 0 0 0 0))" - // NOTE: in this macro, memory location 0 is set in order to force msize to be at least 32 bytes. - "(def 'msg (gaslimit to value data datasize outsize) { [0]:0 [0]:(msize) (call gaslimit to value data datasize @0 outsize) @0 })" - "(def 'msg (gaslimit to value data datasize) { (call gaslimit to value data datasize 0 32) @0 })" - "(def 'msg (gaslimit to value data) { [0]:data (msg gaslimit to value 0 32) })" - "(def 'msg (to value data) { [0]:data (msg allgas to value 0 32) })" - "(def 'msg (to data) { [0]:data (msg allgas to 0 0 32) })" - // NOTE: in the create macros, memory location 0 is set in order to force msize to be at least 32 bytes. - "(def 'create (value code) { [0]:0 [0]:(msize) (create value @0 (lll code @0)) })" - "(def 'create (code) { [0]:0 [0]:(msize) (create 0 @0 (lll code @0)) })" - "(def 'sha3 (loc len) (keccak256 loc len))" - "(def 'sha3 (val) { [0]:val (sha3 0 32) })" - "(def 'sha3pair (a b) { [0]:a [32]:b (sha3 0 64) })" - "(def 'sha3trip (a b c) { [0]:a [32]:b [64]:c (sha3 0 96) })" - "(def 'return (val) { [0]:val (return 0 32) })" - "(def 'returnlll (code) (return 0 (lll code 0)) )" - "(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )" - "(def 'permcount 0)" - "(def 'perm (name) { (makeperm name permcount) (def 'permcount (+ permcount 1)) } )" - "(def 'ecrecover (hash v r s) { [0] hash [32] v [64] r [96] s (msg allgas 1 0 0 128) })" - "(def 'sha256 (data datasize) (msg allgas 2 0 data datasize))" - "(def 'ripemd160 (data datasize) (msg allgas 3 0 data datasize))" - "(def 'sha256 (val) { [0]:val (sha256 0 32) })" - "(def 'ripemd160 (val) { [0]:val (ripemd160 0 32) })" - "(def 'wei 1)" - "(def 'szabo 1000000000000)" - "(def 'finney 1000000000000000)" - "(def 'ether 1000000000000000000)" - // these could be replaced by native instructions once supported by EVM - "(def 'shl (val shift) (mul val (exp 2 shift)))" - "(def 'shr (val shift) (div val (exp 2 shift)))" - "}"; - CodeFragment::compile(s, *this, CodeFragment::ReadCallback()); -} diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h deleted file mode 100644 index 2313272b1..000000000 --- a/liblll/CompilerState.h +++ /dev/null @@ -1,54 +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 . -*/ -/** @file CompilerState.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include - -namespace solidity::lll -{ - -struct Macro -{ - std::vector args; - boost::spirit::utree code; - std::map env; -}; - -struct CompilerState -{ - CompilerState(); - - CodeFragment const& getDef(std::string const& _s) const; - void populateStandard(); - - unsigned stackSize = 128; - std::map> vars; ///< maps name to stack offset & size. - std::map defs; - std::map args; - std::map outers; - std::map, Macro> macros; - std::vector treesToKill; - bool usedAlloc = false; -}; - -} diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h deleted file mode 100644 index 57fbf6ad3..000000000 --- a/liblll/Exceptions.h +++ /dev/null @@ -1,42 +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 . -*/ -/** @file Exceptions.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include - -namespace solidity::lll -{ - -/// Compile a Low-level Lisp-like Language program into EVM-code. -class CompilerException: public util::Exception {}; -class InvalidOperation: public CompilerException {}; -class IntegerOutOfRange: public CompilerException {}; -class EmptyList: public CompilerException {}; -class DataNotExecutable: public CompilerException {}; -class IncorrectParameterCount: public CompilerException {}; -class InvalidName: public CompilerException {}; -class InvalidMacroArgs: public CompilerException {}; -class InvalidLiteral: public CompilerException {}; -class BareSymbol: public CompilerException {}; -class ParserException: public CompilerException {}; - -} diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp deleted file mode 100644 index 0f8ff1053..000000000 --- a/liblll/Parser.cpp +++ /dev/null @@ -1,156 +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 . -*/ -/** @file Parser.cpp - * @author Gav Wood - * @date 2014 - */ - -#include - -#if _MSC_VER -#pragma warning(disable:4348) -#endif - -#define BOOST_RESULT_OF_USE_DECLTYPE -#define BOOST_SPIRIT_USE_PHOENIX_V3 -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::lll; -namespace qi = boost::spirit::qi; -namespace px = boost::phoenix; -namespace sp = boost::spirit; - -void solidity::lll::killBigints(sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: for (auto const& i: _this) killBigints(i); break; - case sp::utree_type::any_type: delete _this.get(); break; - default:; - } -} - -void solidity::lll::debugOutAST(ostream& _out, sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: - switch (_this.tag()) - { - case 0: _out << "( "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << ")"; break; - case 1: _out << "@ "; debugOutAST(_out, _this.front()); break; - case 2: _out << "@@ "; debugOutAST(_out, _this.front()); break; - case 3: _out << "[ "; debugOutAST(_out, _this.front()); _out << " ] "; debugOutAST(_out, _this.back()); break; - case 4: _out << "[[ "; debugOutAST(_out, _this.front()); _out << " ]] "; debugOutAST(_out, _this.back()); break; - case 5: _out << "{ "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << "}"; break; - case 6: _out << "$ "; debugOutAST(_out, _this.front()); break; - default:; - } - - break; - case sp::utree_type::int_type: _out << _this.get(); break; - case sp::utree_type::string_type: _out << "\"" << _this.get, sp::utree_type::string_type>>() << "\""; break; - case sp::utree_type::symbol_type: _out << _this.get, sp::utree_type::symbol_type>>(); break; - case sp::utree_type::any_type: _out << *_this.get(); break; - default: _out << "nil"; - } -} - -namespace solidity { -namespace lll { -namespace parseTreeLLL_ { - -template -struct tagNode -{ - void operator()(sp::utree& n, qi::rule::context_type& c) const - { - (boost::fusion::at_c<0>(c.attributes) = n).tag(N); - } -}; - -}}} - -void solidity::lll::parseTreeLLL(string const& _s, sp::utree& o_out) -{ - using qi::standard::space; - using qi::standard::space_type; - using solidity::lll::parseTreeLLL_::tagNode; - using symbol_type = sp::basic_string; - using it = string::const_iterator; - - qi::rule element; - qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; - qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; - qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; - qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> +qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr[qi::_val = px::construct(px::new_(qi::_1))]; - qi::rule atom = integer[qi::_val = qi::_1] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule seq = '{' > *element > '}'; - qi::rule mload = '@' > element; - qi::rule sload = qi::lit("@@") > element; - qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; - qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; - qi::rule calldataload = qi::lit("$") > element; - qi::rule list = '(' > *element > ')'; - - qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()]; - element = atom | list | extra; - - string s; - s.reserve(_s.size()); - bool incomment = false; - bool instring = false; - bool insstring = false; - for (auto i: _s) - { - if (i == ';' && !instring && !insstring) - incomment = true; - else if (i == '\n') - incomment = instring = insstring = false; - else if (i == '"' && !insstring) - instring = !instring; - else if (i == '\'') - insstring = true; - else if (i == ' ') - insstring = false; - if (!incomment) - s.push_back(i); - } - auto ret = s.cbegin(); - try - { - qi::phrase_parse(ret, s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); - } - catch (qi::expectation_failure const& e) - { - std::string fragment(e.first, e.last); - std::string loc = to_string(std::distance(s.cbegin(), e.first) - 1); - std::string reason("Lexer failure at " + loc + ": '" + fragment + "'"); - BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment(reason)); - } - for (auto i = ret; i != s.cend(); ++i) - if (!isspace(*i)) - { - BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment("Non-whitespace left in parser")); - } -} diff --git a/liblll/Parser.h b/liblll/Parser.h deleted file mode 100644 index 424356e0c..000000000 --- a/liblll/Parser.h +++ /dev/null @@ -1,39 +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 . -*/ -/** @file Parser.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace boost { namespace spirit { class utree; } } -namespace sp = boost::spirit; - -namespace solidity::lll -{ - -void killBigints(sp::utree const& _this); -void parseTreeLLL(std::string const& _s, sp::utree& o_out); -void debugOutAST(std::ostream& _out, sp::utree const& _this); - -} diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 0a6ba4e58..bb54c836f 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -39,10 +39,14 @@ set(sources ast/ASTAnnotations.h ast/ASTEnums.h ast/ASTForward.h + ast/AsmJsonImporter.cpp + ast/AsmJsonImporter.h ast/ASTJsonConverter.cpp ast/ASTJsonConverter.h ast/ASTUtils.cpp ast/ASTUtils.h + ast/ASTJsonImporter.cpp + ast/ASTJsonImporter.h ast/ASTVisitor.h ast/ExperimentalFeatures.h ast/Types.cpp @@ -75,8 +79,9 @@ set(sources codegen/ir/IRGeneratorForStatements.h codegen/ir/IRGenerationContext.cpp codegen/ir/IRGenerationContext.h - codegen/ir/IRLValue.cpp codegen/ir/IRLValue.h + codegen/ir/IRVariable.cpp + codegen/ir/IRVariable.h formal/BMC.cpp formal/BMC.h formal/CHC.cpp diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index a774dee61..c8a824c54 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -163,7 +163,7 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* std::set unreachable; util::BreadthFirstSearch{{_exit, _revert}}.run( [&](CFGNode const* _node, auto&& _addChild) { - if (!reachable.count(_node) && !_node->location.isEmpty()) + if (!reachable.count(_node) && _node->location.isValid()) unreachable.insert(_node->location); for (CFGNode const* entry: _node->entries) _addChild(entry); diff --git a/libsolidity/analysis/ControlFlowGraph.cpp b/libsolidity/analysis/ControlFlowGraph.cpp index 244321abc..4cb096ae0 100644 --- a/libsolidity/analysis/ControlFlowGraph.cpp +++ b/libsolidity/analysis/ControlFlowGraph.cpp @@ -47,6 +47,6 @@ FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const CFGNode* CFG::NodeContainer::newNode() { - m_nodes.emplace_back(new CFGNode()); + m_nodes.emplace_back(std::make_unique()); return m_nodes.back().get(); } diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 1b3bd598b..e802c4204 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -73,7 +73,8 @@ bool DocStringAnalyser::visit(EventDefinition const& _event) void DocStringAnalyser::checkParameters( CallableDeclaration const& _callable, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ) { set validParams; @@ -86,6 +87,7 @@ void DocStringAnalyser::checkParameters( for (auto i = paramRange.first; i != paramRange.second; ++i) if (!validParams.count(i->second.paramName)) appendError( + _node.documentation()->location(), "Documented parameter \"" + i->second.paramName + "\" not found in the parameter list of the function." @@ -95,37 +97,37 @@ void DocStringAnalyser::checkParameters( void DocStringAnalyser::handleConstructor( CallableDeclaration const& _callable, - Documented const& _node, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ) { static set const validTags = set{"author", "dev", "notice", "param"}; parseDocStrings(_node, _annotation, validTags, "constructor"); - checkParameters(_callable, _annotation); + checkParameters(_callable, _node, _annotation); } void DocStringAnalyser::handleCallable( CallableDeclaration const& _callable, - Documented const& _node, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ) { static set const validTags = set{"author", "dev", "notice", "return", "param"}; parseDocStrings(_node, _annotation, validTags, "functions"); - checkParameters(_callable, _annotation); + checkParameters(_callable, _node, _annotation); } void DocStringAnalyser::parseDocStrings( - Documented const& _node, - DocumentedAnnotation& _annotation, + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation, set const& _validTags, string const& _nodeName ) { DocStringParser parser; - if (_node.documentation() && !_node.documentation()->empty()) + if (_node.documentation() && !_node.documentation()->text()->empty()) { - if (!parser.parse(*_node.documentation(), m_errorReporter)) + if (!parser.parse(*_node.documentation()->text(), m_errorReporter)) m_errorOccured = true; _annotation.docTags = parser.tags(); } @@ -134,7 +136,10 @@ void DocStringAnalyser::parseDocStrings( for (auto const& docTag: _annotation.docTags) { if (!_validTags.count(docTag.first)) - appendError("Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."); + appendError( + _node.documentation()->location(), + "Documentation tag @" + docTag.first + " not valid for " + _nodeName + "." + ); else if (docTag.first == "return") { @@ -145,14 +150,18 @@ void DocStringAnalyser::parseDocStrings( string firstWord = content.substr(0, content.find_first_of(" \t")); if (returnTagsVisited > function->returnParameters().size()) - appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + + appendError( + _node.documentation()->location(), + "Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + " exceedes the number of return parameters." ); else { auto parameter = function->returnParameters().at(returnTagsVisited - 1); if (!parameter->name().empty() && parameter->name() != firstWord) - appendError("Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + + appendError( + _node.documentation()->location(), + "Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" + " does not contain the name of its return parameter." ); } @@ -161,8 +170,8 @@ void DocStringAnalyser::parseDocStrings( } } -void DocStringAnalyser::appendError(string const& _description) +void DocStringAnalyser::appendError(SourceLocation const& _location, string const& _description) { m_errorOccured = true; - m_errorReporter.docstringParsingError(_description); + m_errorReporter.docstringParsingError(_location, _description); } diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index 653413346..b9f816854 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -51,29 +51,30 @@ private: void checkParameters( CallableDeclaration const& _callable, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ); void handleConstructor( CallableDeclaration const& _callable, - Documented const& _node, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ); void handleCallable( CallableDeclaration const& _callable, - Documented const& _node, - DocumentedAnnotation& _annotation + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation ); void parseDocStrings( - Documented const& _node, - DocumentedAnnotation& _annotation, + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation, std::set const& _validTags, std::string const& _nodeName ); - void appendError(std::string const& _description); + void appendError(langutil::SourceLocation const& _location, std::string const& _description); bool m_errorOccured = false; langutil::ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 2a4ce374b..a0764edba 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -33,10 +33,44 @@ using namespace std; namespace solidity::frontend { +/// Magic variables get negative ids for easy differentiation +int magicVariableToID(std::string const& _name) +{ + if (_name == "abi") return -1; + else if (_name == "addmod") return -2; + else if (_name == "assert") return -3; + else if (_name == "block") return -4; + else if (_name == "blockhash") return -5; + else if (_name == "ecrecover") return -6; + else if (_name == "gasleft") return -7; + else if (_name == "keccak256") return -8; + else if (_name == "log0") return -10; + else if (_name == "log1") return -11; + else if (_name == "log2") return -12; + else if (_name == "log3") return -13; + else if (_name == "log4") return -14; + else if (_name == "msg") return -15; + else if (_name == "mulmod") return -16; + else if (_name == "now") return -17; + else if (_name == "require") return -18; + else if (_name == "revert") return -19; + else if (_name == "ripemd160") return -20; + else if (_name == "selfdestruct") return -21; + else if (_name == "sha256") return -22; + else if (_name == "sha3") return -23; + else if (_name == "suicide") return -24; + else if (_name == "super") return -25; + else if (_name == "tx") return -26; + else if (_name == "type") return -27; + else if (_name == "this") return -28; + else + solAssert(false, "Unknown magic variable: \"" + _name + "\"."); +} + inline vector> constructMagicVariables() { static auto const magicVarDecl = [](string const& _name, Type const* _type) { - return make_shared(_name, _type); + return make_shared(magicVariableToID(_name), _name, _type); }; return { @@ -97,7 +131,7 @@ vector GlobalContext::declarations() const MagicVariableDeclaration const* GlobalContext::currentThis() const { if (!m_thisPointer[m_currentContract]) - m_thisPointer[m_currentContract] = make_shared("this", TypeProvider::contract(*m_currentContract)); + m_thisPointer[m_currentContract] = make_shared(magicVariableToID("this"), "this", TypeProvider::contract(*m_currentContract)); return m_thisPointer[m_currentContract].get(); } @@ -105,7 +139,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const MagicVariableDeclaration const* GlobalContext::currentSuper() const { if (!m_superPointer[m_currentContract]) - m_superPointer[m_currentContract] = make_shared("super", TypeProvider::contract(*m_currentContract, true)); + m_superPointer[m_currentContract] = make_shared(magicVariableToID("super"), "super", TypeProvider::contract(*m_currentContract, true)); return m_superPointer[m_currentContract].get(); } diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp index c2e9124a7..3643c6401 100644 --- a/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -405,13 +405,13 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) _contract.annotation().contractDependencies.insert(result.begin() + 1, result.end()); } -template -vector<_T const*> NameAndTypeResolver::cThreeMerge(list>& _toMerge) +template +vector NameAndTypeResolver::cThreeMerge(list>& _toMerge) { // returns true iff _candidate appears only as last element of the lists - auto appearsOnlyAtHead = [&](_T const* _candidate) -> bool + auto appearsOnlyAtHead = [&](T const* _candidate) -> bool { - for (list<_T const*> const& bases: _toMerge) + for (list const& bases: _toMerge) { solAssert(!bases.empty(), ""); if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) @@ -420,9 +420,9 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list>& _toMer return true; }; // returns the next candidate to append to the linearized list or nullptr on failure - auto nextCandidate = [&]() -> _T const* + auto nextCandidate = [&]() -> T const* { - for (list<_T const*> const& bases: _toMerge) + for (list const& bases: _toMerge) { solAssert(!bases.empty(), ""); if (appearsOnlyAtHead(bases.front())) @@ -431,7 +431,7 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list>& _toMer return nullptr; }; // removes the given contract from all lists - auto removeCandidate = [&](_T const* _candidate) + auto removeCandidate = [&](T const* _candidate) { for (auto it = _toMerge.begin(); it != _toMerge.end();) { @@ -443,13 +443,13 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list>& _toMer } }; - _toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); }); - vector<_T const*> result; + _toMerge.remove_if([](list const& _bases) { return _bases.empty(); }); + vector result; while (!_toMerge.empty()) { - _T const* candidate = nextCandidate(); + T const* candidate = nextCandidate(); if (!candidate) - return vector<_T const*>(); + return vector(); result.push_back(candidate); removeCandidate(candidate); } @@ -625,7 +625,6 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) { registerDeclaration(_function, true); m_currentFunction = &_function; - _function.annotation().contract = m_currentContract; return true; } @@ -760,6 +759,7 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter); _declaration.annotation().scope = m_currentScope; + _declaration.annotation().contract = m_currentContract; if (_opensScope) enterNewSubScope(_declaration); } diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h index b8ae2fefb..dc4d32bb9 100644 --- a/libsolidity/analysis/NameAndTypeResolver.h +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -122,8 +122,8 @@ private: void linearizeBaseContracts(ContractDefinition& _contract); /// Computes the C3-merge of the given list of lists of bases. /// @returns the linearized vector or an empty vector if linearization is not possible. - template - static std::vector<_T const*> cThreeMerge(std::list>& _toMerge); + template + static std::vector cThreeMerge(std::list>& _toMerge); /// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration, /// where nullptr denotes the global scope. Note that structs are not scope since they do diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp index fc2c8df62..8367f9f58 100644 --- a/libsolidity/analysis/OverrideChecker.cpp +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -144,8 +144,8 @@ vector resolveDirectBaseContracts(ContractDefinition Declaration const* baseDecl = specifier->name().annotation().referencedDeclaration; auto contract = dynamic_cast(baseDecl); - solAssert(contract, "contract is null"); - resolvedContracts.emplace_back(contract); + if (contract) + resolvedContracts.emplace_back(contract); } return resolvedContracts; diff --git a/libsolidity/analysis/OverrideChecker.h b/libsolidity/analysis/OverrideChecker.h index 48fa54314..d4b96663c 100644 --- a/libsolidity/analysis/OverrideChecker.h +++ b/libsolidity/analysis/OverrideChecker.h @@ -129,6 +129,7 @@ private: class OverrideChecker { public: + using OverrideProxyBySignatureMultiSet = std::multiset; /// @param _errorReporter provides the error logging functionality. explicit OverrideChecker(langutil::ErrorReporter& _errorReporter): @@ -137,12 +138,17 @@ public: void check(ContractDefinition const& _contract); -private: struct CompareByID { bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const; }; + /// Returns all functions of bases (including public state variables) that have not yet been overwritten. + /// May contain the same function multiple times when used with shared bases. + OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const; + OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const; + +private: void checkIllegalOverrides(ContractDefinition const& _contract); /// Performs various checks related to @a _overriding overriding @a _super like /// different return type, invalid visibility change, etc. @@ -174,15 +180,8 @@ private: /// Resolves an override list of UserDefinedTypeNames to a list of contracts. std::set resolveOverrideList(OverrideSpecifier const& _overrides) const; - using OverrideProxyBySignatureMultiSet = std::multiset; - void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited); - /// Returns all functions of bases (including public state variables) that have not yet been overwritten. - /// May contain the same function multiple times when used with shared bases. - OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const; - OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const; - langutil::ErrorReporter& m_errorReporter; /// Cache for inheritedFunctions(). diff --git a/libsolidity/analysis/PostTypeChecker.cpp b/libsolidity/analysis/PostTypeChecker.cpp index 6ed306269..62317587a 100644 --- a/libsolidity/analysis/PostTypeChecker.cpp +++ b/libsolidity/analysis/PostTypeChecker.cpp @@ -133,9 +133,9 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker bool visit(VariableDeclaration const& _variable) override { - solAssert(!m_currentConstVariable, ""); if (_variable.isConstant()) { + solAssert(!m_currentConstVariable, ""); m_currentConstVariable = &_variable; m_constVariables.push_back(&_variable); } diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 889ec4d2d..4fe2d01c9 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -224,15 +224,36 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) _typeName.annotation().type = TypeProvider::function(_typeName); } -void ReferencesResolver::endVisit(Mapping const& _typeName) +void ReferencesResolver::endVisit(Mapping const& _mapping) { - TypePointer keyType = _typeName.keyType().annotation().type; - TypePointer valueType = _typeName.valueType().annotation().type; + if (auto const* typeName = dynamic_cast(&_mapping.keyType())) + { + if (auto const* contractType = dynamic_cast(typeName->annotation().type)) + { + if (contractType->contractDefinition().isLibrary()) + m_errorReporter.fatalTypeError( + typeName->location(), + "Library types cannot be used as mapping keys." + ); + } + else if (typeName->annotation().type->category() != Type::Category::Enum) + m_errorReporter.fatalTypeError( + typeName->location(), + "Only elementary types, contract types or enums are allowed as mapping keys." + ); + } + else + solAssert(dynamic_cast(&_mapping.keyType()), ""); + + TypePointer keyType = _mapping.keyType().annotation().type; + TypePointer valueType = _mapping.valueType().annotation().type; + // Convert key type to memory. keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType); + // Convert value type to storage reference. valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType); - _typeName.annotation().type = TypeProvider::mapping(keyType, valueType); + _mapping.annotation().type = TypeProvider::mapping(keyType, valueType); } void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) @@ -270,87 +291,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) { m_resolver.warnVariablesNamedLikeInstructions(); - // Errors created in this stage are completely ignored because we do not yet know - // the type and size of external identifiers, which would result in false errors. - // The only purpose of this step is to fill the inline assembly annotation with - // external references. - ErrorList errors; - ErrorReporter errorsIgnored(errors); - yul::ExternalIdentifierAccess::Resolver resolver = - [&](yul::Identifier const& _identifier, yul::IdentifierContext _context, bool _crossesFunctionBoundary) { - bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot"); - bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset"); - if (_context == yul::IdentifierContext::VariableDeclaration) - { - string namePrefix = _identifier.name.str().substr(0, _identifier.name.str().find('.')); - if (isSlot || isOffset) - declarationError(_identifier.location, "In variable declarations _slot and _offset can not be used as a suffix."); - else if ( - auto declarations = m_resolver.nameFromCurrentScope(namePrefix); - !declarations.empty() - ) - { - SecondarySourceLocation ssl; - for (auto const* decl: declarations) - ssl.append("The shadowed declaration is here:", decl->location()); - if (!ssl.infos.empty()) - declarationError( - _identifier.location, - ssl, - namePrefix.size() < _identifier.name.str().size() ? - "The prefix of this declaration conflicts with a declaration outside the inline assembly block." : - "This declaration shadows a declaration outside the inline assembly block." - ); - } - return size_t(-1); - } - auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); - if (isSlot || isOffset) - { - // special mode to access storage variables - if (!declarations.empty()) - // the special identifier exists itself, we should not allow that. - return size_t(-1); - string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - ( - isSlot ? - string("_slot").size() : - string("_offset").size() - )); - if (realName.empty()) - { - declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix."); - return size_t(-1); - } - declarations = m_resolver.nameFromCurrentScope(realName); - } - 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) - { - declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function."); - return size_t(-1); - } - _inlineAssembly.annotation().externalReferences[&_identifier].isSlot = isSlot; - _inlineAssembly.annotation().externalReferences[&_identifier].isOffset = isOffset; - _inlineAssembly.annotation().externalReferences[&_identifier].declaration = declarations.front(); - return size_t(1); - }; + m_yulAnnotation = &_inlineAssembly.annotation(); + (*this)(_inlineAssembly.operations()); + m_yulAnnotation = nullptr; - // Will be re-generated later with correct information - // We use the latest EVM version because we will re-run it anyway. - yul::AsmAnalysisInfo analysisInfo; - yul::AsmAnalyzer( - analysisInfo, - errorsIgnored, - yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), - resolver - ).analyze(_inlineAssembly.operations()); return false; } @@ -468,6 +412,90 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) _variable.annotation().type = type; } +void ReferencesResolver::operator()(yul::FunctionDefinition const& _function) +{ + bool wasInsideFunction = m_yulInsideFunction; + m_yulInsideFunction = true; + this->operator()(_function.body); + m_yulInsideFunction = wasInsideFunction; +} + +void ReferencesResolver::operator()(yul::Identifier const& _identifier) +{ + bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot"); + bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset"); + + auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); + if (isSlot || isOffset) + { + // special mode to access storage variables + if (!declarations.empty()) + // the special identifier exists itself, we should not allow that. + return; + string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - ( + isSlot ? + string("_slot").size() : + string("_offset").size() + )); + if (realName.empty()) + { + declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix."); + return; + } + declarations = m_resolver.nameFromCurrentScope(realName); + } + if (declarations.size() > 1) + { + declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported."); + return; + } + else if (declarations.size() == 0) + return; + if (auto var = dynamic_cast(declarations.front())) + if (var->isLocalVariable() && m_yulInsideFunction) + { + declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function."); + return; + } + + m_yulAnnotation->externalReferences[&_identifier].isSlot = isSlot; + m_yulAnnotation->externalReferences[&_identifier].isOffset = isOffset; + m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front(); +} + +void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl) +{ + for (auto const& identifier: _varDecl.variables) + { + bool isSlot = boost::algorithm::ends_with(identifier.name.str(), "_slot"); + bool isOffset = boost::algorithm::ends_with(identifier.name.str(), "_offset"); + + string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.')); + if (isSlot || isOffset) + declarationError(identifier.location, "In variable declarations _slot and _offset can not be used as a suffix."); + else if ( + auto declarations = m_resolver.nameFromCurrentScope(namePrefix); + !declarations.empty() + ) + { + SecondarySourceLocation ssl; + for (auto const* decl: declarations) + ssl.append("The shadowed declaration is here:", decl->location()); + if (!ssl.infos.empty()) + declarationError( + identifier.location, + ssl, + namePrefix.size() < identifier.name.str().size() ? + "The prefix of this declaration conflicts with a declaration outside the inline assembly block." : + "This declaration shadows a declaration outside the inline assembly block." + ); + } + } + + if (_varDecl.value) + visit(*_varDecl.value); +} + void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description) { m_errorOccurred = true; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index a368f7d39..c560be31e 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,7 @@ class NameAndTypeResolver; * Resolves references to declarations (of variables and types) and also establishes the link * between a return statement and the return parameter list. */ -class ReferencesResolver: private ASTConstVisitor +class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker { public: ReferencesResolver( @@ -64,6 +65,9 @@ public: bool resolve(ASTNode const& _root); private: + using yul::ASTWalker::visit; + using yul::ASTWalker::operator(); + bool visit(Block const& _block) override; void endVisit(Block const& _block) override; bool visit(ForStatement const& _for) override; @@ -77,12 +81,16 @@ private: 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(Mapping const& _mapping) 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; + void operator()(yul::FunctionDefinition const& _function) override; + void operator()(yul::Identifier const& _identifier) override; + void operator()(yul::VariableDeclaration const& _varDecl) override; + /// Adds a new error to the list of errors. void typeError(langutil::SourceLocation const& _location, std::string const& _description); @@ -105,6 +113,9 @@ private: std::vector m_returnParameters; bool const m_resolveInsideCode; bool m_errorOccurred = false; + + InlineAssemblyAnnotation* m_yulAnnotation = nullptr; + bool m_yulInsideFunction = false; }; } diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 666c15dc5..74fa8d458 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -68,7 +68,8 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit) to_string(recommendedVersion.patch()) + string(";\""); - m_errorReporter.warning(_sourceUnit.location(), errorString); + // when reporting the warning, print the source name only + m_errorReporter.warning({-1, -1, _sourceUnit.location().source}, errorString); } m_sourceUnit = nullptr; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5d8c7b85f..2f9526f87 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -237,8 +237,8 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto base = dynamic_cast(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); - if (m_scope->isInterface()) - m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit."); + if (m_scope->isInterface() && !base->isInterface()) + m_errorReporter.typeError(_inheritance.location(), "Interfaces can only inherit from other interfaces."); if (base->isLibrary()) m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from."); @@ -677,10 +677,20 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); return size_t(-1); } - else if (_context != yul::IdentifierContext::RValue) + else if (_context == yul::IdentifierContext::LValue) { - m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to."); - return size_t(-1); + if (var->isStateVariable()) + { + m_errorReporter.typeError(_identifier.location, "State variables cannot be assigned to - you have to use \"sstore()\"."); + return size_t(-1); + } + else if (ref->second.isOffset) + { + m_errorReporter.typeError(_identifier.location, "Only _slot can be assigned to."); + return size_t(-1); + } + else + solAssert(ref->second.isSlot, ""); } } else if (!var->isConstant() && var->isStateVariable()) @@ -1695,11 +1705,20 @@ void TypeChecker::typeCheckFunctionCall( { m_errorReporter.typeError( _functionCall.location(), - "Cannot call function via contract name." + "Cannot call function via contract type name." ); return; } - + if (_functionType->kind() == FunctionType::Kind::Internal && _functionType->hasDeclaration()) + if (auto const* functionDefinition = dynamic_cast(&_functionType->declaration())) + // functionDefinition->annotation().contract != m_scope ensures that this is a qualified access, + // e.g. ``A.f();`` instead of a simple function call like ``f();`` (the latter is valid for unimplemented + // functions). + if (functionDefinition->annotation().contract != m_scope && !functionDefinition->isImplemented()) + m_errorReporter.typeError( + _functionCall.location(), + "Cannot call unimplemented base function." + ); // Check for unsupported use of bare static call if ( @@ -2145,7 +2164,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) } default: - m_errorReporter.typeError(_functionCall.location(), "Type is not callable"); + m_errorReporter.fatalTypeError(_functionCall.location(), "Type is not callable"); funcCallAnno.kind = FunctionCallKind::Unset; funcCallAnno.isPure = argumentsArePure; break; @@ -2215,6 +2234,124 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) return false; } +bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) +{ + solAssert(_functionCallOptions.options().size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!"); + + _functionCallOptions.expression().accept(*this); + + auto expressionFunctionType = dynamic_cast(type(_functionCallOptions.expression())); + if (!expressionFunctionType) + { + m_errorReporter.fatalTypeError(_functionCallOptions.location(), "Expected callable expression before call options."); + return false; + } + + bool setSalt = false; + bool setValue = false; + bool setGas = false; + + FunctionType::Kind kind = expressionFunctionType->kind(); + if ( + kind != FunctionType::Kind::Creation && + kind != FunctionType::Kind::External && + kind != FunctionType::Kind::BareCall && + kind != FunctionType::Kind::BareCallCode && + kind != FunctionType::Kind::BareDelegateCall && + kind != FunctionType::Kind::BareStaticCall + ) + { + m_errorReporter.fatalTypeError( + _functionCallOptions.location(), + "Function call options can only be set on external function calls or contract creations." + ); + return false; + } + + auto setCheckOption = [&](bool& _option, string const&& _name, bool _alreadySet = false) + { + if (_option || _alreadySet) + m_errorReporter.typeError( + _functionCallOptions.location(), + _alreadySet ? + "Option \"" + std::move(_name) + "\" has already been set." : + "Duplicate option \"" + std::move(_name) + "\"." + ); + + _option = true; + }; + + for (size_t i = 0; i < _functionCallOptions.names().size(); ++i) + { + string const& name = *(_functionCallOptions.names()[i]); + if (name == "salt") + { + if (kind == FunctionType::Kind::Creation) + { + setCheckOption(setSalt, "salt", expressionFunctionType->saltSet()); + expectType(*_functionCallOptions.options()[i], *TypeProvider::fixedBytes(32)); + } + else + m_errorReporter.typeError( + _functionCallOptions.location(), + "Function call option \"salt\" can only be used with \"new\"." + ); + } + else if (name == "value") + { + if (kind == FunctionType::Kind::BareDelegateCall) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" for delegatecall." + ); + else if (kind == FunctionType::Kind::BareStaticCall) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" for staticcall." + ); + else if (!expressionFunctionType->isPayable()) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Cannot set option \"value\" on a non-payable function type." + ); + else + { + expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256()); + + setCheckOption(setValue, "value", expressionFunctionType->valueSet()); + } + } + else if (name == "gas") + { + if (kind == FunctionType::Kind::Creation) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Function call option \"gas\" cannot be used with \"new\"." + ); + else + { + expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256()); + + setCheckOption(setGas, "gas", expressionFunctionType->gasSet()); + } + } + else + m_errorReporter.typeError( + _functionCallOptions.location(), + "Unknown call option \"" + name + "\". Valid options are \"salt\", \"value\" and \"gas\"." + ); + } + + if (setSalt && !m_evmVersion.hasCreate2()) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Unsupported call option \"salt\" (requires Constantinople-compatible VMs)." + ); + + _functionCallOptions.annotation().type = expressionFunctionType->copyAndSetCallOptions(setGas, setValue, setSalt); + return false; +} + void TypeChecker::endVisit(NewExpression const& _newExpression) { TypePointer type = _newExpression.typeName().annotation().type; @@ -2400,7 +2537,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) else if (TypeType const* typeType = dynamic_cast(exprType)) { if (ContractType const* contractType = dynamic_cast(typeType->actualType())) + { annotation.isLValue = annotation.referencedDeclaration->isLValue(); + if ( + auto const* functionType = dynamic_cast(annotation.type); + functionType && + functionType->kind() == FunctionType::Kind::Declaration + ) + annotation.isPure = _memberAccess.expression().annotation().isPure; + } } // TODO some members might be pure, but for example `address(0x123).balance` is not pure @@ -2408,6 +2553,20 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (auto tt = dynamic_cast(exprType)) if (tt->actualType()->category() == Type::Category::Enum) annotation.isPure = true; + if ( + auto const* functionType = dynamic_cast(exprType); + functionType && + functionType->hasDeclaration() && + dynamic_cast(&functionType->declaration()) && + memberName == "selector" + ) + if (auto const* parentAccess = dynamic_cast(&_memberAccess.expression())) + { + annotation.isPure = parentAccess->expression().annotation().isPure; + if (auto const* exprInt = dynamic_cast(&parentAccess->expression())) + if (exprInt->name() == "this" || exprInt->name() == "super") + annotation.isPure = true; + } if (auto magicType = dynamic_cast(exprType)) { if (magicType->kind() == MagicType::Kind::ABI) @@ -2635,7 +2794,7 @@ bool TypeChecker::visit(Identifier const& _identifier) SecondarySourceLocation ssl; for (Declaration const* declaration: annotation.overloadedDeclarations) - if (declaration->location().isEmpty()) + if (!declaration->location().isValid()) { // Try to re-construct function definition string description; @@ -2660,8 +2819,7 @@ bool TypeChecker::visit(Identifier const& _identifier) ); annotation.isLValue = annotation.referencedDeclaration->isLValue(); annotation.type = annotation.referencedDeclaration->type(); - if (!annotation.type) - m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined."); + solAssert(annotation.type, "Declaration referenced before type could be determined."); if (auto variableDeclaration = dynamic_cast(annotation.referencedDeclaration)) annotation.isPure = annotation.isConstant = variableDeclaration->isConstant(); else if (dynamic_cast(annotation.referencedDeclaration)) diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index c7443b29c..d428a6ac9 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -135,6 +135,7 @@ private: void endVisit(BinaryOperation const& _operation) override; bool visit(UnaryOperation const& _operation) override; bool visit(FunctionCall const& _functionCall) override; + bool visit(FunctionCallOptions const& _functionCallOptions) override; void endVisit(NewExpression const& _newExpression) override; bool visit(MemberAccess const& _memberAccess) override; bool visit(IndexAccess const& _indexAccess) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 9f8e1471d..90084f9c4 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -35,31 +35,12 @@ using namespace std; using namespace solidity; using namespace solidity::frontend; -class IDDispenser -{ -public: - static size_t next() { return ++instance(); } - static void reset() { instance() = 0; } -private: - static size_t& instance() - { - static IDDispenser dispenser; - return dispenser.id; - } - size_t id = 0; -}; - -ASTNode::ASTNode(SourceLocation const& _location): - m_id(IDDispenser::next()), +ASTNode::ASTNode(int64_t _id, SourceLocation const& _location): + m_id(_id), m_location(_location) { } -void ASTNode::resetID() -{ - IDDispenser::reset(); -} - ASTAnnotation& ASTNode::annotation() const { if (!m_annotation) @@ -110,6 +91,11 @@ vector ContractDefinition::stateVariablesIncludingIn return stateVars; } +bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const +{ + return util::contains(annotation().linearizedBaseContracts, &_base); +} + map, FunctionTypePointer> ContractDefinition::interfaceFunctions() const { auto exportedFunctionList = interfaceFunctionList(); @@ -221,36 +207,6 @@ vector, FunctionTypePointer>> const& ContractDefinition: return *m_interfaceFunctionList; } -vector const& ContractDefinition::inheritableMembers() const -{ - if (!m_inheritableMembers) - { - m_inheritableMembers = make_unique>(); - auto addInheritableMember = [&](Declaration const* _decl) - { - solAssert(_decl, "addInheritableMember got a nullpointer."); - if (_decl->isVisibleInDerivedContracts()) - m_inheritableMembers->push_back(_decl); - }; - - for (FunctionDefinition const* f: definedFunctions()) - addInheritableMember(f); - - for (VariableDeclaration const* v: stateVariables()) - addInheritableMember(v); - - for (StructDefinition const* s: definedStructs()) - addInheritableMember(s); - - for (EnumDefinition const* e: definedEnums()) - addInheritableMember(e); - - for (EventDefinition const* e: events()) - addInheritableMember(e); - } - return *m_inheritableMembers; -} - TypePointer ContractDefinition::type() const { return TypeProvider::typeType(TypeProvider::contract(*this)); @@ -341,6 +297,13 @@ TypePointer FunctionDefinition::type() const return TypeProvider::function(*this, FunctionType::Kind::Internal); } +TypePointer FunctionDefinition::typeViaContractName() const +{ + if (annotation().contract->isLibrary()) + return FunctionType(*this).asCallableFunction(true); + return TypeProvider::function(*this, FunctionType::Kind::Declaration); +} + string FunctionDefinition::externalSignature() const { return TypeProvider::function(*this)->externalSignature(); diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index fc0b3a5be..336c221c6 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -64,13 +64,11 @@ class ASTNode: private boost::noncopyable public: using SourceLocation = langutil::SourceLocation; - explicit ASTNode(SourceLocation const& _location); + explicit ASTNode(int64_t _id, SourceLocation const& _location); virtual ~ASTNode() {} /// @returns an identifier of this AST node that is unique for a single compilation run. - size_t id() const { return m_id; } - /// Resets the global ID counter. This invalidates all previous IDs. - static void resetID(); + int64_t id() const { return m_id; } virtual void accept(ASTVisitor& _visitor) = 0; virtual void accept(ASTConstVisitor& _visitor) const = 0; @@ -90,8 +88,8 @@ public: } /// @returns a copy of the vector containing only the nodes which derive from T. - template - static std::vector<_T const*> filteredNodes(std::vector> const& _nodes); + template + static std::vector filteredNodes(std::vector> const& _nodes); /// Returns the source code location of this node. SourceLocation const& location() const { return m_location; } @@ -123,12 +121,12 @@ private: SourceLocation m_location; }; -template -std::vector<_T const*> ASTNode::filteredNodes(std::vector> const& _nodes) +template +std::vector ASTNode::filteredNodes(std::vector> const& _nodes) { - std::vector<_T const*> ret; + std::vector ret; for (auto const& n: _nodes) - if (auto const* nt = dynamic_cast<_T const*>(n.get())) + if (auto const* nt = dynamic_cast(n.get())) ret.push_back(nt); return ret; } @@ -139,8 +137,8 @@ std::vector<_T const*> ASTNode::filteredNodes(std::vector> c class SourceUnit: public ASTNode { public: - SourceUnit(SourceLocation const& _location, std::vector> const& _nodes): - ASTNode(_location), m_nodes(_nodes) {} + SourceUnit(int64_t _id, SourceLocation const& _location, std::vector> const& _nodes): + ASTNode(_id, _location), m_nodes(_nodes) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -206,11 +204,12 @@ public: } Declaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility = Visibility::Default ): - ASTNode(_location), m_name(_name), m_visibility(_visibility) {} + ASTNode(_id, _location), m_name(_name), m_visibility(_visibility) {} /// @returns the declared name. ASTString const& name() const { return *m_name; } @@ -218,19 +217,22 @@ public: Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; } bool isPublic() const { return visibility() >= Visibility::Public; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } - bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } + virtual bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; } + virtual bool isVisibleViaContractTypeAccess() const { return false; } virtual bool isLValue() const { return false; } virtual bool isPartOfExternalInterface() const { return false; } /// @returns the type of expressions referencing this declaration. - /// The current contract has to be given since this context can change the type, especially of - /// contract types. /// This can only be called once types of variable declarations have already been resolved. virtual TypePointer type() const = 0; + /// @returns the type for members of the containing contract type that refer to this declaration. + /// This can only be called once types of variable declarations have already been resolved. + virtual TypePointer typeViaContractName() const { return type(); } + /// @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 { return {}; } @@ -253,10 +255,11 @@ class PragmaDirective: public ASTNode { public: PragmaDirective( + int64_t _id, SourceLocation const& _location, std::vector const& _tokens, std::vector const& _literals - ): ASTNode(_location), m_tokens(_tokens), m_literals(_literals) + ): ASTNode(_id, _location), m_tokens(_tokens), m_literals(_literals) {} void accept(ASTVisitor& _visitor) override; @@ -295,12 +298,13 @@ public: using SymbolAliasList = std::vector; ImportDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _path, ASTPointer const& _unitAlias, SymbolAliasList _symbolAliases ): - Declaration(_location, _unitAlias), + Declaration(_id, _location, _unitAlias), m_path(_path), m_symbolAliases(move(_symbolAliases)) { } @@ -341,6 +345,30 @@ private: std::vector m_localVariables; }; +/** + * The doxygen-style, structured documentation class that represents an AST node. + */ +class StructuredDocumentation: public ASTNode +{ +public: + StructuredDocumentation( + int64_t _id, + SourceLocation const& _location, + ASTPointer const& _text + ): ASTNode(_id, _location), m_text(_text) + {} + + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + /// @return A shared pointer of an ASTString. + /// Contains doxygen-style, structured documentation that is parsed later on. + ASTPointer const& text() const { return m_text; } + +private: + ASTPointer m_text; +}; + /** * Abstract class that is added to each AST node that can receive documentation. */ @@ -358,6 +386,24 @@ protected: ASTPointer m_documentation; }; +/** + * Abstract class that is added to each AST node that can receive a structured documentation. + */ +class StructurallyDocumented +{ +public: + virtual ~StructurallyDocumented() = default; + explicit StructurallyDocumented(ASTPointer const& _documentation): m_documentation(_documentation) {} + + /// @return A shared pointer of a FormalDocumentation. + /// Can contain a nullptr in which case indicates absence of documentation + ASTPointer const& documentation() const { return m_documentation; } + +protected: + ASTPointer m_documentation; +}; + + /** * Abstract class that is added to AST nodes that can be marked as not being fully implemented */ @@ -381,20 +427,21 @@ protected: * document order. It first visits all struct declarations, then all variable declarations and * finally all function declarations. */ -class ContractDefinition: public Declaration, public Documented +class ContractDefinition: public Declaration, public StructurallyDocumented { public: ContractDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, - ASTPointer const& _documentation, + ASTPointer const& _documentation, std::vector> const& _baseContracts, std::vector> const& _subNodes, ContractKind _contractKind = ContractKind::Contract, bool _abstract = false ): - Declaration(_location, _name), - Documented(_documentation), + Declaration(_id, _location, _name), + StructurallyDocumented(_documentation), m_baseContracts(_baseContracts), m_subNodes(_subNodes), m_contractKind(_contractKind), @@ -418,13 +465,16 @@ public: bool isInterface() const { return m_contractKind == ContractKind::Interface; } bool isLibrary() const { return m_contractKind == ContractKind::Library; } + /// @returns true, if the contract derives from @arg _base. + bool derivesFrom(ContractDefinition const& _base) const; + /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. std::map, FunctionTypePointer> interfaceFunctions() const; std::vector, FunctionTypePointer>> const& interfaceFunctionList() const; - /// @returns a list of the inheritable members of this contract - std::vector const& inheritableMembers() const; + /// @returns a list of all declarations in this contract + std::vector declarations() const { return filteredNodes(m_subNodes); } /// Returns the constructor or nullptr if no constructor was specified. FunctionDefinition const* constructor() const; @@ -458,18 +508,18 @@ private: mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; mutable std::unique_ptr> m_interfaceEvents; - mutable std::unique_ptr> m_inheritableMembers; }; class InheritanceSpecifier: public ASTNode { public: InheritanceSpecifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseName, std::unique_ptr>> _arguments ): - ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -494,11 +544,12 @@ class UsingForDirective: public ASTNode { public: UsingForDirective( + int64_t _id, SourceLocation const& _location, ASTPointer const& _libraryName, ASTPointer const& _typeName ): - ASTNode(_location), m_libraryName(_libraryName), m_typeName(_typeName) {} + ASTNode(_id, _location), m_libraryName(_libraryName), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -516,11 +567,12 @@ class StructDefinition: public Declaration { public: StructDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -529,6 +581,9 @@ public: TypePointer type() const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return true; } + TypeDeclarationAnnotation& annotation() const override; private: @@ -539,14 +594,18 @@ class EnumDefinition: public Declaration { public: EnumDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::vector> const& _members ): - Declaration(_location, _name), m_members(_members) {} + Declaration(_id, _location, _name), m_members(_members) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return true; } + std::vector> const& members() const { return m_members; } TypePointer type() const override; @@ -563,8 +622,8 @@ private: class EnumValue: public Declaration { public: - EnumValue(SourceLocation const& _location, ASTPointer const& _name): - Declaration(_location, _name) {} + EnumValue(int64_t _id, SourceLocation const& _location, ASTPointer const& _name): + Declaration(_id, _location, _name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -581,10 +640,11 @@ class ParameterList: public ASTNode { public: ParameterList( + int64_t _id, SourceLocation const& _location, std::vector> const& _parameters ): - ASTNode(_location), m_parameters(_parameters) {} + ASTNode(_id, _location), m_parameters(_parameters) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -602,6 +662,7 @@ class CallableDeclaration: public Declaration, public VariableScope { public: CallableDeclaration( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -610,7 +671,7 @@ public: ASTPointer const& _overrides = nullptr, ASTPointer const& _returnParameters = ASTPointer() ): - Declaration(_location, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_parameters(_parameters), m_overrides(_overrides), m_returnParameters(_returnParameters), @@ -643,10 +704,11 @@ class OverrideSpecifier: public ASTNode { public: OverrideSpecifier( + int64_t _id, SourceLocation const& _location, std::vector> const& _overrides ): - ASTNode(_location), + ASTNode(_id, _location), m_overrides(_overrides) { } @@ -661,10 +723,11 @@ protected: std::vector> m_overrides; }; -class FunctionDefinition: public CallableDeclaration, public Documented, public ImplementationOptional +class FunctionDefinition: public CallableDeclaration, public StructurallyDocumented, public ImplementationOptional { public: FunctionDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, Visibility _visibility, @@ -672,14 +735,14 @@ public: Token _kind, bool _isVirtual, ASTPointer const& _overrides, - ASTPointer const& _documentation, + ASTPointer const& _documentation, ASTPointer const& _parameters, std::vector> const& _modifiers, ASTPointer const& _returnParameters, ASTPointer const& _body ): - CallableDeclaration(_location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), - Documented(_documentation), + CallableDeclaration(_id, _location, _name, _visibility, _parameters, _isVirtual, _overrides, _returnParameters), + StructurallyDocumented(_documentation), ImplementationOptional(_body != nullptr), m_stateMutability(_stateMutability), m_kind(_kind), @@ -705,6 +768,10 @@ public: { return Declaration::isVisibleInContract() && isOrdinary(); } + bool isVisibleViaContractTypeAccess() const override + { + return visibility() >= Visibility::Public; + } bool isPartOfExternalInterface() const override { return isPublic() && isOrdinary(); } /// @returns the external signature of the function @@ -718,6 +785,7 @@ public: ContractKind inContractKind() const; TypePointer type() const override; + TypePointer typeViaContractName() 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. @@ -729,7 +797,7 @@ public: { return CallableDeclaration::virtualSemantics() || - annotation().contract->isInterface(); + (annotation().contract && annotation().contract->isInterface()); } private: StateMutability m_stateMutability; @@ -748,7 +816,8 @@ public: enum Location { Unspecified, Storage, Memory, CallData }; VariableDeclaration( - SourceLocation const& _sourceLocation, + int64_t _id, + SourceLocation const& _location, ASTPointer const& _type, ASTPointer const& _name, ASTPointer _value, @@ -759,7 +828,7 @@ public: ASTPointer const& _overrides = nullptr, Location _referenceLocation = Location::Unspecified ): - Declaration(_sourceLocation, _name, _visibility), + Declaration(_id, _location, _name, _visibility), m_typeName(_type), m_value(_value), m_isStateVariable(_isStateVar), @@ -843,20 +912,21 @@ private: /** * Definition of a function modifier. */ -class ModifierDefinition: public CallableDeclaration, public Documented +class ModifierDefinition: public CallableDeclaration, public StructurallyDocumented { public: ModifierDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, - ASTPointer const& _documentation, + ASTPointer const& _documentation, ASTPointer const& _parameters, bool _isVirtual, ASTPointer const& _overrides, ASTPointer const& _body ): - CallableDeclaration(_location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), - Documented(_documentation), + CallableDeclaration(_id, _location, _name, Visibility::Internal, _parameters, _isVirtual, _overrides), + StructurallyDocumented(_documentation), m_body(_body) { } @@ -868,6 +938,8 @@ public: TypePointer type() const override; + Visibility defaultVisibility() const override { return Visibility::Internal; } + ModifierDefinitionAnnotation& annotation() const override; private: @@ -881,11 +953,12 @@ class ModifierInvocation: public ASTNode { public: ModifierInvocation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, std::unique_ptr>> _arguments ): - ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} + ASTNode(_id, _location), m_modifierName(_name), m_arguments(std::move(_arguments)) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -904,18 +977,19 @@ private: /** * Definition of a (loggable) event. */ -class EventDefinition: public CallableDeclaration, public Documented +class EventDefinition: public CallableDeclaration, public StructurallyDocumented { public: EventDefinition( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name, - ASTPointer const& _documentation, + ASTPointer const& _documentation, ASTPointer const& _parameters, bool _anonymous = false ): - CallableDeclaration(_location, _name, Visibility::Default, _parameters), - Documented(_documentation), + CallableDeclaration(_id, _location, _name, Visibility::Default, _parameters), + StructurallyDocumented(_documentation), m_anonymous(_anonymous) { } @@ -928,6 +1002,9 @@ public: TypePointer type() const override; FunctionTypePointer functionType(bool /*_internal*/) const override; + bool isVisibleInDerivedContracts() const override { return true; } + bool isVisibleViaContractTypeAccess() const override { return false; /* TODO */ } + EventDefinitionAnnotation& annotation() const override; private: @@ -936,13 +1013,13 @@ private: /** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global - * functions when such an identifier is encountered. Will never have a valid location in the source code. + * functions when such an identifier is encountered. Will never have a valid location in the source code */ class MagicVariableDeclaration: public Declaration { public: - MagicVariableDeclaration(ASTString const& _name, Type const* _type): - Declaration(SourceLocation(), std::make_shared(_name)), m_type(_type) {} + MagicVariableDeclaration(int _id, ASTString const& _name, Type const* _type): + Declaration(_id, SourceLocation(), std::make_shared(_name)), m_type(_type) { } void accept(ASTVisitor&) override { @@ -973,7 +1050,7 @@ private: class TypeName: public ASTNode { protected: - explicit TypeName(SourceLocation const& _location): ASTNode(_location) {} + explicit TypeName(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} public: TypeNameAnnotation& annotation() const override; @@ -987,10 +1064,11 @@ class ElementaryTypeName: public TypeName { public: ElementaryTypeName( + int64_t _id, SourceLocation const& _location, ElementaryTypeNameToken const& _elem, std::optional _stateMutability = {} - ): TypeName(_location), m_type(_elem), m_stateMutability(_stateMutability) + ): TypeName(_id, _location), m_type(_elem), m_stateMutability(_stateMutability) { solAssert(!_stateMutability.has_value() || _elem.token() == Token::Address, ""); } @@ -1013,8 +1091,8 @@ private: class UserDefinedTypeName: public TypeName { public: - UserDefinedTypeName(SourceLocation const& _location, std::vector const& _namePath): - TypeName(_location), m_namePath(_namePath) {} + UserDefinedTypeName(int64_t _id, SourceLocation const& _location, std::vector const& _namePath): + TypeName(_id, _location), m_namePath(_namePath) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1033,13 +1111,14 @@ class FunctionTypeName: public TypeName { public: FunctionTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _parameterTypes, ASTPointer const& _returnTypes, Visibility _visibility, StateMutability _stateMutability ): - TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + TypeName(_id, _location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), m_visibility(_visibility), m_stateMutability(_stateMutability) {} void accept(ASTVisitor& _visitor) override; @@ -1071,19 +1150,20 @@ class Mapping: public TypeName { public: Mapping( + int64_t _id, SourceLocation const& _location, - ASTPointer const& _keyType, + ASTPointer const& _keyType, ASTPointer const& _valueType ): - TypeName(_location), m_keyType(_keyType), m_valueType(_valueType) {} + TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; - ElementaryTypeName const& keyType() const { return *m_keyType; } + TypeName const& keyType() const { return *m_keyType; } TypeName const& valueType() const { return *m_valueType; } private: - ASTPointer m_keyType; + ASTPointer m_keyType; ASTPointer m_valueType; }; @@ -1094,11 +1174,12 @@ class ArrayTypeName: public TypeName { public: ArrayTypeName( + int64_t _id, SourceLocation const& _location, ASTPointer const& _baseType, ASTPointer const& _length ): - TypeName(_location), m_baseType(_baseType), m_length(_length) {} + TypeName(_id, _location), m_baseType(_baseType), m_length(_length) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1123,9 +1204,10 @@ class Statement: public ASTNode, public Documented { public: explicit Statement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): ASTNode(_location), Documented(_docString) {} + ): ASTNode(_id, _location), Documented(_docString) {} StatementAnnotation& annotation() const override; }; @@ -1137,12 +1219,13 @@ class InlineAssembly: public Statement { public: InlineAssembly( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, yul::Dialect const& _dialect, std::shared_ptr const& _operations ): - Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {} + Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1163,11 +1246,12 @@ class Block: public Statement, public Scopable { public: Block( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _statements ): - Statement(_location, _docString), m_statements(_statements) {} + Statement(_id, _location, _docString), m_statements(_statements) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1187,9 +1271,10 @@ class PlaceholderStatement: public Statement { public: explicit PlaceholderStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1203,13 +1288,14 @@ class IfStatement: public Statement { public: IfStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _trueBody, ASTPointer const& _falseBody ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_condition(_condition), m_trueBody(_trueBody), m_falseBody(_falseBody) @@ -1237,12 +1323,13 @@ class TryCatchClause: public ASTNode, public Scopable { public: TryCatchClause( + int64_t _id, SourceLocation const& _location, ASTPointer const& _errorName, ASTPointer const& _parameters, ASTPointer const& _block ): - ASTNode(_location), + ASTNode(_id, _location), m_errorName(_errorName), m_parameters(_parameters), m_block(_block) @@ -1280,12 +1367,13 @@ class TryStatement: public Statement { public: TryStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _externalCall, std::vector> const& _clauses ): - Statement(_location, _docString), + Statement(_id, _location, _docString), m_externalCall(_externalCall), m_clauses(_clauses) {} @@ -1307,22 +1395,24 @@ class BreakableStatement: public Statement { public: explicit BreakableStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString - ): Statement(_location, _docString) {} + ): Statement(_id, _location, _docString) {} }; class WhileStatement: public BreakableStatement { public: WhileStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, ASTPointer const& _body, bool _isDoWhile ): - BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body), + BreakableStatement(_id, _location, _docString), m_condition(_condition), m_body(_body), m_isDoWhile(_isDoWhile) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1344,6 +1434,7 @@ class ForStatement: public BreakableStatement, public Scopable { public: ForStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _initExpression, @@ -1351,7 +1442,7 @@ public: ASTPointer const& _loopExpression, ASTPointer const& _body ): - BreakableStatement(_location, _docString), + BreakableStatement(_id, _location, _docString), m_initExpression(_initExpression), m_condExpression(_conditionExpression), m_loopExpression(_loopExpression), @@ -1381,8 +1472,8 @@ private: class Continue: public Statement { public: - explicit Continue(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Continue(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1390,8 +1481,8 @@ public: class Break: public Statement { public: - explicit Break(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Break(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1400,10 +1491,11 @@ class Return: public Statement { public: Return( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression - ): Statement(_location, _docString), m_expression(_expression) {} + ): Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1421,8 +1513,8 @@ private: class Throw: public Statement { public: - explicit Throw(SourceLocation const& _location, ASTPointer const& _docString): - Statement(_location, _docString) {} + explicit Throw(int64_t _id, SourceLocation const& _location, ASTPointer const& _docString): + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; }; @@ -1434,11 +1526,12 @@ class EmitStatement: public Statement { public: explicit EmitStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _functionCall ): - Statement(_location, _docString), m_eventCall(_functionCall) {} + Statement(_id, _location, _docString), m_eventCall(_functionCall) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1460,12 +1553,13 @@ class VariableDeclarationStatement: public Statement { public: VariableDeclarationStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, std::vector> const& _variables, ASTPointer const& _initialValue ): - Statement(_location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} + Statement(_id, _location, _docString), m_variables(_variables), m_initialValue(_initialValue) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1489,11 +1583,12 @@ class ExpressionStatement: public Statement { public: ExpressionStatement( + int64_t _id, SourceLocation const& _location, ASTPointer const& _docString, ASTPointer _expression ): - Statement(_location, _docString), m_expression(_expression) {} + Statement(_id, _location, _docString), m_expression(_expression) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1516,7 +1611,7 @@ private: class Expression: public ASTNode { public: - explicit Expression(SourceLocation const& _location): ASTNode(_location) {} + explicit Expression(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {} ExpressionAnnotation& annotation() const override; }; @@ -1525,12 +1620,13 @@ class Conditional: public Expression { public: Conditional( + int64_t _id, SourceLocation const& _location, ASTPointer const& _condition, ASTPointer const& _trueExpression, ASTPointer const& _falseExpression ): - Expression(_location), + Expression(_id, _location), m_condition(_condition), m_trueExpression(_trueExpression), m_falseExpression(_falseExpression) @@ -1554,12 +1650,13 @@ class Assignment: public Expression { public: Assignment( + int64_t _id, SourceLocation const& _location, ASTPointer const& _leftHandSide, Token _assignmentOperator, ASTPointer const& _rightHandSide ): - Expression(_location), + Expression(_id, _location), m_leftHandSide(_leftHandSide), m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide) @@ -1591,11 +1688,12 @@ class TupleExpression: public Expression { public: TupleExpression( + int64_t _id, SourceLocation const& _location, std::vector> const& _components, bool _isArray ): - Expression(_location), + Expression(_id, _location), m_components(_components), m_isArray(_isArray) {} void accept(ASTVisitor& _visitor) override; @@ -1617,12 +1715,13 @@ class UnaryOperation: public Expression { public: UnaryOperation( + int64_t _id, SourceLocation const& _location, Token _operator, ASTPointer const& _subExpression, bool _isPrefix ): - Expression(_location), + Expression(_id, _location), m_operator(_operator), m_subExpression(_subExpression), m_isPrefix(_isPrefix) @@ -1650,12 +1749,13 @@ class BinaryOperation: public Expression { public: BinaryOperation( + int64_t _id, SourceLocation const& _location, ASTPointer const& _left, Token _operator, ASTPointer const& _right ): - Expression(_location), m_left(_left), m_operator(_operator), m_right(_right) + Expression(_id, _location), m_left(_left), m_operator(_operator), m_right(_right) { solAssert(TokenTraits::isBinaryOp(_operator) || TokenTraits::isCompareOp(_operator), ""); } @@ -1681,12 +1781,13 @@ class FunctionCall: public Expression { public: FunctionCall( + int64_t _id, SourceLocation const& _location, ASTPointer const& _expression, std::vector> const& _arguments, std::vector> const& _names ): - Expression(_location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} + Expression(_id, _location), m_expression(_expression), m_arguments(_arguments), m_names(_names) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1702,6 +1803,35 @@ private: std::vector> m_names; }; +/** + * Expression that annotates a function call / a new expression with extra + * options like gas, value, salt: new SomeContract{salt=123}(params) + */ +class FunctionCallOptions: public Expression +{ +public: + FunctionCallOptions( + int64_t _id, + SourceLocation const& _location, + ASTPointer const& _expression, + std::vector> const& _options, + std::vector> const& _names + ): + Expression(_id, _location), m_expression(_expression), m_options(_options), m_names(_names) {} + void accept(ASTVisitor& _visitor) override; + void accept(ASTConstVisitor& _visitor) const override; + + Expression const& expression() const { return *m_expression; } + std::vector> options() const { return {m_options.begin(), m_options.end()}; } + std::vector> const& names() const { return m_names; } + +private: + ASTPointer m_expression; + std::vector> m_options; + std::vector> m_names; + +}; + /** * Expression that creates a new contract or memory-array, * e.g. the "new SomeContract" part in "new SomeContract(1, 2)". @@ -1710,10 +1840,11 @@ class NewExpression: public Expression { public: NewExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _typeName ): - Expression(_location), m_typeName(_typeName) {} + Expression(_id, _location), m_typeName(_typeName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1730,11 +1861,12 @@ class MemberAccess: public Expression { public: MemberAccess( + int64_t _id, SourceLocation const& _location, ASTPointer _expression, ASTPointer const& _memberName ): - Expression(_location), m_expression(_expression), m_memberName(_memberName) {} + Expression(_id, _location), m_expression(_expression), m_memberName(_memberName) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; Expression const& expression() const { return *m_expression; } @@ -1754,11 +1886,12 @@ class IndexAccess: public Expression { public: IndexAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _index ): - Expression(_location), m_base(_base), m_index(_index) {} + Expression(_id, _location), m_base(_base), m_index(_index) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1777,12 +1910,13 @@ class IndexRangeAccess: public Expression { public: IndexRangeAccess( + int64_t _id, SourceLocation const& _location, ASTPointer const& _base, ASTPointer const& _start, ASTPointer const& _end ): - Expression(_location), m_base(_base), m_start(_start), m_end(_end) {} + Expression(_id, _location), m_base(_base), m_start(_start), m_end(_end) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1803,7 +1937,7 @@ private: class PrimaryExpression: public Expression { public: - PrimaryExpression(SourceLocation const& _location): Expression(_location) {} + PrimaryExpression(int64_t _id, SourceLocation const& _location): Expression(_id, _location) {} }; /** @@ -1813,10 +1947,11 @@ class Identifier: public PrimaryExpression { public: Identifier( + int64_t _id, SourceLocation const& _location, ASTPointer const& _name ): - PrimaryExpression(_location), m_name(_name) {} + PrimaryExpression(_id, _location), m_name(_name) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; @@ -1837,10 +1972,11 @@ class ElementaryTypeNameExpression: public PrimaryExpression { public: ElementaryTypeNameExpression( + int64_t _id, SourceLocation const& _location, ASTPointer const& _type ): - PrimaryExpression(_location), + PrimaryExpression(_id, _location), m_type(_type) { } @@ -1874,12 +2010,13 @@ public: Year = static_cast(Token::SubYear) }; Literal( + int64_t _id, SourceLocation const& _location, Token _token, ASTPointer const& _value, SubDenomination _sub = SubDenomination::None ): - PrimaryExpression(_location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} + PrimaryExpression(_id, _location), m_token(_token), m_value(_value), m_subDenomination(_sub) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index ea7ca8590..86636684c 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -56,9 +56,9 @@ struct DocTag std::string paramName; ///< Only used for @param, stores the parameter name. }; -struct DocumentedAnnotation +struct StructurallyDocumentedAnnotation { - virtual ~DocumentedAnnotation() = default; + virtual ~StructurallyDocumentedAnnotation() = default; /// Mapping docstring tag name -> content. std::multimap docTags; }; @@ -78,6 +78,9 @@ struct ScopableAnnotation /// The scope this declaration resides in. Can be nullptr if it is the global scope. /// Available only after name and type resolution step. ASTNode const* scope = nullptr; + /// Pointer to the contract this declaration resides in. Can be nullptr if the current scope + /// is not part of a contract. Available only after name and type resolution step. + ContractDefinition const* contract = nullptr; }; struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation @@ -98,7 +101,7 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation std::string canonicalName; }; -struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation +struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation { /// List of functions without a body. Can also contain functions from base classes. std::vector unimplementedFunctions; @@ -119,17 +122,15 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation std::set baseFunctions; }; -struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation -{ - /// Pointer to the contract this function is defined in - ContractDefinition const* contract = nullptr; -}; - -struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation +struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation { }; -struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation +struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation +{ +}; + +struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation { }; @@ -141,7 +142,7 @@ struct VariableDeclarationAnnotation: DeclarationAnnotation std::set baseFunctions; }; -struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation +struct StatementAnnotation: ASTAnnotation { }; diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index a455f30b2..38da35218 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -93,6 +93,7 @@ class PrimaryExpression; class Identifier; class ElementaryTypeNameExpression; class Literal; +class StructuredDocumentation; class VariableScope; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 8d4c08272..ed3c6fff9 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -26,6 +26,8 @@ #include #include #include +#include + #include #include @@ -266,7 +268,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node) { setJsonNode(_node, "ContractDefinition", { make_pair("name", _node.name()), - make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), + make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("contractKind", contractKind(_node.contractKind())), make_pair("abstract", _node.abstract()), make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()), @@ -347,7 +349,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) { std::vector> attributes = { make_pair("name", _node.name()), - make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), + make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("kind", TokenTraits::toString(_node.kind())), make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), @@ -398,7 +400,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node) { std::vector> attributes = { make_pair("name", _node.name()), - make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), + make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("parameters", toJson(_node.parameterList())), make_pair("virtual", _node.markedVirtual()), @@ -425,7 +427,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node) m_inEvent = true; setJsonNode(_node, "EventDefinition", { make_pair("name", _node.name()), - make_pair("documentation", _node.documentation() ? Json::Value(*_node.documentation()) : Json::nullValue), + make_pair("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue), make_pair("parameters", toJson(_node.parameterList())), make_pair("anonymous", _node.isAnonymous()) }); @@ -492,13 +494,16 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) bool ASTJsonConverter::visit(InlineAssembly const& _node) { vector> externalReferences; + for (auto const& it: _node.annotation().externalReferences) if (it.first) externalReferences.emplace_back(make_pair( it.first->name.str(), inlineAssemblyIdentifierToJson(it) )); + Json::Value externalReferencesJson = Json::arrayValue; + for (auto&& it: boost::range::sort(externalReferences)) externalReferencesJson.append(std::move(it.second)); @@ -506,8 +511,10 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node) m_legacy ? make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) : make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))), - make_pair("externalReferences", std::move(externalReferencesJson)) + make_pair("externalReferences", std::move(externalReferencesJson)), + make_pair("evmVersion", dynamic_cast(_node.dialect()).evmVersion().name()) }); + return false; } @@ -717,6 +724,23 @@ bool ASTJsonConverter::visit(FunctionCall const& _node) return false; } +bool ASTJsonConverter::visit(FunctionCallOptions const& _node) +{ + Json::Value names(Json::arrayValue); + for (auto const& name: _node.names()) + names.append(Json::Value(*name)); + + std::vector> attributes = { + make_pair("expression", toJson(_node.expression())), + make_pair("names", std::move(names)), + make_pair("options", toJson(_node.options())), + }; + appendExpressionAttributes(attributes, _node.annotation()); + + setJsonNode(_node, "FunctionCallOptions", std::move(attributes)); + return false; +} + bool ASTJsonConverter::visit(NewExpression const& _node) { std::vector> attributes = { @@ -809,6 +833,17 @@ bool ASTJsonConverter::visit(Literal const& _node) return false; } +bool ASTJsonConverter::visit(StructuredDocumentation const& _node) +{ + Json::Value text{*_node.text()}; + std::vector> attributes = { + make_pair("text", text) + }; + setJsonNode(_node, "StructuredDocumentation", std::move(attributes)); + return false; +} + + void ASTJsonConverter::endVisit(EventDefinition const&) { diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index cf0d91f8b..a1d3cd4ad 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -111,6 +111,7 @@ public: bool visit(UnaryOperation const& _node) override; bool visit(BinaryOperation const& _node) override; bool visit(FunctionCall const& _node) override; + bool visit(FunctionCallOptions const& _node) override; bool visit(NewExpression const& _node) override; bool visit(MemberAccess const& _node) override; bool visit(IndexAccess const& _node) override; @@ -118,6 +119,7 @@ public: bool visit(Identifier const& _node) override; bool visit(ElementaryTypeNameExpression const& _node) override; bool visit(Literal const& _node) override; + bool visit(StructuredDocumentation const& _node) override; void endVisit(EventDefinition const&) override; diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp new file mode 100644 index 000000000..aa28e3f17 --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -0,0 +1,1018 @@ +/* + 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 julius + * @date 2019 + *Component that imports an AST from json format to the internal format + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace std; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +template +ASTPointer ASTJsonImporter::nullOrCast(Json::Value const& _json) +{ + if (_json.isNull()) + return nullptr; + else + return dynamic_pointer_cast(convertJsonToASTNode(_json)); +} + + +// ============ public =========================== + +map> ASTJsonImporter::jsonToSourceUnit(map const& _sourceList) +{ + m_sourceList = _sourceList; + for (auto const& src: _sourceList) + m_sourceLocations.emplace_back(make_shared(src.first)); + for (auto const& srcPair: m_sourceList) + { + astAssert(!srcPair.second.isNull(), ""); + astAssert(member(srcPair.second,"nodeType") == "SourceUnit", "The 'nodeType' of the highest node must be 'SourceUnit'."); + m_currentSourceName = srcPair.first; + m_sourceUnits[srcPair.first] = createSourceUnit(srcPair.second, srcPair.first); + } + return m_sourceUnits; +} + +// ============ private =========================== + +// =========== general creation functions ============== +template +ASTPointer ASTJsonImporter::createASTNode(Json::Value const& _node, Args&&... _args) +{ + astAssert(member(_node, "id").isInt64(), "'id'-field must be 64bit integer."); + + int64_t id = _node["id"].asInt64(); + + astAssert(m_usedIDs.insert(id).second, "Found duplicate node ID!"); + + auto n = make_shared( + id, + createSourceLocation(_node), + forward(_args)... + ); + return n; +} + +SourceLocation const ASTJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_currentSourceName, int(m_sourceLocations.size())); +} + +template +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _node) +{ + ASTPointer ret = dynamic_pointer_cast(convertJsonToASTNode(_node)); + astAssert(ret, "cast of converted json-node must not be nullptr"); + return ret; +} + + +ASTPointer ASTJsonImporter::convertJsonToASTNode(Json::Value const& _json) +{ + astAssert(_json["nodeType"].isString() && _json.isMember("id"), "JSON-Node needs to have 'nodeType' and 'id' fields."); + string nodeType = _json["nodeType"].asString(); + if (nodeType == "PragmaDirective") + return createPragmaDirective(_json); + if (nodeType == "ImportDirective") + return createImportDirective(_json); + if (nodeType == "ContractDefinition") + return createContractDefinition(_json); + if (nodeType == "InheritanceSpecifier") + return createInheritanceSpecifier(_json); + if (nodeType == "UsingForDirective") + return createUsingForDirective(_json); + if (nodeType == "StructDefinition") + return createStructDefinition(_json); + if (nodeType == "EnumDefinition") + return createEnumDefinition(_json); + if (nodeType == "EnumValue") + return createEnumValue(_json); + if (nodeType == "ParameterList") + return createParameterList(_json); + if (nodeType == "OverrideSpecifier") + return createOverrideSpecifier(_json); + if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_json); + if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_json); + if (nodeType == "ModifierDefinition") + return createModifierDefinition(_json); + if (nodeType == "ModifierInvocation") + return createModifierInvocation(_json); + if (nodeType == "EventDefinition") + return createEventDefinition(_json); + if (nodeType == "ElementaryTypeName") + return createElementaryTypeName(_json); + if (nodeType == "UserDefinedTypeName") + return createUserDefinedTypeName(_json); + if (nodeType == "FunctionTypeName") + return createFunctionTypeName(_json); + if (nodeType == "Mapping") + return createMapping(_json); + if (nodeType == "ArrayTypeName") + return createArrayTypeName(_json); + if (nodeType == "InlineAssembly") + return createInlineAssembly(_json); + if (nodeType == "Block") + return createBlock(_json); + if (nodeType == "PlaceholderStatement") + return createPlaceholderStatement(_json); + if (nodeType == "IfStatement") + return createIfStatement(_json); + if (nodeType == "TryCatchClause") + return createTryCatchClause(_json); + if (nodeType == "TryStatement") + return createTryStatement(_json); + if (nodeType == "WhileStatement") + return createWhileStatement(_json, false); + if (nodeType == "DoWhileStatement") + return createWhileStatement(_json, true); + if (nodeType == "ForStatement") + return createForStatement(_json); + if (nodeType == "Continue") + return createContinue(_json); + if (nodeType == "Break") + return createBreak(_json); + if (nodeType == "Return") + return createReturn(_json); + if (nodeType == "EmitStatement") + return createEmitStatement(_json); + if (nodeType == "Throw") + return createThrow(_json); + if (nodeType == "VariableDeclarationStatement") + return createVariableDeclarationStatement(_json); + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_json); + if (nodeType == "Conditional") + return createConditional(_json); + if (nodeType == "Assignment") + return createAssignment(_json); + if (nodeType == "TupleExpression") + return createTupleExpression(_json); + if (nodeType == "UnaryOperation") + return createUnaryOperation(_json); + if (nodeType == "BinaryOperation") + return createBinaryOperation(_json); + if (nodeType == "FunctionCall") + return createFunctionCall(_json); + if (nodeType == "FunctionCallOptions") + return createFunctionCallOptions(_json); + if (nodeType == "NewExpression") + return createNewExpression(_json); + if (nodeType == "MemberAccess") + return createMemberAccess(_json); + if (nodeType == "IndexAccess") + return createIndexAccess(_json); + if (nodeType == "IndexRangeAccess") + return createIndexRangeAccess(_json); + if (nodeType == "Identifier") + return createIdentifier(_json); + if (nodeType == "ElementaryTypeNameExpression") + return createElementaryTypeNameExpression(_json); + if (nodeType == "Literal") + return createLiteral(_json); + if (nodeType == "StructuredDocumentation") + return createDocumentation(_json); + else + astAssert(false, "Unknown type of ASTNode: " + nodeType); +} + +// ============ functions to instantiate the AST-Nodes from Json-Nodes ============== + +ASTPointer ASTJsonImporter::createSourceUnit(Json::Value const& _node, string const& _srcName) +{ + vector> nodes; + for (auto& child: member(_node, "nodes")) + nodes.emplace_back(convertJsonToASTNode(child)); + ASTPointer tmp = createASTNode(_node, nodes); + tmp->annotation().path = _srcName; + return tmp; +} + +ASTPointer ASTJsonImporter::createPragmaDirective(Json::Value const& _node) +{ + vector tokens; + vector literals; + for (auto const& lit: member(_node, "literals")) + { + string l = lit.asString(); + literals.push_back(l); + tokens.push_back(scanSingleToken(l)); + } + return createASTNode(_node, tokens, literals); +} + +ASTPointer ASTJsonImporter::createImportDirective(Json::Value const& _node) +{ + ASTPointer unitAlias = memberAsASTString(_node, "unitAlias"); + ASTPointer path = memberAsASTString(_node, "file"); + ImportDirective::SymbolAliasList symbolAliases; + + for (auto& tuple: member(_node, "symbolAliases")) + { + astAssert(tuple["local"].isNull() || tuple["local"].isString(), "expected 'local' to be a string or null!"); + + symbolAliases.push_back({ + createIdentifier(tuple["foreign"]), + tuple["local"].isNull() ? nullptr : make_shared(tuple["local"].asString()), + createSourceLocation(tuple["foreign"])} + ); + } + ASTPointer tmp = createASTNode( + _node, + path, + unitAlias, + move(symbolAliases) + ); + + astAssert(_node["absolutePath"].isString(), "Expected 'absolutePath' to be a string!"); + + tmp->annotation().absolutePath = _node["absolutePath"].asString(); + return tmp; +} + +ASTPointer ASTJsonImporter::createContractDefinition(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + std::vector> baseContracts; + + for (auto& base: _node["baseContracts"]) + baseContracts.push_back(createInheritanceSpecifier(base)); + + std::vector> subNodes; + + for (auto& subnode: _node["nodes"]) + subNodes.push_back(convertJsonToASTNode(subnode)); + + return createASTNode( + _node, + make_shared(_node["name"].asString()), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), + baseContracts, + subNodes, + contractKind(_node), + memberAsBool(_node, "abstract") + ); +} + +ASTPointer ASTJsonImporter::createInheritanceSpecifier(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "baseName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createUsingForDirective(Json::Value const& _node) +{ + return createASTNode( + _node, + createUserDefinedTypeName(member(_node, "libraryName")), + _node["typeName"].isNull() ? nullptr : convertJsonToASTNode(_node["typeName"]) + ); +} + +ASTPointer ASTJsonImporter::createStructDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createVariableDeclaration(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumDefinition(Json::Value const& _node) +{ + std::vector> members; + for (auto& member: _node["members"]) + members.push_back(createEnumValue(member)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + members + ); +} + +ASTPointer ASTJsonImporter::createEnumValue(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name") + ); +} + +ASTPointer ASTJsonImporter::createParameterList(Json::Value const& _node) +{ + std::vector> parameters; + for (auto& param: _node["parameters"]) + parameters.push_back(createVariableDeclaration(param)); + return createASTNode( + _node, + parameters + ); +} + +ASTPointer ASTJsonImporter::createOverrideSpecifier(Json::Value const& _node) +{ + std::vector> overrides; + + for (auto& param: _node["overrides"]) + overrides.push_back(createUserDefinedTypeName(param)); + + return createASTNode( + _node, + overrides + ); +} + +ASTPointer ASTJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + astAssert(_node["kind"].isString(), "Expected 'kind' to be a string!"); + + Token kind; + string kindStr = member(_node, "kind").asString(); + + if (kindStr == "constructor") + kind = Token::Constructor; + else if (kindStr == "function") + kind = Token::Function; + else if (kindStr == "fallback") + kind = Token::Fallback; + else if (kindStr == "receive") + kind = Token::Receive; + else + astAssert(false, "Expected 'kind' to be one of [constructor, function, fallback, receive]"); + + std::vector> modifiers; + for (auto& mod: member(_node, "modifiers")) + modifiers.push_back(createModifierInvocation(mod)); + return createASTNode( + _node, + memberAsASTString(_node, "name"), + visibility(_node), + stateMutability(_node), + kind, + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), + createParameterList(member(_node, "parameters")), + modifiers, + createParameterList(member(_node, "returnParameters")), + memberAsBool(_node, "implemented") ? createBlock(member(_node, "body")) : nullptr + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + return createASTNode( + _node, + nullOrCast(member(_node, "typeName")), + make_shared(member(_node, "name").asString()), + nullOrCast(member(_node, "value")), + visibility(_node), + memberAsBool(_node, "stateVariable"), + _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, + memberAsBool(_node, "constant"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + location(_node) + ); +} + +ASTPointer ASTJsonImporter::createModifierDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "virtual"), + _node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")), + createBlock(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createModifierInvocation(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + return createASTNode( + _node, + createIdentifier(member(_node, "modifierName")), + member(_node, "arguments").isNull() ? nullptr : make_unique>>(arguments) + ); +} + +ASTPointer ASTJsonImporter::createEventDefinition(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "name"), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), + createParameterList(member(_node, "parameters")), + memberAsBool(_node, "anonymous") + ); +} + +ASTPointer ASTJsonImporter::createElementaryTypeName(Json::Value const& _node) +{ + unsigned short firstNum; + unsigned short secondNum; + + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + string name = member(_node, "name").asString(); + Token token; + tie(token, firstNum, secondNum) = TokenTraits::fromIdentifierOrKeyword(name); + ElementaryTypeNameToken elem(token, firstNum, secondNum); + + std::optional mutability = {}; + if (_node.isMember("stateMutability")) + mutability = stateMutability(_node); + + return createASTNode(_node, elem, mutability); +} + +ASTPointer ASTJsonImporter::createUserDefinedTypeName(Json::Value const& _node) +{ + astAssert(_node["name"].isString(), "Expected 'name' to be a string!"); + + vector namePath; + vector strs; + string nameString = member(_node, "name").asString(); + boost::algorithm::split(strs, nameString, boost::is_any_of(".")); + for (string s: strs) + namePath.push_back(ASTString(s)); + return createASTNode( + _node, + namePath + ); +} + +ASTPointer ASTJsonImporter::createFunctionTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + createParameterList(member(_node, "parameterTypes")), + createParameterList(member(_node, "returnParameterTypes")), + visibility(_node), + stateMutability(_node) + ); +} + +ASTPointer ASTJsonImporter::createMapping(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "keyType")), + convertJsonToASTNode(member(_node, "valueType")) + ); +} + +ASTPointer ASTJsonImporter::createArrayTypeName(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseType")), + nullOrCast(member(_node, "length")) + ); +} + +ASTPointer ASTJsonImporter::createInlineAssembly(Json::Value const& _node) +{ + astAssert(_node["evmVersion"].isString(), "Expected evmVersion to be a string!"); + auto evmVersion = langutil::EVMVersion::fromString(_node["evmVersion"].asString()); + astAssert(evmVersion.has_value(), "Invalid EVM version!"); + astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!"); + + yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value()); + shared_ptr operations = make_shared(AsmJsonImporter(m_currentSourceName).createBlock(member(_node, "AST"))); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + dialect, + operations + ); +} + +ASTPointer ASTJsonImporter::createBlock(Json::Value const& _node) +{ + std::vector> statements; + for (auto& stat: member(_node, "statements")) + statements.push_back(convertJsonToASTNode(stat)); + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + statements + ); +} + +ASTPointer ASTJsonImporter::createPlaceholderStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createIfStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueBody")), + nullOrCast(member(_node, "falseBody")) + ); +} + +ASTPointer ASTJsonImporter::createTryCatchClause(Json::Value const& _node) +{ + return createASTNode( + _node, + memberAsASTString(_node, "errorName"), + nullOrCast(member(_node, "parameters")), + convertJsonToASTNode(member(_node, "block")) + ); +} + +ASTPointer ASTJsonImporter::createTryStatement(Json::Value const& _node) +{ + vector> clauses; + + for (auto& param: _node["clauses"]) + clauses.emplace_back(createTryCatchClause(param)); + + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "externalCall")), + clauses + ); +} + +ASTPointer ASTJsonImporter::createWhileStatement(Json::Value const& _node, bool _isDoWhile=false) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "body")), + _isDoWhile + ); +} + +ASTPointer ASTJsonImporter::createForStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "initializationExpression")), + nullOrCast(member(_node, "condition")), + nullOrCast(member(_node, "loopExpression")), + convertJsonToASTNode(member(_node, "body")) + ); +} + +ASTPointer ASTJsonImporter::createContinue(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createBreak(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createReturn(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + nullOrCast(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createThrow(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation") + ); +} + +ASTPointer ASTJsonImporter::createEmitStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + createFunctionCall(member(_node, "eventCall")) + ); +} + +ASTPointer ASTJsonImporter::createVariableDeclarationStatement(Json::Value const& _node) +{ + std::vector> variables; + for (auto& var: member(_node, "declarations")) + variables.push_back(var.isNull() ? nullptr : createVariableDeclaration(var)); //unnamed components are empty pointers + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + variables, + nullOrCast(member(_node, "initialValue")) + ); +} + +ASTPointer ASTJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + return createASTNode( + _node, + nullOrASTString(_node, "documentation"), + convertJsonToASTNode(member(_node, "expression")) + ); +} + +ASTPointer ASTJsonImporter::createConditional(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "condition")), + convertJsonToASTNode(member(_node, "trueExpression")), + convertJsonToASTNode(member(_node, "falseExpression")) + ); +} + +ASTPointer ASTJsonImporter::createAssignment(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftHandSide")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightHandSide")) + ); +} + +ASTPointer ASTJsonImporter::createTupleExpression(Json::Value const& _node) +{ + std::vector> components; + for (auto& comp: member(_node, "components")) + components.push_back(nullOrCast(comp)); + return createASTNode( + _node, + components, + memberAsBool(_node, "isInlineArray") + ); +} + +ASTPointer ASTJsonImporter::createUnaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "subExpression")), + memberAsBool(_node, "prefix") + ); +} + +ASTPointer ASTJsonImporter::createBinaryOperation(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "leftExpression")), + scanSingleToken(member(_node, "operator")), + convertJsonToASTNode(member(_node, "rightExpression")) + ); +} + +ASTPointer ASTJsonImporter::createFunctionCall(Json::Value const& _node) +{ + std::vector> arguments; + for (auto& arg: member(_node, "arguments")) + arguments.push_back(convertJsonToASTNode(arg)); + std::vector> names; + for (auto& name: member(_node, "names")) + { + astAssert(name.isString(), "Expected 'names' members to be strings!"); + names.push_back(make_shared(name.asString())); + } + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + arguments, + names + ); +} + +ASTPointer ASTJsonImporter::createFunctionCallOptions(Json::Value const& _node) +{ + std::vector> options; + for (auto& option: member(_node, "options")) + options.push_back(convertJsonToASTNode(option)); + std::vector> names; + for (auto& name: member(_node, "names")) + { + astAssert(name.isString(), "Expected 'names' members to be strings!"); + names.push_back(make_shared(name.asString())); + } + + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + options, + names + ); +} + +ASTPointer ASTJsonImporter::createNewExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createMemberAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "expression")), + memberAsASTString(_node, "memberName") + ); +} + +ASTPointer ASTJsonImporter::createIndexAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "indexExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIndexRangeAccess(Json::Value const& _node) +{ + return createASTNode( + _node, + convertJsonToASTNode(member(_node, "baseExpression")), + nullOrCast(member(_node, "startExpression")), + nullOrCast(member(_node, "endExpression")) + ); +} + +ASTPointer ASTJsonImporter::createIdentifier(Json::Value const& _node) +{ + return createASTNode(_node, memberAsASTString(_node, "name")); +} + +ASTPointer ASTJsonImporter::createElementaryTypeNameExpression(Json::Value const& _node) +{ + return createASTNode( + _node, + createElementaryTypeName(member(_node, "typeName")) + ); +} + +ASTPointer ASTJsonImporter::createLiteral(Json::Value const& _node) +{ + static string const valStr = "value"; + static string const hexValStr = "hexValue"; + + astAssert(member(_node, valStr).isString() || member(_node, hexValStr).isString(), "Literal-value is unset."); + + ASTPointer value = _node.isMember(hexValStr) ? + make_shared(util::asString(util::fromHex(_node[hexValStr].asString()))) : + make_shared(_node[valStr].asString()); + + return createASTNode( + _node, + literalTokenKind(_node), + value, + member(_node, "subdenomination").isNull() ? Literal::SubDenomination::None : subdenomination(_node) + ); +} + +ASTPointer ASTJsonImporter::createDocumentation(Json::Value const& _node) +{ + static string const textString = "text"; + + astAssert(member(_node, textString).isString(), "'text' must be a string"); + + return createASTNode( + _node, + make_shared(_node[textString].asString()) + ); +} + +// ===== helper functions ========== + +Json::Value ASTJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node '" + _node["nodeType"].asString() + "' (id " + _node["id"].asString() + ") is missing field '" + _name + "'."); + return _node[_name]; +} + +Token ASTJsonImporter::scanSingleToken(Json::Value const& _node) +{ + langutil::Scanner scanner{langutil::CharStream(_node.asString(), "")}; + astAssert(scanner.peekNextToken() == Token::EOS, "Token string is too long."); + return scanner.currentToken(); +} + +ASTPointer ASTJsonImporter::nullOrASTString(Json::Value const& _json, string const& _name) +{ + return _json[_name].isString() ? memberAsASTString(_json, _name) : nullptr; +} + +ASTPointer ASTJsonImporter::memberAsASTString(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isString(), "field " + _name + " must be of type string."); + return make_shared(_node[_name].asString()); +} + +bool ASTJsonImporter::memberAsBool(Json::Value const& _node, string const& _name) +{ + Json::Value value = member(_node, _name); + astAssert(value.isBool(), "field " + _name + " must be of type boolean."); + return _node[_name].asBool(); +} + + +// =========== JSON to definition helpers ======================= + +ContractKind ASTJsonImporter::contractKind(Json::Value const& _node) +{ + ContractKind kind; + astAssert(!member(_node, "contractKind").isNull(), "'Contract-kind' can not be null."); + if (_node["contractKind"].asString() == "interface") + kind = ContractKind::Interface; + else if (_node["contractKind"].asString() == "contract") + kind = ContractKind::Contract; + else if (_node["contractKind"].asString() == "library") + kind = ContractKind::Library; + else + astAssert(false, "Unknown ContractKind"); + return kind; +} + +Token ASTJsonImporter::literalTokenKind(Json::Value const& _node) +{ + astAssert(member(_node, "kind").isString(), "Token-'kind' expected to be a string."); + Token tok; + if (_node["kind"].asString() == "number") + tok = Token::Number; + else if (_node["kind"].asString() == "string") + tok = Token::StringLiteral; + else if (_node["kind"].asString() == "bool") + tok = (member(_node, "value").asString() == "true") ? Token::TrueLiteral : Token::FalseLiteral; + else + astAssert(false, "Unknown kind of literalString"); + return tok; +} + +Visibility ASTJsonImporter::visibility(Json::Value const& _node) +{ + Json::Value visibility = member(_node, "visibility"); + astAssert(visibility.isString(), "'visibility' expected to be a string."); + + string const visibilityStr = visibility.asString(); + + if (visibilityStr == "default") + return Visibility::Default; + else if (visibilityStr == "private") + return Visibility::Private; + else if ( visibilityStr == "internal") + return Visibility::Internal; + else if (visibilityStr == "public") + return Visibility::Public; + else if (visibilityStr == "external") + return Visibility::External; + else + astAssert(false, "Unknown visibility declaration"); +} + +VariableDeclaration::Location ASTJsonImporter::location(Json::Value const& _node) +{ + Json::Value storageLoc = member(_node, "storageLocation"); + astAssert(storageLoc.isString(), "'storageLocation' expected to be a string."); + + string const storageLocStr = storageLoc.asString(); + + if (storageLocStr == "default") + return VariableDeclaration::Location::Unspecified; + else if (storageLocStr == "storage") + return VariableDeclaration::Location::Storage; + else if (storageLocStr == "memory") + return VariableDeclaration::Location::Memory; + else if (storageLocStr == "calldata") + return VariableDeclaration::Location::CallData; + else + astAssert(false, "Unknown location declaration"); +} + +Literal::SubDenomination ASTJsonImporter::subdenomination(Json::Value const& _node) +{ + Json::Value subDen = member(_node, "subdenomination"); + + if (subDen.isNull()) + return Literal::SubDenomination::None; + + astAssert(subDen.isString(), "'subDenomination' expected to be string."); + + string const subDenStr = subDen.asString(); + + if (subDenStr == "wei") + return Literal::SubDenomination::Wei; + else if (subDenStr == "szabo") + return Literal::SubDenomination::Szabo; + else if (subDenStr == "finney") + return Literal::SubDenomination::Finney; + else if (subDenStr == "ether") + return Literal::SubDenomination::Ether; + else if (subDenStr == "seconds") + return Literal::SubDenomination::Second; + else if (subDenStr == "minutes") + return Literal::SubDenomination::Minute; + else if (subDenStr == "hours") + return Literal::SubDenomination::Hour; + else if (subDenStr == "days") + return Literal::SubDenomination::Day; + else if (subDenStr == "weeks") + return Literal::SubDenomination::Week; + else if (subDenStr == "years") + return Literal::SubDenomination::Year; + else + astAssert(false, "Unknown subdenomination"); +} + +StateMutability ASTJsonImporter::stateMutability(Json::Value const& _node) +{ + astAssert(member(_node, "stateMutability").isString(), "StateMutability' expected to be string."); + string const mutabilityStr = member(_node, "stateMutability").asString(); + + if (mutabilityStr == "pure") + return StateMutability::Pure; + else if (mutabilityStr == "view") + return StateMutability::View; + else if (mutabilityStr == "nonpayable") + return StateMutability::NonPayable; + else if (mutabilityStr == "payable") + return StateMutability::Payable; + else + astAssert(false, "Unknown stateMutability"); +} + +} diff --git a/libsolidity/ast/ASTJsonImporter.h b/libsolidity/ast/ASTJsonImporter.h new file mode 100644 index 000000000..ebcbd660c --- /dev/null +++ b/libsolidity/ast/ASTJsonImporter.h @@ -0,0 +1,163 @@ +/* + 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 julius + * @date 2019 + * Converts the AST from JSON format to ASTNode + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class ASTJsonImporter +{ +public: + ASTJsonImporter(langutil::EVMVersion _evmVersion) + :m_evmVersion(_evmVersion) + {} + + /// Converts the AST from JSON-format to ASTPointer + /// @a _sourceList used to provide source names for the ASTs + /// @returns map of sourcenames to their respective ASTs + std::map> jsonToSourceUnit(std::map const& _sourceList); + +private: + + // =========== general creation functions ============== + + /// Sets the source location and nodeID + /// @returns the ASTNode Object class of the respective JSON node, + template + ASTPointer createASTNode(Json::Value const& _node, Args&&... _args); + /// @returns the sourceLocation-object created from the string in the JSON node + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + /// Creates an ASTNode for a given JSON-ast of unknown type + /// @returns Pointer to a new created ASTNode + ASTPointer convertJsonToASTNode(Json::Value const& _ast); + /// @returns a pointer to the more specific subclass of ASTNode + /// as indicated by the nodeType field of the json + template + ASTPointer convertJsonToASTNode(Json::Value const& _node); + + + /// \defgroup nodeCreators JSON to AST-Nodes + ///@{ + ASTPointer createSourceUnit(Json::Value const& _node, std::string const& _srcName); + ASTPointer createPragmaDirective(Json::Value const& _node); + ASTPointer createImportDirective(Json::Value const& _node); + ASTPointer createContractDefinition(Json::Value const& _node); + ASTPointer createInheritanceSpecifier(Json::Value const& _node); + ASTPointer createUsingForDirective(Json::Value const& _node); + ASTPointer createStructDefinition(Json::Value const& _node); + ASTPointer createEnumDefinition(Json::Value const& _node); + ASTPointer createEnumValue(Json::Value const& _node); + ASTPointer createParameterList(Json::Value const& _node); + ASTPointer createOverrideSpecifier(Json::Value const& _node); + ASTPointer createFunctionDefinition(Json::Value const& _node); + ASTPointer createVariableDeclaration(Json::Value const& _node); + ASTPointer createModifierDefinition(Json::Value const& _node); + ASTPointer createModifierInvocation(Json::Value const& _node); + ASTPointer createEventDefinition(Json::Value const& _node); + ASTPointer createElementaryTypeName(Json::Value const& _node); + ASTPointer createUserDefinedTypeName(Json::Value const& _node); + ASTPointer createFunctionTypeName(Json::Value const& _node); + ASTPointer createMapping(Json::Value const& _node); + ASTPointer createArrayTypeName(Json::Value const& _node); + ASTPointer createInlineAssembly(Json::Value const& _node); + ASTPointer createBlock(Json::Value const& _node); + ASTPointer createPlaceholderStatement(Json::Value const& _node); + ASTPointer createIfStatement(Json::Value const& _node); + ASTPointer createTryCatchClause(Json::Value const& _node); + ASTPointer createTryStatement(Json::Value const& _node); + ASTPointer createWhileStatement(Json::Value const& _node, bool _isDoWhile); + ASTPointer createForStatement(Json::Value const& _node); + ASTPointer createContinue(Json::Value const& _node); + ASTPointer createBreak(Json::Value const& _node); + ASTPointer createReturn(Json::Value const& _node); + ASTPointer createThrow(Json::Value const& _node); + ASTPointer createEmitStatement(Json::Value const& _node); + ASTPointer createVariableDeclarationStatement(Json::Value const& _node); + ASTPointer createExpressionStatement(Json::Value const& _node); + ASTPointer createConditional(Json::Value const& _node); + ASTPointer createAssignment(Json::Value const& _node); + ASTPointer createTupleExpression(Json::Value const& _node); + ASTPointer createUnaryOperation(Json::Value const& _node); + ASTPointer createBinaryOperation(Json::Value const& _node); + ASTPointer createFunctionCall(Json::Value const& _node); + ASTPointer createFunctionCallOptions(Json::Value const& _node); + ASTPointer createNewExpression(Json::Value const& _node); + ASTPointer createMemberAccess(Json::Value const& _node); + ASTPointer createIndexAccess(Json::Value const& _node); + ASTPointer createIndexRangeAccess(Json::Value const& _node); + ASTPointer createIdentifier(Json::Value const& _node); + ASTPointer createElementaryTypeNameExpression(Json::Value const& _node); + ASTPointer createLiteral(Json::Value const& _node); + ASTPointer createDocumentation(Json::Value const& _node); + ///@} + + // =============== general helper functions =================== + /// @returns the member of a given JSON object, throws if member does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + /// @returns the appropriate TokenObject used in parsed Strings (pragma directive or operator) + Token scanSingleToken(Json::Value const& _node); + template + ///@returns nullptr or an ASTPointer cast to a specific Class + ASTPointer nullOrCast(Json::Value const& _json); + /// @returns nullptr or ASTString, given an JSON string or an empty field + ASTPointer nullOrASTString(Json::Value const& _json, std::string const& _name); + + // ============== JSON to definition helpers =============== + /// \defgroup typeHelpers Json to ast-datatype helpers + /// {@ + ASTPointer memberAsASTString(Json::Value const& _node, std::string const& _name); + bool memberAsBool(Json::Value const& _node, std::string const& _name); + Visibility visibility(Json::Value const& _node); + StateMutability stateMutability(Json::Value const& _node); + VariableDeclaration::Location location(Json::Value const& _node); + ContractKind contractKind(Json::Value const& _node); + Token literalTokenKind(Json::Value const& _node); + Literal::SubDenomination subdenomination(Json::Value const& _node); + ///@} + + // =========== member variables =============== + /// Stores filepath as sourcenames to AST in JSON format + std::map m_sourceList; + /// list of filepaths (used as sourcenames) + std::vector> m_sourceLocations; + /// filepath to AST + std::map> m_sourceUnits; + std::string m_currentSourceName; + /// IDs already used by the nodes + std::set m_usedIDs; + /// Configured EVM version + langutil::EVMVersion m_evmVersion; +}; + +} diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 52e0db4bc..90dad003a 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -84,6 +84,7 @@ public: virtual bool visit(UnaryOperation& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } virtual bool visit(FunctionCall& _node) { return visitNode(_node); } + virtual bool visit(FunctionCallOptions& _node) { return visitNode(_node); } virtual bool visit(NewExpression& _node) { return visitNode(_node); } virtual bool visit(MemberAccess& _node) { return visitNode(_node); } virtual bool visit(IndexAccess& _node) { return visitNode(_node); } @@ -91,6 +92,7 @@ public: virtual bool visit(Identifier& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); } virtual bool visit(Literal& _node) { return visitNode(_node); } + virtual bool visit(StructuredDocumentation& _node) { return visitNode(_node); } virtual void endVisit(SourceUnit& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective& _node) { endVisitNode(_node); } @@ -134,6 +136,7 @@ public: virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionCallOptions& _node) { endVisitNode(_node); } virtual void endVisit(NewExpression& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); } @@ -141,6 +144,7 @@ public: virtual void endVisit(Identifier& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); } virtual void endVisit(Literal& _node) { endVisitNode(_node); } + virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); } protected: /// Generic function called by default for each node, to be overridden by derived classes @@ -197,6 +201,7 @@ public: virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } virtual bool visit(FunctionCall const& _node) { return visitNode(_node); } + virtual bool visit(FunctionCallOptions const& _node) { return visitNode(_node); } virtual bool visit(NewExpression const& _node) { return visitNode(_node); } virtual bool visit(MemberAccess const& _node) { return visitNode(_node); } virtual bool visit(IndexAccess const& _node) { return visitNode(_node); } @@ -204,6 +209,7 @@ public: virtual bool visit(Identifier const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeNameExpression const& _node) { return visitNode(_node); } virtual bool visit(Literal const& _node) { return visitNode(_node); } + virtual bool visit(StructuredDocumentation const& _node) { return visitNode(_node); } virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); } virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); } @@ -247,6 +253,7 @@ public: virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionCallOptions const& _node) { endVisitNode(_node); } virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); } virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); } virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); } @@ -254,6 +261,7 @@ public: virtual void endVisit(Identifier const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); } virtual void endVisit(Literal const& _node) { endVisitNode(_node); } + virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); } protected: /// Generic function called by default for each node, to be overridden by derived classes diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index ebbce88fb..bc0123d51 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -67,10 +67,24 @@ void ImportDirective::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void StructuredDocumentation::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void StructuredDocumentation::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void ContractDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); listAccept(m_baseContracts, _visitor); listAccept(m_subNodes, _visitor); } @@ -81,6 +95,8 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); listAccept(m_baseContracts, _visitor); listAccept(m_subNodes, _visitor); } @@ -203,6 +219,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); m_parameters->accept(_visitor); @@ -219,6 +237,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); m_parameters->accept(_visitor); @@ -263,6 +283,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); m_parameters->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); @@ -275,6 +297,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) { + if (m_documentation) + m_documentation->accept(_visitor); m_parameters->accept(_visitor); if (m_overrides) m_overrides->accept(_visitor); @@ -308,14 +332,22 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const void EventDefinition::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) + { + if (m_documentation) + m_documentation->accept(_visitor); m_parameters->accept(_visitor); + } _visitor.endVisit(*this); } void EventDefinition::accept(ASTConstVisitor& _visitor) const { if (_visitor.visit(*this)) + { + if (m_documentation) + m_documentation->accept(_visitor); m_parameters->accept(_visitor); + } _visitor.endVisit(*this); } @@ -781,6 +813,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void FunctionCallOptions::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_expression->accept(_visitor); + listAccept(m_options, _visitor); + } + _visitor.endVisit(*this); +} + +void FunctionCallOptions::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_expression->accept(_visitor); + listAccept(m_options, _visitor); + } + _visitor.endVisit(*this); +} + void NewExpression::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/ast/AsmJsonImporter.cpp b/libsolidity/ast/AsmJsonImporter.cpp new file mode 100644 index 000000000..a47ea27f7 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.cpp @@ -0,0 +1,305 @@ +/* + 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 julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +using namespace std; +using namespace solidity::yul; + +namespace solidity::frontend +{ + +using SourceLocation = langutil::SourceLocation; + +SourceLocation const AsmJsonImporter::createSourceLocation(Json::Value const& _node) +{ + astAssert(member(_node, "src").isString(), "'src' must be a string"); + + return solidity::langutil::parseSourceLocation(_node["src"].asString(), m_sourceName); +} + +template +T AsmJsonImporter::createAsmNode(Json::Value const& _node) +{ + T r; + r.location = createSourceLocation(_node); + astAssert( + r.location.source && 0 <= r.location.start && r.location.start <= r.location.end, + "Invalid source location in Asm AST" + ); + return r; +} + +Json::Value AsmJsonImporter::member(Json::Value const& _node, string const& _name) +{ + astAssert(_node.isMember(_name), "Node is missing field '" + _name + "'."); + return _node[_name]; +} + +yul::TypedName AsmJsonImporter::createTypedName(Json::Value const& _node) +{ + auto typedName = createAsmNode(_node); + typedName.type = YulString{member(_node, "type").asString()}; + typedName.name = YulString{member(_node, "name").asString()}; + return typedName; +} + +yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "ExpressionStatement") + return createExpressionStatement(_node); + else if (nodeType == "Assignment") + return createAssignment(_node); + else if (nodeType == "VariableDeclaration") + return createVariableDeclaration(_node); + else if (nodeType == "FunctionDefinition") + return createFunctionDefinition(_node); + else if (nodeType == "If") + return createIf(_node); + else if (nodeType == "Switch") + return createSwitch(_node); + else if (nodeType == "ForLoop") + return createForLoop(_node); + else if (nodeType == "Break") + return createBreak(_node); + else if (nodeType == "Continue") + return createContinue(_node); + else if (nodeType == "Leave") + return createLeave(_node); + else + astAssert(false, "Invalid nodeType as statement"); +} + +yul::Expression AsmJsonImporter::createExpression(Json::Value const& _node) +{ + Json::Value jsonNodeType = member(_node, "nodeType"); + astAssert(jsonNodeType.isString(), "Expected \"nodeType\" to be of type string!"); + string nodeType = jsonNodeType.asString(); + + astAssert(nodeType.substr(0, 3) == "Yul", "Invalid nodeType prefix"); + nodeType = nodeType.substr(3); + + if (nodeType == "FunctionCall") + return createFunctionCall(_node); + else if (nodeType == "Identifier") + return createIdentifier(_node); + else if (nodeType == "Literal") + return createLiteral(_node); + else + astAssert(false, "Invalid nodeType as expression"); +} + +vector AsmJsonImporter::createExpressionVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createExpression(var)); + return ret; +} + +vector AsmJsonImporter::createStatementVector(Json::Value const& _array) +{ + vector ret; + for (auto& var: _array) + ret.emplace_back(createStatement(var)); + return ret; +} + +yul::Block AsmJsonImporter::createBlock(Json::Value const& _node) +{ + auto block = createAsmNode(_node); + block.statements = createStatementVector(_node["statements"]); + return block; +} + +yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node) +{ + auto lit = createAsmNode(_node); + string kind = member(_node, "kind").asString(); + + lit.value = YulString{member(_node, "value").asString()}; + lit.type= YulString{member(_node, "type").asString()}; + + langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")}; + + if (kind == "number") + { + lit.kind = yul::LiteralKind::Number; + astAssert( + scanner.currentToken() == Token::Number, + "Expected number but got " + langutil::TokenTraits::friendlyName(scanner.currentToken()) + string(" while scanning ") + lit.value.str() + ); + } + else if (kind == "bool") + { + lit.kind = yul::LiteralKind::Boolean; + astAssert( + scanner.currentToken() == Token::TrueLiteral || + scanner.currentToken() == Token::FalseLiteral, + "Expected true/false literal!" + ); + } + else if (kind == "string") + { + lit.kind = yul::LiteralKind::String; + astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!"); + } + else + solAssert(false, "unknown type of literal"); + + return lit; +} + +yul::Leave AsmJsonImporter::createLeave(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Identifier AsmJsonImporter::createIdentifier(Json::Value const& _node) +{ + auto identifier = createAsmNode(_node); + identifier.name = YulString(member(_node, "name").asString()); + return identifier; +} + +yul::Assignment AsmJsonImporter::createAssignment(Json::Value const& _node) +{ + auto assignment = createAsmNode(_node); + + if (_node.isMember("variableNames")) + for (auto const& var: member(_node, "variableNames")) + assignment.variableNames.emplace_back(createIdentifier(var)); + + assignment.value = make_unique(createExpression(member(_node, "value"))); + return assignment; +} + +yul::FunctionCall AsmJsonImporter::createFunctionCall(Json::Value const& _node) +{ + auto functionCall = createAsmNode(_node); + + for (auto const& var: member(_node, "arguments")) + functionCall.arguments.emplace_back(createExpression(var)); + + functionCall.functionName = createIdentifier(member(_node, "functionName")); + + return functionCall; +} + +yul::ExpressionStatement AsmJsonImporter::createExpressionStatement(Json::Value const& _node) +{ + auto statement = createAsmNode(_node); + statement.expression = createExpression(member(_node, "expression")); + return statement; +} + +yul::VariableDeclaration AsmJsonImporter::createVariableDeclaration(Json::Value const& _node) +{ + auto varDec = createAsmNode(_node); + for (auto const& var: member(_node, "variables")) + varDec.variables.emplace_back(createTypedName(var)); + varDec.value = make_unique(createExpression(member(_node, "value"))); + return varDec; +} + +yul::FunctionDefinition AsmJsonImporter::createFunctionDefinition(Json::Value const& _node) +{ + auto funcDef = createAsmNode(_node); + funcDef.name = YulString{member(_node, "name").asString()}; + + if (_node.isMember("parameters")) + for (auto const& var: member(_node, "parameters")) + funcDef.parameters.emplace_back(createTypedName(var)); + + if (_node.isMember("returnVariables")) + for (auto const& var: member(_node, "returnVariables")) + funcDef.returnVariables.emplace_back(createTypedName(var)); + + funcDef.body = createBlock(member(_node, "body")); + return funcDef; +} + +yul::If AsmJsonImporter::createIf(Json::Value const& _node) +{ + auto ifStatement = createAsmNode(_node); + ifStatement.condition = make_unique(createExpression(member(_node, "condition"))); + ifStatement.body = createBlock(member(_node, "body")); + return ifStatement; +} + +yul::Case AsmJsonImporter::createCase(Json::Value const& _node) +{ + auto caseStatement = createAsmNode(_node); + caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique(createLiteral(member(_node, "value"))); + caseStatement.body = createBlock(member(_node, "body")); + return caseStatement; +} + +yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node) +{ + auto switchStatement = createAsmNode(_node); + switchStatement.expression = make_unique(createExpression(member(_node, "value"))); + for (auto const& var: member(_node, "cases")) + switchStatement.cases.emplace_back(createCase(var)); + return switchStatement; +} + +yul::ForLoop AsmJsonImporter::createForLoop(Json::Value const& _node) +{ + auto forLoop = createAsmNode(_node); + forLoop.pre = createBlock(member(_node, "pre")); + forLoop.condition = make_unique(createExpression(member(_node, "condition"))); + forLoop.post = createBlock(member(_node, "post")); + forLoop.body = createBlock(member(_node, "body")); + return forLoop; +} + +yul::Break AsmJsonImporter::createBreak(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +yul::Continue AsmJsonImporter::createContinue(Json::Value const& _node) +{ + return createAsmNode(_node); +} + +} diff --git a/libsolidity/ast/AsmJsonImporter.h b/libsolidity/ast/AsmJsonImporter.h new file mode 100644 index 000000000..e7f4821c2 --- /dev/null +++ b/libsolidity/ast/AsmJsonImporter.h @@ -0,0 +1,74 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +/** + * @author julius + * @date 2019 + * Converts an inlineAssembly AST from JSON format to AsmData + */ + +#pragma once + +#include +#include +#include + +namespace solidity::frontend +{ + +/** + * Component that imports an AST from json format to the internal format + */ +class AsmJsonImporter +{ +public: + explicit AsmJsonImporter(std::string _sourceName) : m_sourceName(_sourceName) {} + yul::Block createBlock(Json::Value const& _node); + +private: + langutil::SourceLocation const createSourceLocation(Json::Value const& _node); + template + T createAsmNode(Json::Value const& _node); + /// helper function to access member functions of the JSON + /// and throw an error if it does not exist + Json::Value member(Json::Value const& _node, std::string const& _name); + + yul::Statement createStatement(Json::Value const& _node); + yul::Expression createExpression(Json::Value const& _node); + std::vector createStatementVector(Json::Value const& _array); + std::vector createExpressionVector(Json::Value const& _array); + + yul::TypedName createTypedName(Json::Value const& _node); + yul::Literal createLiteral(Json::Value const& _node); + yul::Leave createLeave(Json::Value const& _node); + yul::Identifier createIdentifier(Json::Value const& _node); + yul::Assignment createAssignment(Json::Value const& _node); + yul::FunctionCall createFunctionCall(Json::Value const& _node); + yul::ExpressionStatement createExpressionStatement(Json::Value const& _node); + yul::VariableDeclaration createVariableDeclaration(Json::Value const& _node); + yul::FunctionDefinition createFunctionDefinition(Json::Value const& _node); + yul::If createIf(Json::Value const& _node); + yul::Case createCase(Json::Value const& _node); + yul::Switch createSwitch(Json::Value const& _node); + yul::ForLoop createForLoop(Json::Value const& _node); + yul::Break createBreak(Json::Value const& _node); + yul::Continue createContinue(Json::Value const& _node); + + std::string m_sourceName; + +}; + +} diff --git a/libsolidity/ast/TypeProvider.cpp b/libsolidity/ast/TypeProvider.cpp index 4cb3e781b..7dd895cbe 100644 --- a/libsolidity/ast/TypeProvider.cpp +++ b/libsolidity/ast/TypeProvider.cpp @@ -459,7 +459,8 @@ FunctionType const* TypeProvider::function( Declaration const* _declaration, bool _gasSet, bool _valueSet, - bool _bound + bool _bound, + bool _saltSet ) { return createAndGet( @@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function( _declaration, _gasSet, _valueSet, - _bound + _bound, + _saltSet ); } diff --git a/libsolidity/ast/TypeProvider.h b/libsolidity/ast/TypeProvider.h index cef182aed..38f2642eb 100644 --- a/libsolidity/ast/TypeProvider.h +++ b/libsolidity/ast/TypeProvider.h @@ -153,7 +153,8 @@ public: Declaration const* _declaration = nullptr, bool _gasSet = false, bool _valueSet = false, - bool _bound = false + bool _bound = false, + bool _saltSet = false ); /// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f3f575a18..c4d4c9fd4 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -151,6 +151,8 @@ util::Result transformParametersToExternal(TypePointers const& _pa void Type::clearCache() const { m_members.clear(); + m_stackItems.reset(); + m_stackSize.reset(); } void StorageOffsets::computeOffsets(TypePointers const& _types) @@ -210,7 +212,7 @@ pair const* MemberList::memberStorageOffset(string const& _name) memberTypes.reserve(m_memberTypes.size()); for (auto const& member: m_memberTypes) memberTypes.push_back(member.type); - m_storageOffsets.reset(new StorageOffsets()); + m_storageOffsets = std::make_unique(); m_storageOffsets->computeOffsets(memberTypes); } for (size_t index = 0; index < m_memberTypes.size(); ++index) @@ -1701,15 +1703,22 @@ u256 ArrayType::storageSize() const return max(1, u256(size)); } -unsigned ArrayType::sizeOnStack() const +vector> ArrayType::makeStackItems() const { - if (m_location == DataLocation::CallData) - // offset [length] (stack top) - return 1 + (isDynamicallySized() ? 1 : 0); - else - // storage slot or memory offset - // byte offset inside storage value is omitted - return 1; + switch (m_location) + { + case DataLocation::CallData: + if (isDynamicallySized()) + return {std::make_tuple("offset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())}; + else + return {std::make_tuple("offset", TypeProvider::uint256())}; + case DataLocation::Memory: + return {std::make_tuple("mpos", TypeProvider::uint256())}; + case DataLocation::Storage: + // byte offset inside storage value is omitted + return {std::make_tuple("slot", TypeProvider::uint256())}; + } + solAssert(false, ""); } string ArrayType::toString(bool _short) const @@ -1891,6 +1900,11 @@ string ArraySliceType::toString(bool _short) const return m_arrayType.toString(_short) + " slice"; } +std::vector> ArraySliceType::makeStackItems() const +{ + return {{"offset", TypeProvider::uint256()}, {"length", TypeProvider::uint256()}}; +} + string ContractType::richIdentifier() const { return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id()); @@ -1989,6 +2003,14 @@ vector> ContractType::stateVar return variablesAndOffsets; } +vector> ContractType::makeStackItems() const +{ + if (m_super) + return {}; + else + return {make_tuple("address", isPayable() ? TypeProvider::payableAddress() : TypeProvider::address())}; +} + void StructType::clearCache() const { Type::clearCache(); @@ -2354,7 +2376,7 @@ string EnumType::canonicalName() const size_t EnumType::numberOfMembers() const { return m_enum.members().size(); -}; +} BoolResult EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { @@ -2422,12 +2444,17 @@ u256 TupleType::storageSize() const solAssert(false, "Storage size of non-storable tuple type requested."); } -unsigned TupleType::sizeOnStack() const +vector> TupleType::makeStackItems() const { - unsigned size = 0; + vector> slots; + unsigned i = 1; for (auto const& t: components()) - size += t ? t->sizeOnStack() : 0; - return size; + { + if (t) + slots.emplace_back("component_" + std::to_string(i), t); + ++i; + } + return slots; } TypePointer TupleType::mobileType() const @@ -2741,6 +2768,8 @@ string FunctionType::richIdentifier() const id += "gas"; if (m_valueSet) id += "value"; + if (m_saltSet) + id += "salt"; if (bound()) id += "bound_to" + identifierList(selfType()); return id; @@ -2881,8 +2910,9 @@ unsigned FunctionType::storageBytes() const solAssert(false, "Storage size of non-storable function type requested."); } -unsigned FunctionType::sizeOnStack() const +vector> FunctionType::makeStackItems() const { + vector> slots; Kind kind = m_kind; if (m_kind == Kind::SetGas || m_kind == Kind::SetValue) { @@ -2890,37 +2920,42 @@ unsigned FunctionType::sizeOnStack() const kind = dynamic_cast(*m_returnParameterTypes.front()).m_kind; } - unsigned size = 0; - switch (kind) { case Kind::External: case Kind::DelegateCall: - size = 2; + slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))}; break; case Kind::BareCall: case Kind::BareCallCode: case Kind::BareDelegateCall: case Kind::BareStaticCall: + case Kind::Transfer: + case Kind::Send: + slots = {make_tuple("address", TypeProvider::address())}; + break; case Kind::Internal: + slots = {make_tuple("functionIdentifier", TypeProvider::uint256())}; + break; case Kind::ArrayPush: case Kind::ArrayPop: case Kind::ByteArrayPush: - case Kind::Transfer: - case Kind::Send: - size = 1; + slots = {make_tuple("slot", TypeProvider::uint256())}; break; default: break; } if (m_gasSet) - size++; + slots.emplace_back("gas", TypeProvider::uint256()); if (m_valueSet) - size++; + slots.emplace_back("value", TypeProvider::uint256()); + if (m_saltSet) + slots.emplace_back("salt", TypeProvider::uint256()); if (bound()) - size += m_parameterTypes.front()->sizeOnStack(); - return size; + for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems()) + slots.emplace_back("self_" + boundName, boundType); + return slots; } FunctionTypePointer FunctionType::interfaceFunctionType() const @@ -2957,12 +2992,30 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const ); } -MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) const +MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _scope) const { switch (m_kind) { case Kind::Declaration: - return {{"selector", TypeProvider::fixedBytes(4)}}; + if (declaration().isPartOfExternalInterface()) + return {{"selector", TypeProvider::fixedBytes(4)}}; + else + return MemberList::MemberMap(); + case Kind::Internal: + if ( + auto const* functionDefinition = dynamic_cast(m_declaration); + functionDefinition && + _scope && + functionDefinition->annotation().contract && + _scope != functionDefinition->annotation().contract && + functionDefinition->isPartOfExternalInterface() + ) + { + solAssert(_scope->derivesFrom(*functionDefinition->annotation().contract), ""); + return {{"selector", TypeProvider::fixedBytes(4)}}; + } + else + return MemberList::MemberMap(); case Kind::External: case Kind::Creation: case Kind::BareCall: @@ -2983,7 +3036,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con "value", TypeProvider::function( parseElementaryTypeVector({"uint"}), - TypePointers{copyAndSetGasOrValue(false, true)}, + TypePointers{copyAndSetCallOptions(false, true, false)}, strings(1, ""), strings(1, ""), Kind::SetValue, @@ -2991,7 +3044,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con StateMutability::Pure, nullptr, m_gasSet, - m_valueSet + m_valueSet, + m_saltSet ) ); } @@ -3000,7 +3054,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con "gas", TypeProvider::function( parseElementaryTypeVector({"uint"}), - TypePointers{copyAndSetGasOrValue(true, false)}, + TypePointers{copyAndSetCallOptions(true, false, false)}, strings(1, ""), strings(1, ""), Kind::SetGas, @@ -3008,7 +3062,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con StateMutability::Pure, nullptr, m_gasSet, - m_valueSet + m_valueSet, + m_saltSet ) ); return members; @@ -3131,7 +3186,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con return false; //@todo this is ugly, but cannot be prevented right now - if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet) + if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet || m_saltSet != _other.m_saltSet) return false; if (bound() != _other.bound()) @@ -3232,7 +3287,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) return pointers; } -TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const +TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const { solAssert(m_kind != Kind::Declaration, ""); return TypeProvider::function( @@ -3246,6 +3301,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_declaration, m_gasSet || _setGas, m_valueSet || _setValue, + m_saltSet || _setSalt, m_bound ); } @@ -3286,6 +3342,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun m_declaration, m_gasSet, m_valueSet, + m_saltSet, _bound ); } @@ -3297,13 +3354,13 @@ Type const* FunctionType::selfType() const return m_parameterTypes.at(0); } -ASTPointer FunctionType::documentation() const +ASTPointer FunctionType::documentation() const { - auto function = dynamic_cast(m_declaration); + auto function = dynamic_cast(m_declaration); if (function) return function->documentation(); - return ASTPointer(); + return ASTPointer(); } bool FunctionType::padArguments() const @@ -3392,12 +3449,12 @@ u256 TypeType::storageSize() const solAssert(false, "Storage size of non-storable type type requested."); } -unsigned TypeType::sizeOnStack() const +vector> TypeType::makeStackItems() const { if (auto contractType = dynamic_cast(m_actualType)) if (contractType->contractDefinition().isLibrary()) - return 1; - return 0; + return {make_tuple("address", TypeProvider::address())}; + return {}; } MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const @@ -3406,36 +3463,20 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current if (m_actualType->category() == Category::Contract) { ContractDefinition const& contract = dynamic_cast(*m_actualType).contractDefinition(); - bool isBase = false; - if (_currentScope != nullptr) + bool inDerivingScope = _currentScope && _currentScope->derivesFrom(contract); + + for (auto const* declaration: contract.declarations()) { - auto const& currentBases = _currentScope->annotation().linearizedBaseContracts; - isBase = (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end()); - } - 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.emplace_back(decl->name(), decl->type(), decl); - } - else - { - bool inLibrary = contract.isLibrary(); - for (FunctionDefinition const* function: contract.definedFunctions()) - if ( - (inLibrary && function->isVisibleAsLibraryMember()) || - (!inLibrary && function->isPartOfExternalInterface()) - ) - members.emplace_back( - function->name(), - FunctionType(*function).asCallableFunction(inLibrary), - function - ); - for (auto const& stru: contract.definedStructs()) - members.emplace_back(stru->name(), stru->type(), stru); - for (auto const& enu: contract.definedEnums()) - members.emplace_back(enu->name(), enu->type(), enu); + if (dynamic_cast(declaration)) + continue; + + if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts()) + members.emplace_back(declaration->name(), declaration->type(), declaration); + else if ( + (contract.isLibrary() && declaration->isVisibleAsLibraryMember()) || + declaration->isVisibleViaContractTypeAccess() + ) + members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration); } } else if (m_actualType->category() == Category::Enum) diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 2c617efdc..5aada86c3 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -259,7 +259,33 @@ public: /// Returns true if the type can be stored as a value (as opposed to a reference) on the stack, /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } - virtual unsigned sizeOnStack() const { return 1; } + /// @returns a list of named and typed stack items that determine the layout of this type on the stack. + /// A stack item either has an empty name and type ``nullptr`` referring to a single stack slot, or + /// has a non-empty name and a valid type referring to the stack layout of that type. + /// The complete layout of a type on the stack can be obtained from its stack items recursively as follows: + /// - Each unnamed stack item is untyped (its type is ``nullptr``) and contributes exactly one stack slot. + /// - Each named stack item is typed and contributes the stack slots given by the stack items of its type. + std::vector> const& stackItems() const + { + if (!m_stackItems) + m_stackItems = makeStackItems(); + return *m_stackItems; + } + /// Total number of stack slots occupied by this type. This is the sum of ``sizeOnStack`` of all ``stackItems()``. + unsigned sizeOnStack() const + { + if (!m_stackSize) + { + size_t sizeOnStack = 0; + for (auto const& slot: stackItems()) + if (std::get<1>(slot)) + sizeOnStack += std::get<1>(slot)->sizeOnStack(); + else + ++sizeOnStack; + m_stackSize = sizeOnStack; + } + return *m_stackSize; + } /// If it is possible to initialize such a value in memory by just writing zeros /// of the size memoryHeadSize(). virtual bool hasSimpleZeroValueInMemory() const { return true; } @@ -336,9 +362,18 @@ protected: { return MemberList::MemberMap(); } + /// Generates the stack items to be returned by ``stackItems()``. Defaults + /// to exactly one unnamed and untyped stack item referring to a single stack slot. + virtual std::vector> makeStackItems() const + { + return {std::make_tuple(std::string(), nullptr)}; + } + /// List of member types (parameterised by scape), will be lazy-initialized. mutable std::map> m_members; + mutable std::optional>> m_stackItems; + mutable std::optional m_stackSize; }; /** @@ -562,7 +597,6 @@ public: bool canBeStored() const override { return false; } bool canLiveOutsideStorage() const override { return false; } - unsigned sizeOnStack() const override { return 0; } std::string toString(bool) const override; TypePointer mobileType() const override; @@ -571,6 +605,8 @@ public: std::string const& value() const { return m_value; } +protected: + std::vector> makeStackItems() const override { return {}; } private: std::string m_value; }; @@ -725,7 +761,6 @@ public: 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; @@ -756,6 +791,8 @@ public: void clearCache() const override; +protected: + std::vector> makeStackItems() const override; private: /// String is interpreted as a subtype of Bytes. enum class ArrayKind { Ordinary, Bytes, String }; @@ -785,7 +822,6 @@ public: bool isDynamicallySized() const override { return true; } bool isDynamicallyEncoded() const override { return true; } bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } - unsigned sizeOnStack() const override { return 2; } std::string toString(bool _short) const override; /// @returns true if this is valid to be stored in calldata @@ -796,6 +832,8 @@ public: std::unique_ptr copyForLocation(DataLocation, bool) const override { solAssert(false, ""); } +protected: + std::vector> makeStackItems() const override; private: ArrayType const& m_arrayType; }; @@ -825,7 +863,6 @@ public: 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; @@ -856,7 +893,8 @@ public: /// @returns a list of all state variables (including inherited) of the contract and their /// offsets in storage. std::vector> stateVariables() const; - +protected: + std::vector> makeStackItems() const override; private: ContractDefinition const& m_contract; /// If true, this is a special "super" type of m_contract containing only members that m_contract inherited @@ -989,7 +1027,6 @@ public: 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. @@ -997,6 +1034,8 @@ public: std::vector const& components() const { return m_components; } +protected: + std::vector> makeStackItems() const override; private: std::vector const m_components; }; @@ -1055,7 +1094,7 @@ public: /// Refers to a function declaration without calling context /// (i.e. when accessed directly via the name of the containing contract). /// Cannot be called. - Declaration + Declaration, }; /// Creates the type of a function. @@ -1098,6 +1137,7 @@ public: Declaration const* _declaration = nullptr, bool _gasSet = false, bool _valueSet = false, + bool _saltSet = false, bool _bound = false ): m_parameterTypes(_parameterTypes), @@ -1110,7 +1150,8 @@ public: m_gasSet(_gasSet), m_valueSet(_valueSet), m_bound(_bound), - m_declaration(_declaration) + m_declaration(_declaration), + m_saltSet(_saltSet) { solAssert( m_parameterNames.size() == m_parameterTypes.size(), @@ -1156,7 +1197,6 @@ public: 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; @@ -1206,9 +1246,9 @@ public: /// Currently, this will only return true for internal functions like keccak and ecrecover. bool isPure() const; bool isPayable() const { return m_stateMutability == StateMutability::Payable; } - /// @return A shared pointer of an ASTString. - /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer documentation() const; + /// @return A shared pointer of StructuredDocumentation. + /// Can contain a nullptr in which case indicates absence of documentation. + ASTPointer documentation() const; /// true iff arguments are to be padded to multiples of 32 bytes for external calls /// The only functions that do not pad are hash functions, the low-level call functions @@ -1235,11 +1275,12 @@ public: bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } + bool saltSet() const { return m_saltSet; } bool bound() const { return m_bound; } /// @returns a copy of this type, where gas or value are set manually. This will never set one /// of the parameters to false. - TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; + TypePointer copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const; /// @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 @@ -1249,6 +1290,8 @@ public: /// @param _bound if true, the function type is set to be bound. FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const; +protected: + std::vector> makeStackItems() const override; private: static TypePointers parseElementaryTypeVector(strings const& _types); @@ -1264,6 +1307,7 @@ private: bool const m_valueSet = false; ///< true iff the value to be sent is on the stack bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn) Declaration const* m_declaration = nullptr; + bool m_saltSet = false; ///< true iff the salt value to be used is on the stack }; /** @@ -1317,12 +1361,13 @@ public: 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; BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override; +protected: + std::vector> makeStackItems() const override; private: TypePointer m_actualType; }; @@ -1342,12 +1387,13 @@ public: 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; +protected: + std::vector> makeStackItems() const override { return {}; } private: TypePointers m_parameterTypes; }; @@ -1370,11 +1416,12 @@ public: 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; +protected: + std::vector> makeStackItems() const override { return {}; } private: SourceUnit const& m_sourceUnit; }; @@ -1409,7 +1456,6 @@ public: 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; @@ -1418,6 +1464,8 @@ public: TypePointer typeArgument() const; +protected: + std::vector> makeStackItems() const override { return {}; } private: Kind m_kind; /// Contract type used for contract metadata magic. @@ -1441,7 +1489,6 @@ public: 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; diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index df8cacf86..0ad044a54 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) Whiskers templ(R"( function (headStart, dataEnd) { - if slt(sub(dataEnd, headStart), ) { revert(0, 0) } + if slt(sub(dataEnd, headStart), ) { } } )"); templ("functionName", functionName); + templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short")); templ("minimumSize", to_string(headSize(decodingTypes))); string decodeElements; @@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) R"( { let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { } := (add(headStart, offset), dataEnd) } )" : @@ -222,6 +223,8 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) } )" ); + // TODO add test + elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset")); elementTempl("load", _fromMemory ? "mload" : "calldataload"); elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); elementTempl("pos", to_string(headPos)); @@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( else templ("scaleLengthByStride", Whiskers(R"( - if gt(length, ) { revert(0, 0) } + if gt(length, ) { } length := mul(length, ) )") ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) + ("revertString", revertReasonIfDebug("ABI encoding: array data too long")) .render() + // TODO add revert test ); templ("readableTypeNameFrom", _from.toString(true)); templ("readableTypeNameTo", _to.toString(true)); @@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from R"( // function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } let length := array := ((length)) let dst := array @@ -1141,6 +1146,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from } )" ); + // TODO add test + templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); @@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from } else { - templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }"); + templ("staticBoundsCheck", "if gt(add(src, mul(length, " + + calldataStride + + ")), end) { " + + revertReasonIfDebug("ABI decoding: invalid calldata array stride") + + " }" + ); templ("retrieveElementPos", "src"); } templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); @@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) templ = R"( // function (offset, end) -> arrayPos, length { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } length := calldataload(offset) - if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { } arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(length, )), end) { revert(0, 0) } + if gt(add(arrayPos, mul(length, )), end) { } } )"; else @@ -1196,10 +1208,14 @@ 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) { } } )"; Whiskers w{templ}; + // TODO add test + w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); + w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length")); + w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride")); w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("stride", toCompactHexWithPrefix(_type.calldataStride())); @@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _ Whiskers templ( R"( function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) } + if iszero(slt(add(offset, 0x1f), end)) { } let length := (offset) array := ((length)) mstore(array, length) let src := add(offset, 0x20) let dst := add(array, 0x20) - if gt(add(src, length), end) { revert(0, 0) } + if gt(add(src, length), end) { } (src, dst, length) } )" ); + // TODO add test + templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset")); + templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length")); templ("functionName", functionName); templ("load", _fromMemory ? "mload" : "calldataload"); templ("allocate", m_utils.allocationFunction()); @@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) Whiskers w{R"( // function (offset, end) -> value { - if slt(sub(end, offset), ) { revert(0, 0) } + if slt(sub(end, offset), ) { } value := offset } )"}; + // TODO add test + w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short")); w("functionName", functionName); w("readableTypeName", _type.toString(true)); w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); @@ -1277,7 +1298,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr Whiskers templ(R"( // function (headStart, end) -> value { - if slt(sub(end, headStart), ) { revert(0, 0) } + if slt(sub(end, headStart), ) { } value := () <#members> { @@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr } )"); + // TODO add test + templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short")); templ("functionName", functionName); templ("readableTypeName", _type.toString(true)); templ("allocate", m_utils.allocationFunction()); @@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr dynamic ? R"( let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { revert(0, 0) } + if gt(offset, 0xffffffffffffffff) { } mstore(add(value, ), (add(headStart, offset), end)) )" : R"( @@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr mstore(add(value, ), (add(headStart, offset), end)) )" ); + // TODO add test + memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset")); memberTempl("load", _fromMemory ? "mload" : "calldataload"); memberTempl("pos", to_string(headPos)); memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); @@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) 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) } + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } value := add(rel_offset_of_tail, base_ref) } @@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) 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()); + if gt(length, 0xffffffffffffffff) { } + if sgt(base_ref, sub(calldatasize(), mul(length, ))) { } + )") + ("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())) + // TODO add test + ("revertStringLength", revertReasonIfDebug("Invalid calldata access length")) + // TODO add test + ("revertStringStride", revertReasonIfDebug("Invalid calldata access stride")) + .render()); w("return", "value, length"); } else @@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type) } w("neededLength", toCompactHexWithPrefix(tailSize)); w("functionName", functionName); + w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset")); return w.render(); } else if (_type.isValueType()) @@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons else return _type.sizeOnStack(); } + +std::string ABIFunctions::revertReasonIfDebug(std::string const& _message) +{ + return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); +} diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index f8f09a3f3..ce3efe6ea 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -25,6 +25,8 @@ #include #include +#include + #include #include @@ -55,11 +57,13 @@ class ABIFunctions public: explicit ABIFunctions( langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, std::shared_ptr _functionCollector = std::make_shared() ): m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_functionCollector(std::move(_functionCollector)), - m_utils(_evmVersion, m_functionCollector) + m_utils(_evmVersion, m_revertStrings, m_functionCollector) {} /// @returns name of an assembly function to ABI-encode values of @a _givenTypes @@ -200,7 +204,7 @@ private: /// @param _fromMemory if decoding from memory instead of from calldata /// @param _forUseOnStack if the decoded value is stored on stack or in memory. std::string abiDecodingFunction( - Type const& _Type, + Type const& _type, bool _fromMemory, bool _forUseOnStack ); @@ -249,7 +253,12 @@ private: /// is true), for which it is two. static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options); + /// @returns code that stores @param _message for revert reason + /// if m_revertStrings is debug. + std::string revertReasonIfDebug(std::string const& _message = ""); + langutil::EVMVersion m_evmVersion; + RevertStrings const m_revertStrings; std::shared_ptr m_functionCollector; std::set m_externallyUsedFunctions; YulUtilFunctions m_utils; diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index a6452adb2..78198fff0 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -35,7 +35,7 @@ void Compiler::compileContract( bytes const& _metadata ) { - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); @@ -45,7 +45,7 @@ void Compiler::compileContract( // 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_revertStrings); + ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimiserSettings); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index e4a8adc6a..e12332015 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -37,9 +37,8 @@ class Compiler public: Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): m_optimiserSettings(std::move(_optimiserSettings)), - m_revertStrings(_revertStrings), - m_runtimeContext(_evmVersion), - m_context(_evmVersion, &m_runtimeContext) + m_runtimeContext(_evmVersion, _revertStrings), + m_context(_evmVersion, _revertStrings, &m_runtimeContext) { } /// Compiles a contract. @@ -65,9 +64,9 @@ public: return m_context.assemblyString(_sourceCodes); } /// @arg _sourceCodes is the map of input files to source code strings - Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const + Json::Value assemblyJSON(std::map const& _indices = std::map()) const { - return m_context.assemblyJSON(_sourceCodes); + return m_context.assemblyJSON(_indices); } /// @returns Assembly items of the normal compiler context evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); } @@ -80,7 +79,6 @@ public: private: OptimiserSettings const m_optimiserSettings; - RevertStrings const m_revertStrings; 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 ef5534a3c..8c1099855 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -37,6 +37,8 @@ #include #include +#include + #include #include #include @@ -55,6 +57,7 @@ using namespace std; using namespace solidity; +using namespace solidity::util; using namespace solidity::evmasm; using namespace solidity::frontend; using namespace solidity::langutil; @@ -296,12 +299,13 @@ CompilerContext& CompilerContext::appendConditionalInvalid() return *this; } -CompilerContext& CompilerContext::appendRevert() +CompilerContext& CompilerContext::appendRevert(string const& _message) { - return *this << u256(0) << u256(0) << Instruction::REVERT; + appendInlineAssembly("{ " + revertReasonIfDebug(_message) + " }"); + return *this; } -CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData) +CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData, string const& _message) { if (_forwardReturnData && m_evmVersion.supportsReturndata()) appendInlineAssembly(R"({ @@ -311,9 +315,7 @@ CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnDat } })", {"condition"}); else - appendInlineAssembly(R"({ - if condition { revert(0, 0) } - })", {"condition"}); + appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"}); *this << Instruction::POP; return *this; } @@ -385,9 +387,9 @@ void CompilerContext::appendInlineAssembly( ErrorReporter errorReporter(errors); 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); + shared_ptr parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); #ifdef SOL_OUTPUT_ASM - cout << yul::AsmPrinter()(*parserResult) << endl; + cout << yul::AsmPrinter(&dialect)(*parserResult) << endl; #endif auto reportError = [&](string const& _context) @@ -421,24 +423,18 @@ void CompilerContext::appendInlineAssembly( // so we essentially only optimize the ABI functions. if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { - 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 - ); + + optimizeYul(obj, dialect, _optimiserSettings, externallyUsedIdentifiers); + analysisInfo = std::move(*obj.analysisInfo); parserResult = std::move(obj.code); #ifdef SOL_OUTPUT_ASM cout << "After optimizer:" << endl; - cout << yul::AsmPrinter()(*parserResult) << endl; + cout << yul::AsmPrinter(&dialect)(*parserResult) << endl; #endif } @@ -460,6 +456,29 @@ void CompilerContext::appendInlineAssembly( updateSourceLocation(); } + +void CompilerContext::optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSettings, std::set const& _externalIdentifiers) +{ +#ifdef SOL_OUTPUT_ASM + cout << yul::AsmPrinter(*dialect)(*_object.code) << endl; +#endif + + bool const isCreation = runtimeContext() != nullptr; + yul::GasMeter meter(_dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); + yul::OptimiserSuite::run( + _dialect, + &meter, + _object, + _optimiserSettings.optimizeStackAllocation, + _externalIdentifiers + ); + +#ifdef SOL_OUTPUT_ASM + cout << "After optimizer:" << endl; + cout << yul::AsmPrinter(*dialect)(*object.code) << endl; +#endif +} + FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector::const_iterator _searchStart @@ -488,6 +507,11 @@ vector::const_iterator CompilerContext::superContract return ++it; } +string CompilerContext::revertReasonIfDebug(string const& _message) +{ + return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); +} + void CompilerContext::updateSourceLocation() { m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 45f16f1f1..c37afbfa5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -27,13 +27,18 @@ #include #include +#include #include #include #include +#include #include #include +#include +#include + #include #include #include @@ -51,11 +56,16 @@ class Compiler; class CompilerContext { public: - explicit CompilerContext(langutil::EVMVersion _evmVersion, CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext( + langutil::EVMVersion _evmVersion, + RevertStrings _revertStrings, + CompilerContext* _runtimeContext = nullptr + ): m_asm(std::make_shared()), m_evmVersion(_evmVersion), + m_revertStrings(_revertStrings), m_runtimeContext(_runtimeContext), - m_abiFunctions(m_evmVersion) + m_abiFunctions(m_evmVersion, m_revertStrings) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); @@ -160,12 +170,14 @@ public: /// Appends a conditional INVALID instruction CompilerContext& appendConditionalInvalid(); /// Appends a REVERT(0, 0) call - CompilerContext& appendRevert(); + /// @param _message is an optional revert message used in debug mode + CompilerContext& appendRevert(std::string const& _message = ""); /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the /// empty string. Consumes the condition. /// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward /// the data. - CompilerContext& appendConditionalRevert(bool _forwardReturnData = false); + /// @param _message is an optional revert message used in debug mode + CompilerContext& appendConditionalRevert(bool _forwardReturnData = false, std::string const& _message = ""); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo( evmasm::AssemblyItem const& _tag, @@ -219,6 +231,13 @@ public: OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() ); + /// If m_revertStrings is debug, @returns inline assembly code that + /// stores @param _message in memory position 0 and reverts. + /// Otherwise returns "revert(0, 0)". + std::string revertReasonIfDebug(std::string const& _message = ""); + + void optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSetting, std::set const& _externalIdentifiers = {}); + /// Appends arbitrary data to the end of the bytecode. void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } @@ -243,9 +262,9 @@ public: } /// @arg _sourceCodes is the map of input files to source code strings - Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const + Json::Value assemblyJSON(std::map const& _indicies = std::map()) const { - return m_asm->assemblyJSON(_sourceCodes); + return m_asm->assemblyJSON(_indicies); } evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); } @@ -263,6 +282,8 @@ public: void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; } + RevertStrings revertStrings() const { return m_revertStrings; } + private: /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns /// the first function definition that is overwritten by _function. @@ -312,6 +333,7 @@ private: evmasm::AssemblyPointer m_asm; /// Version of the EVM to compile against. langutil::EVMVersion m_evmVersion; + RevertStrings const m_revertStrings; /// Activated experimental features. std::set m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index f9a0fbcec..4c75081ff 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -130,9 +130,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type) // 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) } + if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } base_ref := add(base_ref, rel_offset_of_tail) - })")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"}); + })") + ("neededLength", toCompactHexWithPrefix(tailSize)) + ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset")) + .render(), {"base_ref", "ptr_to_tail"}); // stack layout: if (!_type.isDynamicallySized()) @@ -158,9 +161,12 @@ void CompilerUtils::accessCalldataTail(Type const& _type) Whiskers(R"({ length := calldataload(base_ref) base_ref := add(base_ref, 0x20) - if gt(length, 0xffffffffffffffff) { revert(0, 0) } + if gt(length, 0xffffffffffffffff) { } if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } - })")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(), + })") + ("calldataStride", toCompactHexWithPrefix(calldataStride)) + ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length")) + .render(), {"base_ref", "length"} ); // stack layout: @@ -277,7 +283,13 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem size_t encodedSize = 0; for (auto const& t: _typeParameters) encodedSize += t->decodingType()->calldataHeadSize(); - m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"}); + + Whiskers templ(R"({ + if lt(len, ) { } + })"); + templ("encodedSize", to_string(encodedSize)); + templ("revertString", m_context.revertReasonIfDebug("Calldata too short")); + m_context.appendInlineAssembly(templ.render(), {"len"}); m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::SWAP1; @@ -319,19 +331,23 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem // Check that the data pointer is valid and that length times // item size is still inside the range. Whiskers templ(R"({ - if gt(ptr, 0x100000000) { revert(0, 0) } + if gt(ptr, 0x100000000) { } ptr := add(ptr, base_offset) let array_data_start := add(ptr, 0x20) - if gt(array_data_start, input_end) { revert(0, 0) } + if gt(array_data_start, input_end) { } let array_length := mload(ptr) if or( 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.calldataStride())); + // TODO add test + templ("revertStringPointer", m_context.revertReasonIfDebug("ABI memory decoding: invalid data pointer")); + templ("revertStringStart", m_context.revertReasonIfDebug("ABI memory decoding: invalid data start")); + templ("revertStringLength", m_context.revertReasonIfDebug("ABI memory decoding: invalid data length")); 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; @@ -359,24 +375,33 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem 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"}); + m_context.appendInlineAssembly(Whiskers(R"({ + if gt(data_offset, 0x100000000) { } + })") + // TODO add test + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data offset")) + .render(), {"data_offset"}); m_context << Instruction::DUP3 << Instruction::ADD; // stack: input_end base_offset next_pointer array_head_ptr - m_context.appendInlineAssembly( - "{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }", - {"input_end", "base_offset", "next_ptr", "array_head_ptr"} - ); + m_context.appendInlineAssembly(Whiskers(R"({ + if gt(add(array_head_ptr, 0x20), input_end) { } + })") + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer")) + .render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"}); + // retrieve length 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 - m_context.appendInlineAssembly(R"({ + m_context.appendInlineAssembly(Whiskers(R"({ if or( gt(array_length, 0x100000000), 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"}); + ) { } + })") + ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer")) + .render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); } else { @@ -605,7 +630,7 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) Whiskers templ(R"({ let size := mul(length, ) // cheap way of zero-initializing a memory range - codecopy(memptr, codesize(), size) + calldatacopy(memptr, calldatasize(), size) memptr := add(memptr, size) })"); templ("element_size", to_string(_type.memoryStride())); @@ -792,8 +817,7 @@ void CompilerUtils::convertType( solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; if (_asPartOfArgumentDecoding) - // TODO: error message? - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Enum out of range"); else m_context.appendConditionalInvalid(); enumOverflowCheckPending = false; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 5cc0ac9c8..3d5d85825 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -34,7 +35,8 @@ class Type; // forward class CompilerUtils { public: - explicit CompilerUtils(CompilerContext& _context): m_context(_context) {} + explicit CompilerUtils(CompilerContext& _context): m_context(_context) + {} /// Stores the initial value of the free-memory-pointer at its position; void initialiseFreeMemoryPointer(); diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ecf62d29a..a99aefb5f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -20,6 +20,7 @@ * Solidity compiler. */ + #include #include #include @@ -27,7 +28,16 @@ #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include @@ -38,6 +48,7 @@ #include #include + #include using namespace std; @@ -128,8 +139,7 @@ void ContractCompiler::appendCallValueCheck() { // Throw if function is not payable but call contained ether. m_context << Instruction::CALLVALUE; - // TODO: error message? - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Ether sent to non-payable function"); } void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) @@ -409,7 +419,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << notFoundOrReceiveEther; if (!fallback && !etherReceiver) - m_context.appendRevert(); + m_context.appendRevert("Contract does not have fallback nor receive functions"); else { if (etherReceiver) @@ -440,8 +450,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac m_context << Instruction::STOP; } else - // TODO: error message here? - m_context.appendRevert(); + m_context.appendRevert("Unknown signature and no fallback defined"); } @@ -457,7 +466,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // If the function is not a view function and is called without DELEGATECALL, // we revert. m_context << dupInstruction(2); - m_context.appendConditionalRevert(); + m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL"); } m_context.setStackOffset(0); // We have to allow this for libraries, because value of the previous @@ -517,7 +526,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_revertStrings, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); } bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) @@ -530,10 +539,10 @@ bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) m_continueTags.clear(); if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) .appendConstStateVariableAccessor(_variableDeclaration); else - ExpressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals) + ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) .appendStateVariableAccessor(_variableDeclaration); return false; @@ -772,7 +781,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) else { // lvalue context - solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); + solAssert(!ref->second.isOffset, ""); auto variable = dynamic_cast(decl); solAssert( !!variable && m_context.isLocalVariable(variable), @@ -790,10 +799,36 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) _assembly.appendInstruction(Instruction::POP); } }; - solAssert(_inlineAssembly.annotation().analysisInfo, ""); + + yul::Block const* code = &_inlineAssembly.operations(); + yul::AsmAnalysisInfo* analysisInfo = _inlineAssembly.annotation().analysisInfo.get(); + + // Only used in the scope below, but required to live outside to keep the + // shared_ptr's alive + yul::Object object = {}; + + // The optimiser cannot handle external references + if ( + m_optimiserSettings.runYulOptimiser && + _inlineAssembly.annotation().externalReferences.empty() + ) + { + yul::EVMDialect const* dialect = dynamic_cast(&_inlineAssembly.dialect()); + solAssert(dialect, ""); + + // Create a modifiable copy of the code and analysis + object.code = make_shared(yul::ASTCopier().translate(*code)); + object.analysisInfo = make_shared(yul::AsmAnalyzer::analyzeStrictAssertCorrect(*dialect, object)); + + m_context.optimizeYul(object, *dialect, m_optimiserSettings); + + code = object.code.get(); + analysisInfo = object.analysisInfo.get(); + } + yul::CodeGenerator::assemble( - _inlineAssembly.operations(), - *_inlineAssembly.annotation().analysisInfo, + *code, + *analysisInfo, *m_context.assemblyPtr(), m_context.evmVersion(), identifierAccess, @@ -954,7 +989,8 @@ void ContractCompiler::handleCatch(vector> const& _ca revert(0, returndatasize()) })"); else - m_context.appendRevert(); + // Since both returndata and revert are >=byzantium, this should be unreachable. + solAssert(false, ""); } m_context << endTag; } @@ -1316,7 +1352,7 @@ void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration con void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { - ExpressionCompiler expressionCompiler(m_context, m_revertStrings, m_optimiserSettings.runOrderLiterals); + 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 02fcbe4ca..0a2ecf7e8 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -43,11 +43,9 @@ public: explicit ContractCompiler( ContractCompiler* _runtimeCompiler, CompilerContext& _context, - OptimiserSettings _optimiserSettings, - RevertStrings _revertStrings + OptimiserSettings _optimiserSettings ): m_optimiserSettings(std::move(_optimiserSettings)), - m_revertStrings(_revertStrings), m_runtimeCompiler(_runtimeCompiler), m_context(_context) { @@ -140,7 +138,6 @@ private: void storeStackHeight(ASTNode const* _node); OptimiserSettings const m_optimiserSettings; - RevertStrings const m_revertStrings; /// 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 2e1ef44dc..0fc24d919 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -620,6 +620,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); + // Stack: [salt], [value] + solAssert(!function.gasSet(), "Gas limit set for contract creation."); solAssert(function.returnParameterTypes().size() == 1, ""); TypePointers argumentTypes; @@ -633,16 +635,37 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().fetchFreeMemoryPointer(); utils().copyContractCodeToMemory(*contract, true); utils().abiEncode(argumentTypes, function.parameterTypes()); - // now on stack: memory_end_ptr - // need: size, offset, endowment + // now on stack: [salt], [value], memory_end_ptr + // need: [salt], size, offset, value + + if (function.saltSet()) + { + m_context << dupInstruction(2 + (function.valueSet() ? 1 : 0)); + m_context << Instruction::SWAP1; + } + + // now: [salt], [value], [salt], memory_end_ptr utils().toSizeAfterFreeMemoryPointer(); + + // now: [salt], [value], [salt], size, offset if (function.valueSet()) - m_context << dupInstruction(3); + m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0)); else m_context << u256(0); - m_context << Instruction::CREATE; + + // now: [salt], [value], [salt], size, offset, value + if (function.saltSet()) + m_context << Instruction::CREATE2; + else + m_context << Instruction::CREATE; + + // now: [salt], [value], address + if (function.valueSet()) m_context << swapInstruction(1) << Instruction::POP; + if (function.saltSet()) + m_context << swapInstruction(1) << Instruction::POP; + // Check if zero (reverted) m_context << Instruction::DUP1 << Instruction::ISZERO; if (_functionCall.annotation().tryCall) @@ -654,7 +677,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << errorCase; } else - // TODO: Can we bubble up here? There might be different reasons for failure, I think. m_context.appendConditionalRevert(true); break; } @@ -711,8 +733,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) if (function.kind() == FunctionType::Kind::Transfer) { // Check if zero (out of stack or not enough balance). - // TODO: bubble up here, but might also be different error. m_context << Instruction::ISZERO; + // Revert message bubbles up. m_context.appendConditionalRevert(true); } break; @@ -729,7 +751,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function-sel(Error(string)) + encoding solAssert(arguments.size() == 1, ""); solAssert(function.parameterTypes().size() == 1, ""); - if (m_revertStrings == RevertStrings::Strip) + if (m_context.revertStrings() == RevertStrings::Strip) { if (!arguments.front()->annotation().isPure) { @@ -1009,7 +1031,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); - bool haveReasonString = arguments.size() > 1 && m_revertStrings != RevertStrings::Strip; + bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip; if (arguments.size() > 1) { @@ -1018,7 +1040,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // function call. solAssert(arguments.size() == 2, ""); solAssert(function.kind() == FunctionType::Kind::Require, ""); - if (m_revertStrings == RevertStrings::Strip) + if (m_context.revertStrings() == RevertStrings::Strip) { if (!arguments.at(1)->annotation().isPure) { @@ -1192,6 +1214,49 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) return false; } +bool ExpressionCompiler::visit(FunctionCallOptions const& _functionCallOptions) +{ + _functionCallOptions.expression().accept(*this); + + // Desired Stack: [salt], [gas], [value] + enum Option { Salt, Gas, Value }; + + vector