Merge remote-tracking branch 'origin/develop' into HEAD

This commit is contained in:
chriseth 2020-02-27 14:54:00 +01:00
commit 06ad5b3200
671 changed files with 19686 additions and 9996 deletions

View File

@ -11,7 +11,7 @@ docker build -t ethereum/solidity-buildpack-deps:ubuntu1904-<revision> -f Docker
docker push ethereum/solidity-buildpack-deps:ubuntu1904-<revision>
```
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 (`<revision>` 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 `<image-desc>-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 (`<revision>` 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:

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>
#
# (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

View File

@ -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 \

1
.gitignore vendored
View File

@ -40,6 +40,7 @@ docs/utils/*.pyc
/deps/downloads/
deps/install
deps/cache
cmake-build-debug/
# vim stuff
[._]*.sw[a-p]

View File

@ -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)

View File

@ -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:

View File

@ -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.

View File

@ -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%

View File

@ -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")

View File

@ -41,7 +41,6 @@ if (SUPPORT_TOOLS)
endif()
message("------------------------------------------------------------------ flags")
message("-- OSSFUZZ ${OSSFUZZ}")
message("-- LLL ${LLL}")
message("------------------------------------------------------------------------")
message("")
endmacro()

View File

@ -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)

View File

@ -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 {

View File

@ -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 <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 <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(<num_bytes_of_x_minus_one>, 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]+

View File

@ -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"
}
}

View File

@ -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

View File

@ -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");
}

View File

@ -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.

View File

@ -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.
sure the pragma version specifies this version as a minimum.

View File

@ -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.

View File

@ -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 <data-location-assignment>` 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.

View File

@ -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

View File

@ -104,4 +104,3 @@ Contents
common-patterns.rst
bugs.rst
contributing.rst
lll.rst

View File

@ -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 <https://overlays.gentoo.org/#ethereum>`_ that contains a solidity package.
After the overlay is setup, ``solc`` can be installed in x86_64 architectures by:
.. code-block:: bash

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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 <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised

View File

@ -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 <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 <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 <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 <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 <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}()
}
}

View File

@ -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 <upgrade-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 <https://github.com/ethereum/solidity/issues/new/choose>`_ 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 <upgrade-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 {}
}

File diff suppressed because it is too large Load Diff

View File

@ -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<string, unsigned> 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);
}
}

View File

@ -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 <class T> 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<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
) 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:

View File

@ -19,12 +19,14 @@
#include <libsolutil/CommonData.h>
#include <libsolutil/FixedHash.h>
#include <liblangutil/SourceLocation.h>
#include <fstream>
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<string, unsigned> 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;
}

View File

@ -48,6 +48,8 @@ enum AssemblyItemType {
};
class Assembly;
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
class AssemblyItem
{
@ -122,6 +124,11 @@ public:
}
bool operator!=(Instruction _instr) const { return !operator==(_instr); }
static std::string computeSourceMapping(
AssemblyItems const& _items,
std::map<std::string, unsigned> 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<u256> m_pushedValue;
};
using AssemblyItems = std::vector<AssemblyItem>;
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
{
size_t size = 0;

View File

@ -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 <class _AssemblyItemIterator>
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant);
template <class AssemblyItemIterator>
AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant);
/// @returns the resulting items after optimization.
AssemblyItems getOptimizedItems();
@ -169,10 +169,10 @@ private:
std::map<int, Id> m_targetStack;
};
template <class _AssemblyItemIterator>
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _iterator,
_AssemblyItemIterator _end,
template <class AssemblyItemIterator>
AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
AssemblyItemIterator _iterator,
AssemblyItemIterator _end,
bool _msizeImportant
)
{

View File

@ -19,8 +19,6 @@
#include <libevmasm/KnownState.h>
#include <libsolutil/FixedHash.h>
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;

View File

@ -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 <class _Mapping> void intersect(_Mapping& _this, _Mapping const& _other)
template <class Mapping> 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)

View File

@ -65,7 +65,7 @@ struct EVMBuiltins
template<typename... Args> constexpr Pattern operator()(Args&&... _args) const
{
return {inst, {std::forward<Args>(_args)...}};
};
}
};
struct PatternGeneratorInstance
@ -74,7 +74,7 @@ struct EVMBuiltins
template<typename... Args> constexpr Pattern operator()(Args&&... _args) const
{
return {instruction, {std::forward<Args>(_args)...}};
};
}
};

View File

@ -16,6 +16,7 @@ set(sources
SemVerHandler.cpp
SemVerHandler.h
SourceLocation.h
SourceLocation.cpp
SourceReferenceExtractor.cpp
SourceReferenceExtractor.h
SourceReferenceFormatter.cpp

View File

@ -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<int, int> CharStream::translatePositionToLineColumn(int _position) const

View File

@ -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
);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -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<Token, unsigned, unsigned> 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

View File

@ -98,6 +98,7 @@ public:
std::string const& source() const noexcept { return m_source->source(); }
std::shared_ptr<CharStream> charStream() noexcept { return m_source; }
std::shared_ptr<CharStream const> 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<unsigned, unsigned> 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<unsigned, unsigned> 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<int, int> 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<CharStream> m_source;

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <liblangutil/Exceptions.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
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<std::string> pos;
boost::algorithm::split(pos, _input, boost::is_any_of(":"));
astAssert(
pos.size() == 3 &&
_maxIndex >= static_cast<size_t>(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<langutil::CharStream> source = std::make_shared<langutil::CharStream>("", _sourceName);
return SourceLocation{start, end, source};
}
}

View File

@ -23,13 +23,12 @@
#pragma once
#include <libsolutil/Assertions.h>
#include <libsolutil/Common.h> // defines noexcept macro for MSVC
#include <libsolutil/Exceptions.h>
#include <liblangutil/CharStream.h>
#include <memory>
#include <string>
#include <ostream>
#include <tuple>
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<CharStream> 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;
}
}

View File

@ -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<CharStream> const& source = _location->source;

View File

@ -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)

View File

@ -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<int>(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)

View File

@ -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)

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file CodeFragment.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include <liblll/CodeFragment.h>
#include <liblll/CompilerState.h>
#include <liblll/Parser.h>
#include <libevmasm/Instruction.h>
#include <libsolutil/CommonIO.h>
#include <boost/algorithm/string.hpp>
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif // defined(__GNUC__)
#include <boost/spirit/include/support_utree.hpp>
#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::basic_string<boost::iterator_range<char const*>, 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::basic_string<boost::iterator_range<char const*>, 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<InvalidName>(std::string("Symbol not found: ") + s);
m_asm.append((u256)it->second.first);
}
else
error<BareSymbol>(s);
break;
}
case sp::utree_type::any_type:
{
bigint i = *_t.get<bigint*>();
if (i < 0 || i > bigint(u256(0) - 1))
error<IntegerOutOfRange>(toString(i));
m_asm.append((u256)i);
break;
}
default:
error<CompilerException>("Unexpected fragment type");
break;
}
}
void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
if (_t.tag() == 0 && _t.empty())
error<EmptyList>();
else if (_t.tag() == 0 && _t.front().which() != sp::utree_type::symbol_type)
error<DataNotExecutable>();
else
{
string s;
string us;
switch (_t.tag())
{
case 0:
{
auto sr = _t.front().get<sp::basic_string<boost::iterator_range<char const*>, 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<InvalidName>(toString(i));
if (i.which() == sp::utree_type::string_type)
{
auto sr = i.get<sp::basic_string<boost::iterator_range<char const*>, 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::basic_string<boost::iterator_range<char const*>, 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<InvalidName>("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<InvalidName>(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<IncorrectParameterCount>("The assembly instruction resulted in stack underflow");
m_asm.append(fragment);
}
}
else if (us == "INCLUDE")
{
if (_t.size() != 2)
error<IncorrectParameterCount>(us);
string fileName = firstAsString();
if (fileName.empty())
error<InvalidName>("Empty file name provided");
if (!m_readFile)
error<InvalidName>("Import callback not present");
string contents = m_readFile(fileName);
if (contents.empty())
error<InvalidName>(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<IncorrectParameterCount>(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<IncorrectParameterCount>();
auto it = _s.vars.find(firstAsString());
if (it != _s.vars.end())
_s.vars.erase(it);
}
else if (us == "GET")
{
if (_t.size() != 2)
error<IncorrectParameterCount>(us);
m_asm.append((u256)varAddress(firstAsString()));
m_asm.append(Instruction::MLOAD);
}
else if (us == "WITH")
{
if (_t.size() != 4)
error<IncorrectParameterCount>();
string key = firstAsString();
if (_s.vars.find(key) != _s.vars.end())
error<InvalidName>(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<IncorrectParameterCount>(us);
vector<string> args;
for (auto const& i: _t)
{
if (ii == 1)
{
if (i.tag())
error<InvalidName>(toString(i));
if (i.which() == sp::utree_type::string_type)
{
auto sr = i.get<sp::basic_string<boost::iterator_range<char const*>, 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::basic_string<boost::iterator_range<char const*>, 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<InvalidMacroArgs>();
auto sr = j.get<sp::basic_string<boost::iterator_range<char const*>, 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<IncorrectParameterCount>(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<InvalidDeposit>(toString(i));
}
else if (i.tag() != 0)
{
error<InvalidLiteral>(toString(i));
}
else if (i.which() == sp::utree_type::string_type)
{
auto sr = i.get<sp::basic_string<boost::iterator_range<char const*>, 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<bigint*>();
if (bi < 0)
error<IntegerOutOfRange>(toString(i));
else
{
bytes tmp = toCompactBigEndian(bi);
data.insert(data.end(), tmp.begin(), tmp.end());
}
}
else
{
error<InvalidLiteral>(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<std::string, Instruction> const c_arith = {
{ "+", Instruction::ADD },
{ "-", Instruction::SUB },
{ "*", Instruction::MUL },
{ "/", Instruction::DIV },
{ "%", Instruction::MOD },
{ "&", Instruction::AND },
{ "|", Instruction::OR },
{ "^", Instruction::XOR }
};
std::map<std::string, pair<Instruction, bool>> 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<std::string, Instruction> const c_unary = {
{ "!", Instruction::ISZERO },
{ "~", Instruction::NOT }
};
vector<CodeFragment> 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<IncorrectParameterCount>(us); };
auto requireMinSize = [&](unsigned s) { if (code.size() < s) error<IncorrectParameterCount>(us); };
auto requireMaxSize = [&](unsigned s) { if (code.size() > s) error<IncorrectParameterCount>(us); };
auto requireDeposit = [&](unsigned i, int s) { if (code[i].m_asm.deposit() != s) error<InvalidDeposit>(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<InvalidDeposit>(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<evmasm::Assembly>(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<InvalidOperation>("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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file CodeFragment.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <liblll/Exceptions.h>
#include <libevmasm/Instruction.h>
#include <libevmasm/Assembly.h>
#include <libsolutil/Common.h>
namespace boost { namespace spirit { class utree; } }
namespace sp = boost::spirit;
namespace solidity::lll
{
struct CompilerState;
class CodeFragment
{
public:
using ReadCallback = std::function<std::string(std::string const&)>;
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 <class T> static void error() { BOOST_THROW_EXCEPTION(T() ); }
template <class T> 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;
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Compiler.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include <liblll/Compiler.h>
#include <liblll/Parser.h>
#include <liblll/CompilerState.h>
#include <liblll/CodeFragment.h>
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<std::string>* _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<std::string>* _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();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Compiler.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <libsolutil/Common.h>
#include <liblangutil/EVMVersion.h>
#include <string>
#include <vector>
namespace solidity::lll
{
using ReadCallback = std::function<std::string(std::string const&)>;
std::string parseLLL(std::string _src);
std::string compileLLLToAsm(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
bytes compileLLL(std::string _src, langutil::EVMVersion _evmVersion, bool _opt = true, std::vector<std::string>* _errors = nullptr, ReadCallback const& _readFile = ReadCallback());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file CompilerState.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include <liblll/CompilerState.h>
#include <liblll/CodeFragment.h>
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());
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file CompilerState.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <liblll/CodeFragment.h>
#include <boost/spirit/include/support_utree.hpp>
namespace solidity::lll
{
struct Macro
{
std::vector<std::string> args;
boost::spirit::utree code;
std::map<std::string, CodeFragment> env;
};
struct CompilerState
{
CompilerState();
CodeFragment const& getDef(std::string const& _s) const;
void populateStandard();
unsigned stackSize = 128;
std::map<std::string, std::pair<unsigned, unsigned>> vars; ///< maps name to stack offset & size.
std::map<std::string, CodeFragment> defs;
std::map<std::string, CodeFragment> args;
std::map<std::string, CodeFragment> outers;
std::map<std::pair<std::string, unsigned>, Macro> macros;
std::vector<boost::spirit::utree> treesToKill;
bool usedAlloc = false;
};
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Exceptions.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <libsolutil/Exceptions.h>
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 {};
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Parser.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include <liblll/Parser.h>
#if _MSC_VER
#pragma warning(disable:4348)
#endif
#define BOOST_RESULT_OF_USE_DECLTYPE
#define BOOST_SPIRIT_USE_PHOENIX_V3
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/support_utree.hpp>
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<bigint*>(); 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<int>(); break;
case sp::utree_type::string_type: _out << "\"" << _this.get<sp::basic_string<boost::iterator_range<char const*>, sp::utree_type::string_type>>() << "\""; break;
case sp::utree_type::symbol_type: _out << _this.get<sp::basic_string<boost::iterator_range<char const*>, sp::utree_type::symbol_type>>(); break;
case sp::utree_type::any_type: _out << *_this.get<bigint*>(); break;
default: _out << "nil";
}
}
namespace solidity {
namespace lll {
namespace parseTreeLLL_ {
template<unsigned N>
struct tagNode
{
void operator()(sp::utree& n, qi::rule<string::const_iterator, qi::ascii::space_type, sp::utree()>::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<std::string, sp::utree_type::symbol_type>;
using it = string::const_iterator;
qi::rule<it, space_type, sp::utree()> element;
qi::rule<it, string()> str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"';
qi::rule<it, string()> strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))];
qi::rule<it, symbol_type()> symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))];
qi::rule<it, string()> 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<it, sp::utree()> integer = intstr[qi::_val = px::construct<sp::any_ptr>(px::new_<bigint>(qi::_1))];
qi::rule<it, space_type, sp::utree()> atom = integer[qi::_val = qi::_1] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1];
qi::rule<it, space_type, sp::utree::list_type()> seq = '{' > *element > '}';
qi::rule<it, space_type, sp::utree::list_type()> mload = '@' > element;
qi::rule<it, space_type, sp::utree::list_type()> sload = qi::lit("@@") > element;
qi::rule<it, space_type, sp::utree::list_type()> mstore = '[' > element > ']' > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element;
qi::rule<it, space_type, sp::utree::list_type()> calldataload = qi::lit("$") > element;
qi::rule<it, space_type, sp::utree::list_type()> list = '(' > *element > ')';
qi::rule<it, space_type, sp::utree()> 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<it> 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"));
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @file Parser.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <liblll/Exceptions.h>
#include <libsolutil/Common.h>
#include <string>
#include <vector>
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);
}

View File

@ -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

View File

@ -163,7 +163,7 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
std::set<SourceLocation> unreachable;
util::BreadthFirstSearch<CFGNode const*>{{_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);

View File

@ -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<CFGNode>());
return m_nodes.back().get();
}

View File

@ -73,7 +73,8 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
void DocStringAnalyser::checkParameters(
CallableDeclaration const& _callable,
DocumentedAnnotation& _annotation
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
)
{
set<string> 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<string> const validTags = set<string>{"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<string> const validTags = set<string>{"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<string> 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);
}

View File

@ -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<std::string> 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;

View File

@ -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<shared_ptr<MagicVariableDeclaration const>> constructMagicVariables()
{
static auto const magicVarDecl = [](string const& _name, Type const* _type) {
return make_shared<MagicVariableDeclaration>(_name, _type);
return make_shared<MagicVariableDeclaration>(magicVariableToID(_name), _name, _type);
};
return {
@ -97,7 +131,7 @@ vector<Declaration const*> GlobalContext::declarations() const
MagicVariableDeclaration const* GlobalContext::currentThis() const
{
if (!m_thisPointer[m_currentContract])
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>("this", TypeProvider::contract(*m_currentContract));
m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>(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<MagicVariableDeclaration>("super", TypeProvider::contract(*m_currentContract, true));
m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>(magicVariableToID("super"), "super", TypeProvider::contract(*m_currentContract, true));
return m_superPointer[m_currentContract].get();
}

View File

@ -405,13 +405,13 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
}
template <class _T>
vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge)
template <class T>
vector<T const*> NameAndTypeResolver::cThreeMerge(list<list<T const*>>& _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<T const*> 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<list<_T const*>>& _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<T const*> const& bases: _toMerge)
{
solAssert(!bases.empty(), "");
if (appearsOnlyAtHead(bases.front()))
@ -431,7 +431,7 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _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<list<_T const*>>& _toMer
}
};
_toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); });
vector<_T const*> result;
_toMerge.remove_if([](list<T const*> const& _bases) { return _bases.empty(); });
vector<T const*> result;
while (!_toMerge.empty())
{
_T const* candidate = nextCandidate();
T const* candidate = nextCandidate();
if (!candidate)
return vector<_T const*>();
return vector<T const*>();
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);
}

View File

@ -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 <class _T>
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
template <class T>
static std::vector<T const*> cThreeMerge(std::list<std::list<T const*>>& _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

View File

@ -144,8 +144,8 @@ vector<ContractDefinition const*> resolveDirectBaseContracts(ContractDefinition
Declaration const* baseDecl =
specifier->name().annotation().referencedDeclaration;
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
solAssert(contract, "contract is null");
resolvedContracts.emplace_back(contract);
if (contract)
resolvedContracts.emplace_back(contract);
}
return resolvedContracts;

View File

@ -129,6 +129,7 @@ private:
class OverrideChecker
{
public:
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
/// @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<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
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().

View File

@ -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);
}

View File

@ -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<UserDefinedTypeName const*>(&_mapping.keyType()))
{
if (auto const* contractType = dynamic_cast<ContractType const*>(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<ElementaryTypeName const*>(&_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<VariableDeclaration const*>(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<VariableDeclaration const*>(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;

View File

@ -25,6 +25,7 @@
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <liblangutil/EVMVersion.h>
#include <libyul/optimiser/ASTWalker.h>
#include <boost/noncopyable.hpp>
#include <list>
@ -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<ParameterList const*> m_returnParameters;
bool const m_resolveInsideCode;
bool m_errorOccurred = false;
InlineAssemblyAnnotation* m_yulAnnotation = nullptr;
bool m_yulInsideFunction = false;
};
}

View File

@ -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;
}

View File

@ -237,8 +237,8 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
auto base = dynamic_cast<ContractDefinition const*>(&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<FunctionDefinition const*>(&_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<FunctionType const*>(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<decltype(typeType)>(exprType))
{
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
{
annotation.isLValue = annotation.referencedDeclaration->isLValue();
if (
auto const* functionType = dynamic_cast<FunctionType const*>(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<TypeType const*>(exprType))
if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true;
if (
auto const* functionType = dynamic_cast<FunctionType const*>(exprType);
functionType &&
functionType->hasDeclaration() &&
dynamic_cast<FunctionDefinition const*>(&functionType->declaration()) &&
memberName == "selector"
)
if (auto const* parentAccess = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
{
annotation.isPure = parentAccess->expression().annotation().isPure;
if (auto const* exprInt = dynamic_cast<Identifier const*>(&parentAccess->expression()))
if (exprInt->name() == "this" || exprInt->name() == "super")
annotation.isPure = true;
}
if (auto magicType = dynamic_cast<MagicType const*>(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<VariableDeclaration const*>(annotation.referencedDeclaration))
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))

View File

@ -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;

View File

@ -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<VariableDeclaration const*> ContractDefinition::stateVariablesIncludingIn
return stateVars;
}
bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
{
return util::contains(annotation().linearizedBaseContracts, &_base);
}
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
{
auto exportedFunctionList = interfaceFunctionList();
@ -221,36 +207,6 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
return *m_interfaceFunctionList;
}
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
{
if (!m_inheritableMembers)
{
m_inheritableMembers = make_unique<vector<Declaration const*>>();
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();

File diff suppressed because it is too large Load Diff

View File

@ -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<std::string, DocTag> 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<FunctionDefinition const*> unimplementedFunctions;
@ -119,17 +122,15 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
std::set<CallableDeclaration const*> 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<CallableDeclaration const*> baseFunctions;
};
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
struct StatementAnnotation: ASTAnnotation
{
};

View File

@ -93,6 +93,7 @@ class PrimaryExpression;
class Identifier;
class ElementaryTypeNameExpression;
class Literal;
class StructuredDocumentation;
class VariableScope;

View File

@ -26,6 +26,8 @@
#include <libyul/AsmJsonConverter.h>
#include <libyul/AsmData.h>
#include <libyul/AsmPrinter.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libsolutil/JSON.h>
#include <libsolutil/UTF8.h>
@ -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<pair<string, Json::Value>> 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<pair<string, Json::Value>> 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<pair<string, Json::Value>> 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<solidity::yul::EVMDialect const&>(_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<pair<string, Json::Value>> 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<pair<string, Json::Value>> 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<pair<string, Json::Value>> attributes = {
make_pair("text", text)
};
setJsonNode(_node, "StructuredDocumentation", std::move(attributes));
return false;
}
void ASTJsonConverter::endVisit(EventDefinition const&)
{

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author julius <djudju@protonmail.com>
* @date 2019
* Converts the AST from JSON format to ASTNode
*/
#pragma once
#include <vector>
#include <libsolidity/ast/AST.h>
#include <json/json.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/SourceLocation.h>
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<std::string, ASTPointer<SourceUnit>> jsonToSourceUnit(std::map<std::string, Json::Value> const& _sourceList);
private:
// =========== general creation functions ==============
/// Sets the source location and nodeID
/// @returns the ASTNode Object class of the respective JSON node,
template <typename T, typename... Args>
ASTPointer<T> 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<ASTNode> 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<class T>
ASTPointer<T> convertJsonToASTNode(Json::Value const& _node);
/// \defgroup nodeCreators JSON to AST-Nodes
///@{
ASTPointer<SourceUnit> createSourceUnit(Json::Value const& _node, std::string const& _srcName);
ASTPointer<PragmaDirective> createPragmaDirective(Json::Value const& _node);
ASTPointer<ImportDirective> createImportDirective(Json::Value const& _node);
ASTPointer<ContractDefinition> createContractDefinition(Json::Value const& _node);
ASTPointer<InheritanceSpecifier> createInheritanceSpecifier(Json::Value const& _node);
ASTPointer<UsingForDirective> createUsingForDirective(Json::Value const& _node);
ASTPointer<ASTNode> createStructDefinition(Json::Value const& _node);
ASTPointer<EnumDefinition> createEnumDefinition(Json::Value const& _node);
ASTPointer<EnumValue> createEnumValue(Json::Value const& _node);
ASTPointer<ParameterList> createParameterList(Json::Value const& _node);
ASTPointer<OverrideSpecifier> createOverrideSpecifier(Json::Value const& _node);
ASTPointer<FunctionDefinition> createFunctionDefinition(Json::Value const& _node);
ASTPointer<VariableDeclaration> createVariableDeclaration(Json::Value const& _node);
ASTPointer<ModifierDefinition> createModifierDefinition(Json::Value const& _node);
ASTPointer<ModifierInvocation> createModifierInvocation(Json::Value const& _node);
ASTPointer<EventDefinition> createEventDefinition(Json::Value const& _node);
ASTPointer<ElementaryTypeName> createElementaryTypeName(Json::Value const& _node);
ASTPointer<UserDefinedTypeName> createUserDefinedTypeName(Json::Value const& _node);
ASTPointer<FunctionTypeName> createFunctionTypeName(Json::Value const& _node);
ASTPointer<Mapping> createMapping(Json::Value const& _node);
ASTPointer<ArrayTypeName> createArrayTypeName(Json::Value const& _node);
ASTPointer<InlineAssembly> createInlineAssembly(Json::Value const& _node);
ASTPointer<Block> createBlock(Json::Value const& _node);
ASTPointer<PlaceholderStatement> createPlaceholderStatement(Json::Value const& _node);
ASTPointer<IfStatement> createIfStatement(Json::Value const& _node);
ASTPointer<TryCatchClause> createTryCatchClause(Json::Value const& _node);
ASTPointer<TryStatement> createTryStatement(Json::Value const& _node);
ASTPointer<WhileStatement> createWhileStatement(Json::Value const& _node, bool _isDoWhile);
ASTPointer<ForStatement> createForStatement(Json::Value const& _node);
ASTPointer<Continue> createContinue(Json::Value const& _node);
ASTPointer<Break> createBreak(Json::Value const& _node);
ASTPointer<Return> createReturn(Json::Value const& _node);
ASTPointer<Throw> createThrow(Json::Value const& _node);
ASTPointer<EmitStatement> createEmitStatement(Json::Value const& _node);
ASTPointer<VariableDeclarationStatement> createVariableDeclarationStatement(Json::Value const& _node);
ASTPointer<ExpressionStatement> createExpressionStatement(Json::Value const& _node);
ASTPointer<Conditional> createConditional(Json::Value const& _node);
ASTPointer<Assignment> createAssignment(Json::Value const& _node);
ASTPointer<TupleExpression> createTupleExpression(Json::Value const& _node);
ASTPointer<UnaryOperation> createUnaryOperation(Json::Value const& _node);
ASTPointer<BinaryOperation> createBinaryOperation(Json::Value const& _node);
ASTPointer<FunctionCall> createFunctionCall(Json::Value const& _node);
ASTPointer<FunctionCallOptions> createFunctionCallOptions(Json::Value const& _node);
ASTPointer<NewExpression> createNewExpression(Json::Value const& _node);
ASTPointer<MemberAccess> createMemberAccess(Json::Value const& _node);
ASTPointer<IndexAccess> createIndexAccess(Json::Value const& _node);
ASTPointer<IndexRangeAccess> createIndexRangeAccess(Json::Value const& _node);
ASTPointer<Identifier> createIdentifier(Json::Value const& _node);
ASTPointer<ElementaryTypeNameExpression> createElementaryTypeNameExpression(Json::Value const& _node);
ASTPointer<ASTNode> createLiteral(Json::Value const& _node);
ASTPointer<StructuredDocumentation> 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<class T>
///@returns nullptr or an ASTPointer cast to a specific Class
ASTPointer<T> nullOrCast(Json::Value const& _json);
/// @returns nullptr or ASTString, given an JSON string or an empty field
ASTPointer<ASTString> nullOrASTString(Json::Value const& _json, std::string const& _name);
// ============== JSON to definition helpers ===============
/// \defgroup typeHelpers Json to ast-datatype helpers
/// {@
ASTPointer<ASTString> 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<std::string, Json::Value> m_sourceList;
/// list of filepaths (used as sourcenames)
std::vector<std::shared_ptr<std::string const>> m_sourceLocations;
/// filepath to AST
std::map<std::string, ASTPointer<SourceUnit>> m_sourceUnits;
std::string m_currentSourceName;
/// IDs already used by the nodes
std::set<int64_t> m_usedIDs;
/// Configured EVM version
langutil::EVMVersion m_evmVersion;
};
}

