mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge remote-tracking branch 'origin/develop' into HEAD
This commit is contained in:
commit
06ad5b3200
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
101
.circleci/docker/Dockerfile.ubuntu1604.clang.ossfuzz
Normal file
101
.circleci/docker/Dockerfile.ubuntu1604.clang.ossfuzz
Normal 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
|
@ -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
1
.gitignore
vendored
@ -40,6 +40,7 @@ docs/utils/*.pyc
|
||||
/deps/downloads/
|
||||
deps/install
|
||||
deps/cache
|
||||
cmake-build-debug/
|
||||
|
||||
# vim stuff
|
||||
[._]*.sw[a-p]
|
||||
|
@ -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)
|
||||
|
53
Changelog.md
53
Changelog.md
@ -9,17 +9,62 @@ Compiler Features:
|
||||
Bugfixes:
|
||||
|
||||
|
||||
### 0.6.2 (unreleased)
|
||||
### 0.6.4 (unreleased)
|
||||
|
||||
Language Features:
|
||||
* Allow accessing external functions via contract and interface names to obtain their selector.
|
||||
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon).
|
||||
* Yul Optimizer: Apply penalty when trying to rematerialize into loops.
|
||||
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* isoltest: Added new keyword `wei` to express function value in semantic tests
|
||||
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
|
||||
|
||||
|
||||
### 0.6.3 (2020-02-18)
|
||||
|
||||
Language Features:
|
||||
* Allow contract types and enums as keys for mappings.
|
||||
* Allow function selectors to be used as compile-time constants.
|
||||
* Report source locations for structured documentation errors.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
|
||||
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
|
||||
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
|
||||
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Assembly: Added missing `source` field to legacy assembly json output to complete the source reference.
|
||||
* Parser: Fix an internal error for ``abstract`` without ``contract``.
|
||||
* Type Checker: Make invalid calls to uncallable types fatal errors instead of regular.
|
||||
|
||||
|
||||
### 0.6.2 (2020-01-27)
|
||||
|
||||
Language Features:
|
||||
* Allow accessing external functions via contract and interface names to obtain their selector.
|
||||
* Allow interfaces to inherit from other interfaces
|
||||
* Allow gas and value to be set in external function calls using ``c.f{gas: 10000, value: 4 ether}()``.
|
||||
* Allow specifying the ``salt`` for contract creations and thus the ``create2`` opcode using ``new C{salt: 0x1234, value: 1 ether}(arg1, arg2)``.
|
||||
* Inline Assembly: Support literals ``true`` and ``false``.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* LLL: The LLL compiler has been removed.
|
||||
* General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon).
|
||||
* General: Support compiling starting from an imported AST. Among others, this can be used for mutation testing.
|
||||
* Yul Optimizer: Apply penalty when trying to rematerialize into loops.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Commandline interface: Only activate yul optimizer if ``--optimize`` is given.
|
||||
* Fixes internal compiler error on explicitly calling unimplemented base functions.
|
||||
|
||||
|
||||
Build System:
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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%
|
||||
|
@ -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")
|
||||
|
@ -41,7 +41,6 @@ if (SUPPORT_TOOLS)
|
||||
endif()
|
||||
message("------------------------------------------------------------------ flags")
|
||||
message("-- OSSFUZZ ${OSSFUZZ}")
|
||||
message("-- LLL ${LLL}")
|
||||
message("------------------------------------------------------------------------")
|
||||
message("")
|
||||
endmacro()
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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]+
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
@ -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
|
||||
|
@ -104,4 +104,3 @@ Contents
|
||||
common-patterns.rst
|
||||
bugs.rst
|
||||
contributing.rst
|
||||
lll.rst
|
||||
|
@ -174,7 +174,8 @@ Install it using ``brew``:
|
||||
# eg. Install 0.4.8
|
||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
||||
|
||||
Gentoo Linux also provides a solidity package that can be installed using ``emerge``:
|
||||
Gentoo Linux has an `Ethereum overlay <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
|
||||
|
||||
|
21
docs/lll.rst
21
docs/lll.rst
@ -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.
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {}
|
||||
}
|
||||
|
971
docs/yul.rst
971
docs/yul.rst
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)...}};
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -16,6 +16,7 @@ set(sources
|
||||
SemVerHandler.cpp
|
||||
SemVerHandler.h
|
||||
SourceLocation.h
|
||||
SourceLocation.cpp
|
||||
SourceReferenceExtractor.cpp
|
||||
SourceReferenceExtractor.h
|
||||
SourceReferenceFormatter.cpp
|
||||
|
@ -93,10 +93,13 @@ string CharStream::lineAtPosition(int _position) const
|
||||
lineStart = 0;
|
||||
else
|
||||
lineStart++;
|
||||
return m_source.substr(
|
||||
string line = m_source.substr(
|
||||
lineStart,
|
||||
min(m_source.find('\n', lineStart), m_source.size()) - lineStart
|
||||
);
|
||||
if (!line.empty() && line.back() == '\r')
|
||||
line.pop_back();
|
||||
return line;
|
||||
}
|
||||
|
||||
tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
51
liblangutil/SourceLocation.cpp
Normal file
51
liblangutil/SourceLocation.cpp
Normal 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};
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
@ -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());
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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 {};
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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().
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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;
|
||||
|
@ -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
@ -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
|
||||
{
|
||||
};
|
||||
|
||||
|
@ -93,6 +93,7 @@ class PrimaryExpression;
|
||||
class Identifier;
|
||||
class ElementaryTypeNameExpression;
|
||||
class Literal;
|
||||
class StructuredDocumentation;
|
||||
|
||||
class VariableScope;
|
||||
|
||||
|
@ -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&)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
1018
libsolidity/ast/ASTJsonImporter.cpp
Normal file
1018
libsolidity/ast/ASTJsonImporter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
163
libsolidity/ast/ASTJsonImporter.h
Normal file
163
libsolidity/ast/ASTJsonImporter.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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))
|
||||
|
305
libsolidity/ast/AsmJsonImporter.cpp
Normal file
305
libsolidity/ast/AsmJsonImporter.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
74
libsolidity/ast/AsmJsonImporter.h
Normal file
74
libsolidity/ast/AsmJsonImporter.h
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user