View File

@ -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

View File

@ -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))

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author julius <djudju@protonmail.com>
* @date 2019
* Converts an inlineAssembly AST from JSON format to AsmData
*/
#include <libsolidity/ast/AsmJsonImporter.h>
#include <libsolidity/ast/ASTJsonImporter.h>
#include <libsolidity/ast/Types.h>
#include <libyul/AsmData.h>
#include <libyul/AsmDataForward.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/Scanner.h>
#include <vector>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
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 <class T>
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<yul::TypedName>(_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<yul::Expression> AsmJsonImporter::createExpressionVector(Json::Value const& _array)
{
vector<yul::Expression> ret;
for (auto& var: _array)
ret.emplace_back(createExpression(var));
return ret;
}
vector<yul::Statement> AsmJsonImporter::createStatementVector(Json::Value const& _array)
{
vector<yul::Statement> ret;
for (auto& var: _array)
ret.emplace_back(createStatement(var));
return ret;
}
yul::Block AsmJsonImporter::createBlock(Json::Value const& _node)
{
auto block = createAsmNode<yul::Block>(_node);
block.statements = createStatementVector(_node["statements"]);
return block;
}
yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
{
auto lit = createAsmNode<yul::Literal>(_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<yul::Leave>(_node);
}
yul::Identifier AsmJsonImporter::createIdentifier(Json::Value const& _node)
{
auto identifier = createAsmNode<yul::Identifier>(_node);
identifier.name = YulString(member(_node, "name").asString());
return identifier;
}
yul::Assignment AsmJsonImporter::createAssignment(Json::Value const& _node)
{
auto assignment = createAsmNode<yul::Assignment>(_node);
if (_node.isMember("variableNames"))
for (auto const& var: member(_node, "variableNames"))
assignment.variableNames.emplace_back(createIdentifier(var));
assignment.value = make_unique<yul::Expression>(createExpression(member(_node, "value")));
return assignment;
}
yul::FunctionCall AsmJsonImporter::createFunctionCall(Json::Value const& _node)
{
auto functionCall = createAsmNode<yul::FunctionCall>(_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<yul::ExpressionStatement>(_node);
statement.expression = createExpression(member(_node, "expression"));
return statement;
}
yul::VariableDeclaration AsmJsonImporter::createVariableDeclaration(Json::Value const& _node)
{
auto varDec = createAsmNode<yul::VariableDeclaration>(_node);
for (auto const& var: member(_node, "variables"))
varDec.variables.emplace_back(createTypedName(var));
varDec.value = make_unique<yul::Expression>(createExpression(member(_node, "value")));
return varDec;
}
yul::FunctionDefinition AsmJsonImporter::createFunctionDefinition(Json::Value const& _node)
{
auto funcDef = createAsmNode<yul::FunctionDefinition>(_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<yul::If>(_node);
ifStatement.condition = make_unique<yul::Expression>(createExpression(member(_node, "condition")));
ifStatement.body = createBlock(member(_node, "body"));
return ifStatement;
}
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
{
auto caseStatement = createAsmNode<yul::Case>(_node);
caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique<yul::Literal>(createLiteral(member(_node, "value")));
caseStatement.body = createBlock(member(_node, "body"));
return caseStatement;
}
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
{
auto switchStatement = createAsmNode<yul::Switch>(_node);
switchStatement.expression = make_unique<yul::Expression>(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<yul::ForLoop>(_node);
forLoop.pre = createBlock(member(_node, "pre"));
forLoop.condition = make_unique<yul::Expression>(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<yul::Break>(_node);
}
yul::Continue AsmJsonImporter::createContinue(Json::Value const& _node)
{
return createAsmNode<yul::Continue>(_node);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @author julius <djudju@protonmail.com>
* @date 2019
* Converts an inlineAssembly AST from JSON format to AsmData
*/
#pragma once
#include <json/json.h>
#include <liblangutil/SourceLocation.h>
#include <libyul/AsmDataForward.h>
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 <class T>
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<yul::Statement> createStatementVector(Json::Value const& _array);
std::vector<yul::Expression> 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;
};
}

View File

@ -459,7 +459,8 @@ FunctionType const* TypeProvider::function(
Declaration const* _declaration,
bool _gasSet,
bool _valueSet,
bool _bound
bool _bound,
bool _saltSet
)
{
return createAndGet<FunctionType>(
@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function(
_declaration,
_gasSet,
_valueSet,
_bound
_bound,
_saltSet
);
}

View File

@ -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

View File

@ -151,6 +151,8 @@ util::Result<TypePointers> 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<u256, unsigned> 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<StorageOffsets>();
m_storageOffsets->computeOffsets(memberTypes);
}
for (size_t index = 0; index < m_memberTypes.size(); ++index)
@ -1701,15 +1703,22 @@ u256 ArrayType::storageSize() const
return max<u256>(1, u256(size));
}
unsigned ArrayType::sizeOnStack() const
vector<tuple<string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::stateVar
return variablesAndOffsets;
}
vector<tuple<string, TypePointer>> 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<tuple<string, TypePointer>> TupleType::makeStackItems() const
{
unsigned size = 0;
vector<tuple<string, TypePointer>> 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<tuple<string, TypePointer>> FunctionType::makeStackItems() const
{
vector<tuple<string, TypePointer>> 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<FunctionType const&>(*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<FunctionDefinition const*>(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<ASTString> FunctionType::documentation() const
ASTPointer<StructuredDocumentation> FunctionType::documentation() const
{
auto function = dynamic_cast<Documented const*>(m_declaration);
auto function = dynamic_cast<StructurallyDocumented const*>(m_declaration);
if (function)
return function->documentation();
return ASTPointer<ASTString>();
return ASTPointer<StructuredDocumentation>();
}
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<tuple<string, TypePointer>> TypeType::makeStackItems() const
{
if (auto contractType = dynamic_cast<ContractType const*>(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<ContractType const&>(*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<ModifierDefinition const*>(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)

View File

@ -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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> makeStackItems() const
{
return {std::make_tuple(std::string(), nullptr)};
}
/// List of member types (parameterised by scape), will be lazy-initialized.
mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members;
mutable std::optional<std::vector<std::tuple<std::string, TypePointer>>> m_stackItems;
mutable std::optional<size_t> 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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
protected:
std::vector<std::tuple<std::string, TypePointer>> 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<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
protected:
std::vector<std::tuple<std::string, TypePointer>> 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<TypePointer> const& components() const { return m_components; }
protected:
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
private:
std::vector<TypePointer> 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<ASTString> documentation() const;
/// @return A shared pointer of StructuredDocumentation.
/// Can contain a nullptr in which case indicates absence of documentation.
ASTPointer<StructuredDocumentation> 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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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<std::tuple<std::string, TypePointer>> 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;

View File

@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
Whiskers templ(R"(
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
<decodeElements>
}
)");
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 := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
if gt(offset, 0xffffffffffffffff) { <revertString> }
<values> := <abiDecode>(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, <maxLength>) { revert(0, 0) }
if gt(length, <maxLength>) { <revertString> }
length := mul(length, <stride>)
)")
("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"(
// <readableTypeName>
function <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertString> }
let length := <retrieveLength>
array := <allocate>(<allocationSize>(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"(
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos, length {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
length := calldataload(offset)
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
arrayPos := add(offset, 0x20)
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(length, <stride>)), end) { <revertStringPos> }
}
)";
else
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
// <readableTypeName>
function <functionName>(offset, end) -> arrayPos {
arrayPos := offset
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) }
if gt(add(arrayPos, mul(<length>, <stride>)), end) { <revertStringPos> }
}
)";
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 <functionName>(offset, end) -> array {
if iszero(slt(add(offset, 0x1f), end)) { revert(0, 0) }
if iszero(slt(add(offset, 0x1f), end)) { <revertStringOffset> }
let length := <load>(offset)
array := <allocate>(<allocationSize>(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) { <revertStringLength> }
<copyToMemFun>(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"(
// <readableTypeName>
function <functionName>(offset, end) -> value {
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
if slt(sub(end, offset), <minimumSize>) { <revertString> }
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"(
// <readableTypeName>
function <functionName>(headStart, end) -> value {
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
value := <allocate>(<memorySize>)
<#members>
{
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
</members>
}
)");
// 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 := <load>(add(headStart, <pos>))
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
if gt(offset, 0xffffffffffffffff) { <revertString> }
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
)" :
R"(
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
mstore(add(value, <memoryOffset>), <abiDecode>(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 <functionName>(base_ref, ptr) -> <return> {
let rel_offset_of_tail := calldataload(ptr)
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertStringOffset> }
value := add(rel_offset_of_tail, base_ref)
<handleLength>
}
@ -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, <calldataStride>))) { revert(0, 0) }
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
)")
("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);
}

View File

@ -25,6 +25,8 @@
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/interface/DebugSettings.h>
#include <liblangutil/EVMVersion.h>
#include <functional>
@ -55,11 +57,13 @@ class ABIFunctions
public:
explicit ABIFunctions(
langutil::EVMVersion _evmVersion,
RevertStrings _revertStrings,
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
):
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<MultiUseYulFunctionCollector> m_functionCollector;
std::set<std::string> m_externallyUsedFunctions;
YulUtilFunctions m_utils;

View File

@ -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);

View File

@ -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<std::string, unsigned> const& _indices = std::map<std::string, unsigned>()) 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;

Some files were not shown because too many files have changed in this diff Show More