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>
|
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:
|
Once the docker image has been built and pushed to Dockerhub, you can find it at:
|
||||||
|
|
||||||
|
@ -7,9 +7,18 @@
|
|||||||
# - ems: Emscripten
|
# - ems: Emscripten
|
||||||
version: 2.1
|
version: 2.1
|
||||||
parameters:
|
parameters:
|
||||||
docker-image-rev:
|
ubuntu-1804-docker-image-rev:
|
||||||
type: string
|
type: string
|
||||||
default: "4"
|
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:
|
defaults:
|
||||||
|
|
||||||
@ -62,6 +71,11 @@ defaults:
|
|||||||
path: build/solc/solc
|
path: build/solc/solc
|
||||||
destination: solc
|
destination: solc
|
||||||
|
|
||||||
|
# compiled tool executable target
|
||||||
|
- artifacts_tools: &artifacts_tools
|
||||||
|
path: build/tools/solidity-upgrade
|
||||||
|
destination: solidity-upgrade
|
||||||
|
|
||||||
# compiled executable targets
|
# compiled executable targets
|
||||||
- artifacts_executables: &artifacts_executables
|
- artifacts_executables: &artifacts_executables
|
||||||
root: build
|
root: build
|
||||||
@ -108,9 +122,20 @@ defaults:
|
|||||||
name: command line tests
|
name: command line tests
|
||||||
command: ./test/cmdlineTests.sh
|
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
|
- test_ubuntu1904_clang: &test_ubuntu1904_clang
|
||||||
docker:
|
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:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
@ -121,7 +146,7 @@ defaults:
|
|||||||
|
|
||||||
- test_ubuntu1904: &test_ubuntu1904
|
- test_ubuntu1904: &test_ubuntu1904
|
||||||
docker:
|
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:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
@ -155,6 +180,11 @@ defaults:
|
|||||||
requires:
|
requires:
|
||||||
- b_ubu
|
- b_ubu
|
||||||
|
|
||||||
|
- workflow_ubuntu1604_clang: &workflow_ubuntu1604_clang
|
||||||
|
<<: *workflow_trigger_on_tags
|
||||||
|
requires:
|
||||||
|
- b_ubu_ossfuzz
|
||||||
|
|
||||||
- workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang
|
- workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang
|
||||||
<<: *workflow_trigger_on_tags
|
<<: *workflow_trigger_on_tags
|
||||||
requires:
|
requires:
|
||||||
@ -185,7 +215,7 @@ defaults:
|
|||||||
requires:
|
requires:
|
||||||
- b_ems
|
- b_ems
|
||||||
|
|
||||||
- workflow_ubuntu1904_ossfuzz: &workflow_ubuntu1904_ossfuzz
|
- workflow_ubuntu1604_ossfuzz: &workflow_ubuntu1604_ossfuzz
|
||||||
<<: *workflow_trigger_on_tags
|
<<: *workflow_trigger_on_tags
|
||||||
requires:
|
requires:
|
||||||
- b_ubu_ossfuzz
|
- b_ubu_ossfuzz
|
||||||
@ -257,6 +287,22 @@ jobs:
|
|||||||
name: Check for C++ coding style
|
name: Check for C++ coding style
|
||||||
command: ./scripts/check_style.sh
|
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:
|
chk_buglist:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node
|
- image: circleci/node
|
||||||
@ -291,11 +337,10 @@ jobs:
|
|||||||
|
|
||||||
b_ubu_clang: &build_ubuntu1904_clang
|
b_ubu_clang: &build_ubuntu1904_clang
|
||||||
docker:
|
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:
|
environment:
|
||||||
CC: clang
|
CC: clang
|
||||||
CXX: clang++
|
CXX: clang++
|
||||||
CMAKE_OPTIONS: -DLLL=ON
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: *run_build
|
- run: *run_build
|
||||||
@ -304,26 +349,24 @@ jobs:
|
|||||||
|
|
||||||
b_ubu: &build_ubuntu1904
|
b_ubu: &build_ubuntu1904
|
||||||
docker:
|
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:
|
|
||||||
CMAKE_OPTIONS: -DLLL=ON
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: *run_build
|
- run: *run_build
|
||||||
- store_artifacts: *artifacts_solc
|
- store_artifacts: *artifacts_solc
|
||||||
|
- store_artifacts: *artifacts_tools
|
||||||
- persist_to_workspace: *artifacts_executables
|
- persist_to_workspace: *artifacts_executables
|
||||||
|
|
||||||
b_ubu_release: &build_ubuntu1904_release
|
b_ubu_release: &build_ubuntu1904_release
|
||||||
<<: *build_ubuntu1904
|
<<: *build_ubuntu1904
|
||||||
environment:
|
environment:
|
||||||
FORCE_RELEASE: ON
|
FORCE_RELEASE: ON
|
||||||
CMAKE_OPTIONS: -DLLL=ON
|
|
||||||
|
|
||||||
b_ubu18: &build_ubuntu1804
|
b_ubu18: &build_ubuntu1804
|
||||||
docker:
|
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:
|
environment:
|
||||||
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2 -DLLL=ON
|
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
|
||||||
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -368,17 +411,18 @@ jobs:
|
|||||||
<<: *build_ubuntu1904
|
<<: *build_ubuntu1904
|
||||||
environment:
|
environment:
|
||||||
CMAKE_BUILD_TYPE: Debug
|
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:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: *run_build
|
- run: *run_build
|
||||||
|
|
||||||
b_ubu_ossfuzz:
|
b_ubu_ossfuzz: &build_ubuntu1604_clang
|
||||||
<<: *build_ubuntu1904_clang
|
docker:
|
||||||
|
- image: ethereum/solidity-buildpack-deps:ubuntu1604-clang-ossfuzz-<< pipeline.parameters.ubuntu-1604-clang-ossfuzz-docker-image-rev >>
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm
|
|
||||||
CC: clang
|
CC: clang
|
||||||
CXX: clang++
|
CXX: clang++
|
||||||
|
TERM: xterm
|
||||||
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@ -387,7 +431,7 @@ jobs:
|
|||||||
- persist_to_workspace: *artifacts_executables_ossfuzz
|
- persist_to_workspace: *artifacts_executables_ossfuzz
|
||||||
|
|
||||||
t_ubu_ossfuzz: &t_ubu_ossfuzz
|
t_ubu_ossfuzz: &t_ubu_ossfuzz
|
||||||
<<: *test_ubuntu1904_clang
|
<<: *test_ubuntu1604_clang
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
@ -395,8 +439,8 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Regression tests
|
name: Regression tests
|
||||||
command: |
|
command: |
|
||||||
|
git clone https://github.com/ethereum/solidity-fuzzing-corpus /tmp/solidity-fuzzing-corpus
|
||||||
mkdir -p test_results
|
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
|
scripts/regressions.py -o test_results
|
||||||
- run: *gitter_notify_failure
|
- run: *gitter_notify_failure
|
||||||
- run: *gitter_notify_success
|
- run: *gitter_notify_success
|
||||||
@ -408,7 +452,6 @@ jobs:
|
|||||||
- image: archlinux/base
|
- image: archlinux/base
|
||||||
environment:
|
environment:
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
CMAKE_OPTIONS: -DLLL=ON
|
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: Install build dependencies
|
name: Install build dependencies
|
||||||
@ -425,7 +468,6 @@ jobs:
|
|||||||
environment:
|
environment:
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
CMAKE_BUILD_TYPE: Debug
|
CMAKE_BUILD_TYPE: Debug
|
||||||
CMAKE_OPTIONS: -DLLL=ON
|
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
@ -445,6 +487,7 @@ jobs:
|
|||||||
- /usr/local/Homebrew
|
- /usr/local/Homebrew
|
||||||
- run: *run_build
|
- run: *run_build
|
||||||
- store_artifacts: *artifacts_solc
|
- store_artifacts: *artifacts_solc
|
||||||
|
- store_artifacts: *artifacts_tools
|
||||||
- persist_to_workspace: *artifacts_build_dir
|
- persist_to_workspace: *artifacts_build_dir
|
||||||
|
|
||||||
t_osx_soltest:
|
t_osx_soltest:
|
||||||
@ -529,7 +572,7 @@ jobs:
|
|||||||
|
|
||||||
b_docs:
|
b_docs:
|
||||||
docker:
|
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:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: *setup_prerelease_commit_hash
|
- run: *setup_prerelease_commit_hash
|
||||||
@ -554,7 +597,7 @@ jobs:
|
|||||||
|
|
||||||
t_ubu_cli: &t_ubu_cli
|
t_ubu_cli: &t_ubu_cli
|
||||||
docker:
|
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:
|
environment:
|
||||||
TERM: xterm
|
TERM: xterm
|
||||||
steps:
|
steps:
|
||||||
@ -724,6 +767,7 @@ workflows:
|
|||||||
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
|
# DISABLED FOR 0.6.0 - chk_docs_examples: *workflow_trigger_on_tags
|
||||||
- chk_buglist: *workflow_trigger_on_tags
|
- chk_buglist: *workflow_trigger_on_tags
|
||||||
- chk_proofs: *workflow_trigger_on_tags
|
- chk_proofs: *workflow_trigger_on_tags
|
||||||
|
- chk_pylint: *workflow_trigger_on_tags
|
||||||
|
|
||||||
# build-only
|
# build-only
|
||||||
- b_docs: *workflow_trigger_on_tags
|
- b_docs: *workflow_trigger_on_tags
|
||||||
@ -775,7 +819,7 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
# OSSFUZZ builds and (regression) tests
|
# OSSFUZZ builds and (regression) tests
|
||||||
- b_ubu_ossfuzz: *workflow_trigger_on_tags
|
- 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
|
# Code Coverage enabled build and tests
|
||||||
- b_ubu_codecov: *workflow_trigger_on_tags
|
- 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++
|
ENV CXX clang++
|
||||||
|
|
||||||
# Boost
|
# 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; \
|
/usr/src/boost; \
|
||||||
cd /usr/src/boost; \
|
cd /usr/src/boost; \
|
||||||
|
git submodule update --init --recursive; \
|
||||||
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
|
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
|
||||||
./b2 toolset=clang headers; \
|
./b2 toolset=clang headers; \
|
||||||
./b2 toolset=clang variant=release \
|
./b2 toolset=clang variant=release \
|
||||||
@ -76,7 +77,7 @@ RUN set -ex; \
|
|||||||
git clone https://github.com/google/libprotobuf-mutator.git \
|
git clone https://github.com/google/libprotobuf-mutator.git \
|
||||||
/usr/src/libprotobuf-mutator; \
|
/usr/src/libprotobuf-mutator; \
|
||||||
cd /usr/src/libprotobuf-mutator; \
|
cd /usr/src/libprotobuf-mutator; \
|
||||||
git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
|
git checkout 3521f47a2828da9ace403e4ecc4aece1a84feb36; \
|
||||||
mkdir build; \
|
mkdir build; \
|
||||||
cd build; \
|
cd build; \
|
||||||
cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \
|
cmake .. -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON \
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,6 +40,7 @@ docs/utils/*.pyc
|
|||||||
/deps/downloads/
|
/deps/downloads/
|
||||||
deps/install
|
deps/install
|
||||||
deps/cache
|
deps/cache
|
||||||
|
cmake-build-debug/
|
||||||
|
|
||||||
# vim stuff
|
# vim stuff
|
||||||
[._]*.sw[a-p]
|
[._]*.sw[a-p]
|
||||||
|
@ -19,10 +19,7 @@ if (IS_BIG_ENDIAN)
|
|||||||
message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.")
|
message(FATAL_ERROR "${PROJECT_NAME} currently does not support big endian systems.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(LLL "Build LLL" OFF)
|
|
||||||
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" 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.
|
# Setup cccache.
|
||||||
include(EthCcache)
|
include(EthCcache)
|
||||||
@ -58,13 +55,10 @@ add_subdirectory(libevmasm)
|
|||||||
add_subdirectory(libyul)
|
add_subdirectory(libyul)
|
||||||
add_subdirectory(libsolidity)
|
add_subdirectory(libsolidity)
|
||||||
add_subdirectory(libsolc)
|
add_subdirectory(libsolc)
|
||||||
|
add_subdirectory(tools)
|
||||||
|
|
||||||
if (NOT EMSCRIPTEN)
|
if (NOT EMSCRIPTEN)
|
||||||
add_subdirectory(solc)
|
add_subdirectory(solc)
|
||||||
if (LLL)
|
|
||||||
add_subdirectory(liblll)
|
|
||||||
add_subdirectory(lllc)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (TESTS AND NOT EMSCRIPTEN)
|
if (TESTS AND NOT EMSCRIPTEN)
|
||||||
|
53
Changelog.md
53
Changelog.md
@ -9,17 +9,62 @@ Compiler Features:
|
|||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
||||||
|
|
||||||
### 0.6.2 (unreleased)
|
### 0.6.4 (unreleased)
|
||||||
|
|
||||||
Language Features:
|
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:
|
Compiler Features:
|
||||||
* General: Raise warning if runtime bytecode exceeds 24576 bytes (a limit introduced in Spurious Dragon).
|
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
||||||
* Yul Optimizer: Apply penalty when trying to rematerialize into loops.
|
|
||||||
|
|
||||||
Bugfixes:
|
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:
|
Build System:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# The Solidity Contract-Oriented Programming Language
|
# The Solidity Contract-Oriented Programming Language
|
||||||
[](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
You can talk to us on [](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.
|
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
|
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
- cd %APPVEYOR_BUILD_FOLDER%
|
||||||
- scripts\release.bat %CONFIGURATION% 2017
|
- 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:
|
test_script:
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
||||||
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt
|
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt
|
||||||
# Skip bytecode compare if private key is not available
|
# Skip bytecode compare if private key is not available
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
- 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
|
scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir
|
||||||
}
|
}
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
- 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(-Wall)
|
||||||
add_compile_options(-Wextra)
|
add_compile_options(-Wextra)
|
||||||
add_compile_options(-Werror)
|
add_compile_options(-Werror)
|
||||||
|
add_compile_options(-pedantic)
|
||||||
|
add_compile_options(-Wno-unknown-pragmas)
|
||||||
|
add_compile_options(-Wimplicit-fallthrough)
|
||||||
|
|
||||||
# Configuration-specific compiler settings.
|
# Configuration-specific compiler settings.
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -DETH_DEBUG")
|
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.")
|
message(FATAL_ERROR "${PROJECT_NAME} requires g++ 5.0 or greater.")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# Use fancy colors in the compiler diagnostics
|
||||||
|
add_compile_options(-fdiagnostics-color)
|
||||||
|
|
||||||
# Additional Clang-specific compiler settings.
|
# Additional Clang-specific compiler settings.
|
||||||
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
|
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
|
||||||
|
@ -41,7 +41,6 @@ if (SUPPORT_TOOLS)
|
|||||||
endif()
|
endif()
|
||||||
message("------------------------------------------------------------------ flags")
|
message("------------------------------------------------------------------ flags")
|
||||||
message("-- OSSFUZZ ${OSSFUZZ}")
|
message("-- OSSFUZZ ${OSSFUZZ}")
|
||||||
message("-- LLL ${LLL}")
|
|
||||||
message("------------------------------------------------------------------------")
|
message("------------------------------------------------------------------------")
|
||||||
message("")
|
message("")
|
||||||
endmacro()
|
endmacro()
|
||||||
|
@ -7,5 +7,9 @@ set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
|
|||||||
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
|
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
|
||||||
# Use libfuzzer as the fuzzing back-end
|
# Use libfuzzer as the fuzzing back-end
|
||||||
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
|
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
|
||||||
# clang/libfuzzer specific flags for ASan instrumentation
|
# clang/libfuzzer specific flags for UBSan 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)
|
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
|
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
|
``keccak256(abi.encodePacked(a, b, c))``. Even though it is not a breaking
|
||||||
change, it is suggested that developers change
|
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))``.
|
``x.call(abi.encodeWithSignature("f(uint256)", a, b))``.
|
||||||
|
|
||||||
* Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return
|
* Functions ``.call()``, ``.delegatecall()`` and ``.staticcall()`` now return
|
||||||
@ -455,7 +455,7 @@ New version:
|
|||||||
uint z = someInteger();
|
uint z = someInteger();
|
||||||
x += z;
|
x += z;
|
||||||
// Throw is now disallowed.
|
// Throw is now disallowed.
|
||||||
require(x > 100);
|
require(x <= 100);
|
||||||
int y = -3 >> 1;
|
int y = -3 >> 1;
|
||||||
require(y == -2);
|
require(y == -2);
|
||||||
do {
|
do {
|
||||||
|
@ -1,41 +1,20 @@
|
|||||||
#################
|
.. _inline-assembly:
|
||||||
Solidity Assembly
|
|
||||||
#################
|
###############
|
||||||
|
Inline Assembly
|
||||||
|
###############
|
||||||
|
|
||||||
.. index:: ! assembly, ! asm, ! evmasm
|
.. 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
|
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,
|
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
|
||||||
especially when you are enhancing the language by writing libraries.
|
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
|
The language used for inline assembly in Solidity is called `Yul <yul>`_
|
||||||
and provide arguments to opcodes at the correct point on the stack. Solidity's inline
|
and it is documented in its own section. This section will only cover
|
||||||
assembly helps you do this, and with other issues that arise when writing manual assembly.
|
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::
|
.. warning::
|
||||||
Inline assembly is a way to access the Ethereum Virtual Machine
|
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
|
features and checks of Solidity. You should only use it for
|
||||||
tasks that need it, and only if you are confident with using it.
|
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
|
An inline assembly block is marked by ``assembly { ... }``, where the code inside
|
||||||
usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inline assembly can contain
|
the curly braces is code in the `Yul <yul>`_ language.
|
||||||
``.``. Inline assembly is marked by ``assembly { ... }`` and inside
|
|
||||||
these curly braces, you can use the following (see the later sections for more details):
|
|
||||||
|
|
||||||
- literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
|
The inline assembly code can access local Solidity variables as explained below.
|
||||||
- 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) } }``
|
|
||||||
|
|
||||||
Inline assembly manages local variables and control-flow. Because of that,
|
Different inline assembly blocks share no namespace, i.e. it is not possible
|
||||||
opcodes that interfere with these features are not available. This includes
|
to call a Yul function or access a Yul variable defined in a different inline assembly block.
|
||||||
the ``dup`` and ``swap`` instructions as well as ``jump`` instructions and labels.
|
|
||||||
|
|
||||||
Example
|
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
|
Access to External Variables, Functions and Libraries
|
||||||
-----------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
|
||||||
You can access Solidity variables and other identifiers by using their name.
|
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
|
Local variables of value type are directly usable in inline assembly.
|
||||||
occupy a full storage slot, so their "address" is composed of a slot and a byte-offset
|
|
||||||
|
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
|
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``.
|
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;
|
uint b;
|
||||||
function f(uint x) public view returns (uint r) {
|
function f(uint x) public view returns (uint r) {
|
||||||
assembly {
|
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:
|
To clean signed types, you can use the ``signextend`` opcode:
|
||||||
``assembly { signextend(<num_bytes_of_x_minus_one>, x) }``
|
``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
|
Since Solidity 0.6.0 the name of a inline assembly variable may not end in ``_offset`` or ``_slot``
|
||||||
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``
|
|
||||||
and it may not shadow any declaration visible in the scope of the inline assembly block
|
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
|
(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
|
variable contains a dot ``.``, the prefix up to the ``.`` may not conflict with any
|
||||||
declaration visible in the scope of the inline assembly block.
|
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
|
Assignments are possible to assembly-local variables and to function-local
|
||||||
variables. Take care that when you assign to variables that point to
|
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.
|
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.
|
You can assign to the ``_slot`` part of a local storage variable pointer.
|
||||||
If you want to assign the values returned from a function that has
|
For these (structs, arrays or mappings), the ``_offset`` part is always zero.
|
||||||
multiple return parameters, you have to provide multiple variables.
|
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
|
Things to Avoid
|
||||||
---------------
|
---------------
|
||||||
@ -593,7 +193,8 @@ Conventions in Solidity
|
|||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
In contrast to EVM assembly, Solidity has types which are narrower than 256 bits,
|
In contrast to EVM assembly, Solidity has types which are narrower than 256 bits,
|
||||||
e.g. ``uint24``. For efficiency, most arithmetic operations ignore the fact that types can be shorter than 256
|
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,
|
bits, and the higher-order bits are cleaned when necessary,
|
||||||
i.e., shortly before they are written to memory or before comparisons are performed.
|
i.e., shortly before they are written to memory or before comparisons are performed.
|
||||||
This means that if you access such a variable
|
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
|
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
|
to allow better convertibility between statically- and dynamically-sized arrays, so
|
||||||
do not rely on this.
|
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": {
|
"0.6.1": {
|
||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2020-01-02"
|
"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 os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from pygments_lexer_solidity import SolidityLexer
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
@ -24,7 +26,6 @@ import re
|
|||||||
def setup(sphinx):
|
def setup(sphinx):
|
||||||
thisdir = os.path.dirname(os.path.realpath(__file__))
|
thisdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
sys.path.insert(0, thisdir + '/utils')
|
sys.path.insert(0, thisdir + '/utils')
|
||||||
from pygments_lexer_solidity import SolidityLexer
|
|
||||||
sphinx.add_lexer('Solidity', SolidityLexer())
|
sphinx.add_lexer('Solidity', SolidityLexer())
|
||||||
|
|
||||||
sphinx.add_stylesheet('css/custom.css')
|
sphinx.add_stylesheet('css/custom.css')
|
||||||
@ -53,7 +54,7 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'Solidity'
|
project = 'Solidity'
|
||||||
copyright = '2016-2019, Ethereum'
|
copyright = '2016-2020, Ethereum'
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |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;
|
pragma solidity >=0.4.0 <0.8.0;
|
||||||
|
|
||||||
contract C {
|
contract C {
|
||||||
uint constant x = 32**22 + 8;
|
uint constant X = 32**22 + 8;
|
||||||
string constant text = "abc";
|
string constant TEXT = "abc";
|
||||||
bytes32 constant myHash = keccak256("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 {
|
contract Test {
|
||||||
// This function is called for all messages sent to
|
// 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()"));
|
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
|
||||||
require(success);
|
require(success);
|
||||||
// results in test.x becoming == 1 and test.y becoming 0.
|
// 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);
|
require(success);
|
||||||
// results in test.x becoming == 1 and test.y becoming 1.
|
// 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:
|
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.
|
- All declared functions must be external.
|
||||||
- They cannot declare a constructor.
|
- They cannot declare a constructor.
|
||||||
- They cannot declare state variables.
|
- 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
|
can be overridden again - this is only possible if the overriding
|
||||||
function is marked ``virtual``.
|
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
|
Types defined inside interfaces and other contract-like structures
|
||||||
can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``.
|
can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``.
|
||||||
|
|
||||||
.. warning:
|
.. warning:
|
||||||
|
|
||||||
Interfaces have supported ``enum`` types since :doc:`Solidity version 0.5.0 <050-breaking-changes>`, make
|
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.
|
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
|
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).
|
(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.
|
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.
|
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
|
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()``,
|
gas sent with the call with the special options ``{value: 10, gas: 10000}``.
|
||||||
respectively. Any Wei you send to the contract is added to the total balance
|
Note that it is discouraged to specify gas values explicitly, since the gas costs
|
||||||
of the contract:
|
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 {
|
contract Consumer {
|
||||||
InfoFeed feed;
|
InfoFeed feed;
|
||||||
function setFeed(InfoFeed addr) public { feed = addr; }
|
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
|
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::
|
.. 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
|
``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
|
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.
|
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
|
external functions happen after any changes to state variables in your contract
|
||||||
so your contract is not vulnerable to a reentrancy exploit.
|
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
|
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 {
|
function createAndEndowD(uint arg, uint amount) public payable {
|
||||||
// Send ether along with the creation
|
// Send ether along with the creation
|
||||||
D newD = (new D).value(amount)(arg);
|
D newD = new D{value: amount}(arg);
|
||||||
newD.x();
|
newD.x();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
As seen in the example, it is possible to send Ether while creating
|
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.
|
to limit the amount of gas.
|
||||||
If the creation fails (due to out-of-stack, not enough balance or other problems),
|
If the creation fails (due to out-of-stack, not enough balance or other problems),
|
||||||
an exception is thrown.
|
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
|
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
|
Complications for Arrays and Structs
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
The semantics of assignments are a bit more complicated for
|
The semantics of assignments are more complicated for non-value types like arrays and structs,
|
||||||
non-value types like arrays and structs.
|
including ``bytes`` and ``string``, see :ref:`Data location and assignment behaviour <data-location-assignment>` for details.
|
||||||
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.
|
|
||||||
|
|
||||||
In the example below the call to ``g(x)`` has no effect on ``x`` because it creates
|
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``
|
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
|
in a catch block or the execution of the try/catch statement itself
|
||||||
reverts (for example due to decoding failures as noted above or
|
reverts (for example due to decoding failures as noted above or
|
||||||
due to not providing a low-level catch clause).
|
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 )*
|
UserDefinedTypeName = Identifier ( '.' Identifier )*
|
||||||
|
|
||||||
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
|
Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
|
||||||
ArrayTypeName = TypeName '[' Expression? ']'
|
ArrayTypeName = TypeName '[' Expression? ']'
|
||||||
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
|
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
|
||||||
( 'returns' FunctionTypeParameterList )?
|
( 'returns' FunctionTypeParameterList )?
|
||||||
@ -96,6 +96,7 @@ Expression
|
|||||||
| IndexRangeAccess
|
| IndexRangeAccess
|
||||||
| MemberAccess
|
| MemberAccess
|
||||||
| FunctionCall
|
| FunctionCall
|
||||||
|
| Expression '{' NameValueList '}'
|
||||||
| '(' Expression ')'
|
| '(' Expression ')'
|
||||||
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
|
| ('!' | '~' | 'delete' | '++' | '--' | '+' | '-') Expression
|
||||||
| Expression '**' Expression
|
| Expression '**' Expression
|
||||||
|
@ -104,4 +104,3 @@ Contents
|
|||||||
common-patterns.rst
|
common-patterns.rst
|
||||||
bugs.rst
|
bugs.rst
|
||||||
contributing.rst
|
contributing.rst
|
||||||
lll.rst
|
|
||||||
|
@ -174,7 +174,8 @@ Install it using ``brew``:
|
|||||||
# eg. Install 0.4.8
|
# eg. Install 0.4.8
|
||||||
brew install https://raw.githubusercontent.com/ethereum/homebrew-ethereum/77cce03da9f289e5a3ffe579840d3c5dc0a62717/solidity.rb
|
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
|
.. 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
|
0xa2
|
||||||
0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash>
|
0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash>
|
||||||
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
|
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
|
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.
|
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;
|
mapping(address => uint) shares;
|
||||||
/// Withdraw your share.
|
/// Withdraw your share.
|
||||||
function withdraw() public {
|
function withdraw() public {
|
||||||
(bool success,) = msg.sender.call.value(shares[msg.sender])("");
|
(bool success,) = msg.sender.call{value: shares[msg.sender]}("");
|
||||||
if (success)
|
if (success)
|
||||||
shares[msg.sender] = 0;
|
shares[msg.sender] = 0;
|
||||||
}
|
}
|
||||||
@ -149,7 +149,7 @@ Sending and Receiving Ether
|
|||||||
(for example in the "details" section in Remix).
|
(for example in the "details" section in Remix).
|
||||||
|
|
||||||
- There is a way to forward more gas to the receiving contract using
|
- 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
|
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
|
recipient to perform more expensive actions (and it returns a failure code
|
||||||
instead of automatically propagating the error). This might include calling back
|
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
|
Mapping types use the syntax ``mapping(_KeyType => _ValueType)`` and variables
|
||||||
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
|
of mapping type are declared using the syntax ``mapping(_KeyType => _ValueType) _VariableName``.
|
||||||
The ``_KeyType`` can be any
|
The ``_KeyType`` can be any
|
||||||
built-in value type plus ``bytes`` and ``string``. User-defined
|
built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined
|
||||||
or complex types such as contract types, enums, mappings, structs or array types
|
or complex types, such as mappings, structs or array types are not allowed.
|
||||||
apart from ``bytes`` and ``string`` are not allowed.
|
|
||||||
``_ValueType`` can be any type, including mappings, arrays and structs.
|
``_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
|
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
|
arbitrary arguments and would also handle a first argument of type
|
||||||
``bytes4`` differently. These edge cases were removed in version 0.5.0.
|
``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::
|
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::
|
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.
|
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.
|
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::
|
.. note::
|
||||||
All contracts can be converted to ``address`` type, so it is possible to query the balance of the
|
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.
|
* ``.address`` returns the address of the contract of the function.
|
||||||
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
|
* ``.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.
|
* ``.gas(uint)`` returns a callable function object which, when called, will send
|
||||||
* ``.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.
|
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::
|
Example that shows how to use the members::
|
||||||
|
|
||||||
@ -651,6 +656,8 @@ Example that shows how to use the members::
|
|||||||
|
|
||||||
function g() public {
|
function g() public {
|
||||||
this.f.gas(10).value(800)();
|
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", "strip", "debug" and "verboseDebug".
|
||||||
// "default" does not inject compiler-generated revert strings and keeps user-supplied ones.
|
// "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
|
// "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)
|
// "verboseDebug" even appends further information to user-supplied revert strings (not yet implemented)
|
||||||
"revertStrings": "default"
|
"revertStrings": "default"
|
||||||
}
|
}
|
||||||
@ -474,3 +474,240 @@ Error types
|
|||||||
11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue.
|
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.
|
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.
|
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::langutil;
|
||||||
using namespace solidity::util;
|
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)
|
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
||||||
{
|
{
|
||||||
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
|
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
|
||||||
m_deposit += _i.deposit();
|
m_deposit += _i.deposit();
|
||||||
m_items.emplace_back(_i);
|
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().setLocation(m_currentSourceLocation);
|
||||||
m_items.back().m_modifierDepth = m_currentModifierDepth;
|
m_items.back().m_modifierDepth = m_currentModifierDepth;
|
||||||
return back();
|
return m_items.back();
|
||||||
}
|
|
||||||
|
|
||||||
void Assembly::injectStart(AssemblyItem const& _i)
|
|
||||||
{
|
|
||||||
m_items.insert(m_items.begin(), _i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
unsigned Assembly::bytesRequired(unsigned subTagSize) const
|
||||||
@ -116,7 +69,7 @@ namespace
|
|||||||
|
|
||||||
string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location)
|
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 "";
|
return "";
|
||||||
|
|
||||||
auto it = _sourceCodes.find(_location.source->name());
|
auto it = _sourceCodes.find(_location.source->name());
|
||||||
@ -144,7 +97,7 @@ public:
|
|||||||
|
|
||||||
void feed(AssemblyItem const& _item)
|
void feed(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
if (!_item.location().isEmpty() && _item.location() != m_location)
|
if (_item.location().isValid() && _item.location() != m_location)
|
||||||
{
|
{
|
||||||
flush();
|
flush();
|
||||||
m_location = _item.location();
|
m_location = _item.location();
|
||||||
@ -188,12 +141,12 @@ public:
|
|||||||
|
|
||||||
void printLocation()
|
void printLocation()
|
||||||
{
|
{
|
||||||
if (!m_location.source && m_location.isEmpty())
|
if (!m_location.isValid())
|
||||||
return;
|
return;
|
||||||
m_out << m_prefix << " /*";
|
m_out << m_prefix << " /*";
|
||||||
if (m_location.source)
|
if (m_location.source)
|
||||||
m_out << " \"" + m_location.source->name() + "\"";
|
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 << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
|
||||||
m_out << " " << locationFromSources(m_sourceCodes, m_location);
|
m_out << " " << locationFromSources(m_sourceCodes, m_location);
|
||||||
m_out << " */" << endl;
|
m_out << " */" << endl;
|
||||||
@ -244,10 +197,11 @@ string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
|||||||
return tmp.str();
|
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;
|
Json::Value value;
|
||||||
value["name"] = _name;
|
value["name"] = _name;
|
||||||
|
value["source"] = _source;
|
||||||
value["begin"] = _begin;
|
value["begin"] = _begin;
|
||||||
value["end"] = _end;
|
value["end"] = _end;
|
||||||
if (!_value.empty())
|
if (!_value.empty())
|
||||||
@ -264,65 +218,79 @@ string Assembly::toStringInHex(u256 _value)
|
|||||||
return hexStr.str();
|
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 root;
|
||||||
|
|
||||||
Json::Value& collection = root[".code"] = Json::arrayValue;
|
Json::Value& collection = root[".code"] = Json::arrayValue;
|
||||||
for (AssemblyItem const& i: m_items)
|
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())
|
switch (i.type())
|
||||||
{
|
{
|
||||||
case Operation:
|
case Operation:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case Push:
|
case Push:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushString:
|
case PushString:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushTag:
|
case PushTag:
|
||||||
if (i.data() == 0)
|
if (i.data() == 0)
|
||||||
collection.append(
|
collection.append(
|
||||||
createJsonValue("PUSH [ErrorTag]", i.location().start, i.location().end, ""));
|
createJsonValue("PUSH [ErrorTag]", sourceIndex, i.location().start, i.location().end, ""));
|
||||||
else
|
else
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushSub:
|
case PushSub:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushSubSize:
|
case PushSubSize:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushProgramSize:
|
case PushProgramSize:
|
||||||
collection.append(
|
collection.append(
|
||||||
createJsonValue("PUSHSIZE", i.location().start, i.location().end));
|
createJsonValue("PUSHSIZE", sourceIndex, i.location().start, i.location().end));
|
||||||
break;
|
break;
|
||||||
case PushLibraryAddress:
|
case PushLibraryAddress:
|
||||||
collection.append(
|
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;
|
break;
|
||||||
case PushDeployTimeAddress:
|
case PushDeployTimeAddress:
|
||||||
collection.append(
|
collection.append(
|
||||||
createJsonValue("PUSHDEPLOYADDRESS", i.location().start, i.location().end)
|
createJsonValue("PUSHDEPLOYADDRESS", sourceIndex, i.location().start, i.location().end)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case Tag:
|
case Tag:
|
||||||
collection.append(
|
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(
|
collection.append(
|
||||||
createJsonValue("JUMPDEST", i.location().start, i.location().end));
|
createJsonValue("JUMPDEST", sourceIndex, i.location().start, i.location().end));
|
||||||
break;
|
break;
|
||||||
case PushData:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
assertThrow(false, InvalidOpcode, "");
|
assertThrow(false, InvalidOpcode, "");
|
||||||
@ -340,7 +308,7 @@ Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const
|
|||||||
{
|
{
|
||||||
std::stringstream hexStr;
|
std::stringstream hexStr;
|
||||||
hexStr << hex << i;
|
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 newPushLibraryAddress(std::string const& _identifier);
|
||||||
|
|
||||||
AssemblyItem const& append(AssemblyItem const& _i);
|
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)); }
|
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
|
||||||
|
|
||||||
template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
|
template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
|
||||||
@ -134,21 +133,9 @@ public:
|
|||||||
|
|
||||||
/// Create a JSON representation of the assembly.
|
/// Create a JSON representation of the assembly.
|
||||||
Json::Value assemblyJSON(
|
Json::Value assemblyJSON(
|
||||||
StringMap const& _sourceCodes = StringMap()
|
std::map<std::string, unsigned> const& _sourceIndices = std::map<std::string, unsigned>()
|
||||||
) const;
|
) 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:
|
protected:
|
||||||
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
/// 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
|
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
|
||||||
@ -158,7 +145,14 @@ protected:
|
|||||||
unsigned bytesRequired(unsigned subTagSize) const;
|
unsigned bytesRequired(unsigned subTagSize) const;
|
||||||
|
|
||||||
private:
|
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);
|
static std::string toStringInHex(u256 _value);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
#include <libsolutil/FixedHash.h>
|
#include <libsolutil/FixedHash.h>
|
||||||
|
#include <liblangutil/SourceLocation.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::evmasm;
|
using namespace solidity::evmasm;
|
||||||
|
using namespace solidity::langutil;
|
||||||
|
|
||||||
static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide");
|
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;
|
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 Assembly;
|
||||||
|
class AssemblyItem;
|
||||||
|
using AssemblyItems = std::vector<AssemblyItem>;
|
||||||
|
|
||||||
class AssemblyItem
|
class AssemblyItem
|
||||||
{
|
{
|
||||||
@ -122,6 +124,11 @@ public:
|
|||||||
}
|
}
|
||||||
bool operator!=(Instruction _instr) const { return !operator==(_instr); }
|
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
|
/// @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.
|
/// the value of a jump tag takes @a _addressLength bytes.
|
||||||
unsigned bytesRequired(unsigned _addressLength) const;
|
unsigned bytesRequired(unsigned _addressLength) const;
|
||||||
@ -157,8 +164,6 @@ private:
|
|||||||
mutable std::shared_ptr<u256> m_pushedValue;
|
mutable std::shared_ptr<u256> m_pushedValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
using AssemblyItems = std::vector<AssemblyItem>;
|
|
||||||
|
|
||||||
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
|
inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength)
|
||||||
{
|
{
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
|
@ -69,8 +69,8 @@ public:
|
|||||||
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
|
/// 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.
|
/// 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
|
/// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect
|
||||||
template <class _AssemblyItemIterator>
|
template <class AssemblyItemIterator>
|
||||||
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant);
|
AssemblyItemIterator feedItems(AssemblyItemIterator _iterator, AssemblyItemIterator _end, bool _msizeImportant);
|
||||||
|
|
||||||
/// @returns the resulting items after optimization.
|
/// @returns the resulting items after optimization.
|
||||||
AssemblyItems getOptimizedItems();
|
AssemblyItems getOptimizedItems();
|
||||||
@ -169,10 +169,10 @@ private:
|
|||||||
std::map<int, Id> m_targetStack;
|
std::map<int, Id> m_targetStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class _AssemblyItemIterator>
|
template <class AssemblyItemIterator>
|
||||||
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
|
AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
|
||||||
_AssemblyItemIterator _iterator,
|
AssemblyItemIterator _iterator,
|
||||||
_AssemblyItemIterator _end,
|
AssemblyItemIterator _end,
|
||||||
bool _msizeImportant
|
bool _msizeImportant
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#include <libevmasm/KnownState.h>
|
#include <libevmasm/KnownState.h>
|
||||||
|
|
||||||
#include <libsolutil/FixedHash.h>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
@ -182,7 +180,14 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
|
|||||||
case Instruction::EXP:
|
case Instruction::EXP:
|
||||||
gas = GasCosts::expGas;
|
gas = GasCosts::expGas;
|
||||||
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
|
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
|
else
|
||||||
gas += GasCosts::expByteGas(m_evmVersion) * 32;
|
gas += GasCosts::expByteGas(m_evmVersion) * 32;
|
||||||
break;
|
break;
|
||||||
|
@ -179,7 +179,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
|
|||||||
|
|
||||||
/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from
|
/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from
|
||||||
/// _this which is not in or not equal to the value in _other.
|
/// _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();)
|
for (auto it = _this.begin(); it != _this.end();)
|
||||||
if (_other.count(it->first) && _other.at(it->first) == it->second)
|
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
|
template<typename... Args> constexpr Pattern operator()(Args&&... _args) const
|
||||||
{
|
{
|
||||||
return {inst, {std::forward<Args>(_args)...}};
|
return {inst, {std::forward<Args>(_args)...}};
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PatternGeneratorInstance
|
struct PatternGeneratorInstance
|
||||||
@ -74,7 +74,7 @@ struct EVMBuiltins
|
|||||||
template<typename... Args> constexpr Pattern operator()(Args&&... _args) const
|
template<typename... Args> constexpr Pattern operator()(Args&&... _args) const
|
||||||
{
|
{
|
||||||
return {instruction, {std::forward<Args>(_args)...}};
|
return {instruction, {std::forward<Args>(_args)...}};
|
||||||
};
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ set(sources
|
|||||||
SemVerHandler.cpp
|
SemVerHandler.cpp
|
||||||
SemVerHandler.h
|
SemVerHandler.h
|
||||||
SourceLocation.h
|
SourceLocation.h
|
||||||
|
SourceLocation.cpp
|
||||||
SourceReferenceExtractor.cpp
|
SourceReferenceExtractor.cpp
|
||||||
SourceReferenceExtractor.h
|
SourceReferenceExtractor.h
|
||||||
SourceReferenceFormatter.cpp
|
SourceReferenceFormatter.cpp
|
||||||
|
@ -93,10 +93,13 @@ string CharStream::lineAtPosition(int _position) const
|
|||||||
lineStart = 0;
|
lineStart = 0;
|
||||||
else
|
else
|
||||||
lineStart++;
|
lineStart++;
|
||||||
return m_source.substr(
|
string line = m_source.substr(
|
||||||
lineStart,
|
lineStart,
|
||||||
min(m_source.find('\n', lineStart), m_source.size()) - 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
|
tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
|
||||||
|
@ -244,3 +244,12 @@ void ErrorReporter::docstringParsingError(string const& _description)
|
|||||||
_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 fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description);
|
||||||
|
|
||||||
void docstringParsingError(std::string const& _description);
|
void docstringParsingError(std::string const& _description);
|
||||||
|
void docstringParsingError(SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
ErrorList const& errors() const;
|
ErrorList const& errors() const;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ Error::Error(Type _type, SourceLocation const& _location, string const& _descrip
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_location.isEmpty())
|
if (_location.isValid())
|
||||||
*this << errinfo_sourceLocation(_location);
|
*this << errinfo_sourceLocation(_location);
|
||||||
if (!_description.empty())
|
if (!_description.empty())
|
||||||
*this << util::errinfo_comment(_description);
|
*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::Error(Error::Type _type, std::string const& _description, SourceLocation const& _location):
|
||||||
Error(_type)
|
Error(_type)
|
||||||
{
|
{
|
||||||
if (!_location.isEmpty())
|
if (_location.isValid())
|
||||||
*this << errinfo_sourceLocation(_location);
|
*this << errinfo_sourceLocation(_location);
|
||||||
*this << util::errinfo_comment(_description);
|
*this << util::errinfo_comment(_description);
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ struct CompilerError: virtual util::Exception {};
|
|||||||
struct InternalCompilerError: virtual util::Exception {};
|
struct InternalCompilerError: virtual util::Exception {};
|
||||||
struct FatalError: virtual util::Exception {};
|
struct FatalError: virtual util::Exception {};
|
||||||
struct UnimplementedFeatureError: 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.
|
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
|
||||||
#define solAssert(CONDITION, DESCRIPTION) \
|
#define solAssert(CONDITION, DESCRIPTION) \
|
||||||
@ -51,6 +52,9 @@ struct UnimplementedFeatureError: virtual util::Exception {};
|
|||||||
#define solUnimplemented(DESCRIPTION) \
|
#define solUnimplemented(DESCRIPTION) \
|
||||||
solUnimplementedAssert(false, DESCRIPTION)
|
solUnimplementedAssert(false, DESCRIPTION)
|
||||||
|
|
||||||
|
#define astAssert(CONDITION, DESCRIPTION) \
|
||||||
|
assertThrow(CONDITION, ::solidity::langutil::InvalidAstError, DESCRIPTION)
|
||||||
|
|
||||||
class Error: virtual public util::Exception
|
class Error: virtual public util::Exception
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -28,14 +28,9 @@ using namespace std;
|
|||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
|
|
||||||
int ParserBase::position() const
|
SourceLocation ParserBase::currentLocation() const
|
||||||
{
|
{
|
||||||
return m_scanner->currentLocation().start;
|
return m_scanner->currentLocation();
|
||||||
}
|
|
||||||
|
|
||||||
int ParserBase::endPosition() const
|
|
||||||
{
|
|
||||||
return m_scanner->currentLocation().end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Token ParserBase::currentToken() const
|
Token ParserBase::currentToken() const
|
||||||
@ -101,8 +96,8 @@ void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentN
|
|||||||
Token tok = m_scanner->currentToken();
|
Token tok = m_scanner->currentToken();
|
||||||
if (tok != _value)
|
if (tok != _value)
|
||||||
{
|
{
|
||||||
int startPosition = position();
|
SourceLocation errorLoc = currentLocation();
|
||||||
SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()};
|
int startPosition = errorLoc.start;
|
||||||
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
|
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
|
||||||
m_scanner->next();
|
m_scanner->next();
|
||||||
|
|
||||||
@ -150,7 +145,7 @@ void ParserBase::decreaseRecursionDepth()
|
|||||||
|
|
||||||
void ParserBase::parserWarning(string const& _description)
|
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)
|
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)
|
void ParserBase::parserError(string const& _description)
|
||||||
{
|
{
|
||||||
parserError(SourceLocation{position(), endPosition(), source()}, _description);
|
parserError(currentLocation(), _description);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParserBase::fatalParserError(string const& _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)
|
void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description)
|
||||||
|
@ -62,10 +62,8 @@ protected:
|
|||||||
ParserBase& m_parser;
|
ParserBase& m_parser;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Start position of the current token
|
/// Location of the current token
|
||||||
int position() const;
|
SourceLocation currentLocation() const;
|
||||||
/// End position of the current token
|
|
||||||
int endPosition() const;
|
|
||||||
|
|
||||||
///@{
|
///@{
|
||||||
///@name Helper functions
|
///@name Helper functions
|
||||||
|
@ -108,18 +108,18 @@ public:
|
|||||||
m_complete(false)
|
m_complete(false)
|
||||||
{
|
{
|
||||||
if (_type == LITERAL_TYPE_COMMENT)
|
if (_type == LITERAL_TYPE_COMMENT)
|
||||||
m_scanner->m_nextSkippedComment.literal.clear();
|
m_scanner->m_skippedComments[Scanner::NextNext].literal.clear();
|
||||||
else
|
else
|
||||||
m_scanner->m_nextToken.literal.clear();
|
m_scanner->m_tokens[Scanner::NextNext].literal.clear();
|
||||||
}
|
}
|
||||||
~LiteralScope()
|
~LiteralScope()
|
||||||
{
|
{
|
||||||
if (!m_complete)
|
if (!m_complete)
|
||||||
{
|
{
|
||||||
if (m_type == LITERAL_TYPE_COMMENT)
|
if (m_type == LITERAL_TYPE_COMMENT)
|
||||||
m_scanner->m_nextSkippedComment.literal.clear();
|
m_scanner->m_skippedComments[Scanner::NextNext].literal.clear();
|
||||||
else
|
else
|
||||||
m_scanner->m_nextToken.literal.clear();
|
m_scanner->m_tokens[Scanner::NextNext].literal.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void complete() { m_complete = true; }
|
void complete() { m_complete = true; }
|
||||||
@ -151,6 +151,7 @@ void Scanner::reset()
|
|||||||
skipWhitespace();
|
skipWhitespace();
|
||||||
next();
|
next();
|
||||||
next();
|
next();
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scanner::setPosition(size_t _offset)
|
void Scanner::setPosition(size_t _offset)
|
||||||
@ -158,6 +159,7 @@ void Scanner::setPosition(size_t _offset)
|
|||||||
m_char = m_source->setPosition(_offset);
|
m_char = m_source->setPosition(_offset);
|
||||||
scanToken();
|
scanToken();
|
||||||
next();
|
next();
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scanner::supportPeriodInIdentifier(bool _value)
|
void Scanner::supportPeriodInIdentifier(bool _value)
|
||||||
@ -222,13 +224,14 @@ void Scanner::addUnicodeAsUTF8(unsigned codepoint)
|
|||||||
void Scanner::rescan()
|
void Scanner::rescan()
|
||||||
{
|
{
|
||||||
size_t rollbackTo = 0;
|
size_t rollbackTo = 0;
|
||||||
if (m_skippedComment.literal.empty())
|
if (m_skippedComments[Current].literal.empty())
|
||||||
rollbackTo = m_currentToken.location.start;
|
rollbackTo = m_tokens[Current].location.start;
|
||||||
else
|
else
|
||||||
rollbackTo = m_skippedComment.location.start;
|
rollbackTo = m_skippedComments[Current].location.start;
|
||||||
m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo);
|
m_char = m_source->rollback(size_t(m_source->position()) - rollbackTo);
|
||||||
next();
|
next();
|
||||||
next();
|
next();
|
||||||
|
next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that tokens can be stored in a byte.
|
// Ensure that tokens can be stored in a byte.
|
||||||
@ -236,11 +239,14 @@ BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100);
|
|||||||
|
|
||||||
Token Scanner::next()
|
Token Scanner::next()
|
||||||
{
|
{
|
||||||
m_currentToken = m_nextToken;
|
m_tokens[Current] = std::move(m_tokens[Next]);
|
||||||
m_skippedComment = m_nextSkippedComment;
|
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();
|
scanToken();
|
||||||
|
|
||||||
return m_currentToken.token;
|
return m_tokens[Current].token;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token Scanner::selectToken(char _next, Token _then, Token _else)
|
Token Scanner::selectToken(char _next, Token _then, Token _else)
|
||||||
@ -300,19 +306,24 @@ bool Scanner::tryScanEndOfLine()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token Scanner::scanSingleLineDocComment()
|
int Scanner::scanSingleLineDocComment()
|
||||||
{
|
{
|
||||||
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
||||||
|
int endPosition = m_source->position();
|
||||||
advance(); //consume the last '/' at ///
|
advance(); //consume the last '/' at ///
|
||||||
|
|
||||||
skipWhitespaceExceptUnicodeLinebreak();
|
skipWhitespaceExceptUnicodeLinebreak();
|
||||||
|
|
||||||
while (!isSourcePastEndOfInput())
|
while (!isSourcePastEndOfInput())
|
||||||
{
|
{
|
||||||
|
endPosition = m_source->position();
|
||||||
if (tryScanEndOfLine())
|
if (tryScanEndOfLine())
|
||||||
{
|
{
|
||||||
// check if next line is also a documentation comment
|
// Check if next line is also a single-line comment.
|
||||||
skipWhitespace();
|
// If any whitespaces were skipped, use source position before.
|
||||||
|
if (!skipWhitespace())
|
||||||
|
endPosition = m_source->position();
|
||||||
|
|
||||||
if (!m_source->isPastEndOfInput(3) &&
|
if (!m_source->isPastEndOfInput(3) &&
|
||||||
m_source->get(0) == '/' &&
|
m_source->get(0) == '/' &&
|
||||||
m_source->get(1) == '/' &&
|
m_source->get(1) == '/' &&
|
||||||
@ -332,7 +343,7 @@ Token Scanner::scanSingleLineDocComment()
|
|||||||
advance();
|
advance();
|
||||||
}
|
}
|
||||||
literal.complete();
|
literal.complete();
|
||||||
return Token::CommentLiteral;
|
return endPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token Scanner::skipMultiLineComment()
|
Token Scanner::skipMultiLineComment()
|
||||||
@ -420,11 +431,10 @@ Token Scanner::scanSlash()
|
|||||||
else if (m_char == '/')
|
else if (m_char == '/')
|
||||||
{
|
{
|
||||||
// doxygen style /// comment
|
// doxygen style /// comment
|
||||||
Token comment;
|
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||||
m_nextSkippedComment.location.start = firstSlashPosition;
|
m_skippedComments[NextNext].location.source = m_source;
|
||||||
comment = scanSingleLineDocComment();
|
m_skippedComments[NextNext].token = Token::CommentLiteral;
|
||||||
m_nextSkippedComment.location.end = sourcePos();
|
m_skippedComments[NextNext].location.end = scanSingleLineDocComment();
|
||||||
m_nextSkippedComment.token = comment;
|
|
||||||
return Token::Whitespace;
|
return Token::Whitespace;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -447,10 +457,11 @@ Token Scanner::scanSlash()
|
|||||||
}
|
}
|
||||||
// we actually have a multiline documentation comment
|
// we actually have a multiline documentation comment
|
||||||
Token comment;
|
Token comment;
|
||||||
m_nextSkippedComment.location.start = firstSlashPosition;
|
m_skippedComments[NextNext].location.start = firstSlashPosition;
|
||||||
|
m_skippedComments[NextNext].location.source = m_source;
|
||||||
comment = scanMultiLineDocComment();
|
comment = scanMultiLineDocComment();
|
||||||
m_nextSkippedComment.location.end = sourcePos();
|
m_skippedComments[NextNext].location.end = sourcePos();
|
||||||
m_nextSkippedComment.token = comment;
|
m_skippedComments[NextNext].token = comment;
|
||||||
if (comment == Token::Illegal)
|
if (comment == Token::Illegal)
|
||||||
return Token::Illegal; // error already set
|
return Token::Illegal; // error already set
|
||||||
else
|
else
|
||||||
@ -467,11 +478,8 @@ Token Scanner::scanSlash()
|
|||||||
|
|
||||||
void Scanner::scanToken()
|
void Scanner::scanToken()
|
||||||
{
|
{
|
||||||
m_nextToken.error = ScannerError::NoError;
|
m_tokens[NextNext] = {};
|
||||||
m_nextToken.literal.clear();
|
m_skippedComments[NextNext] = {};
|
||||||
m_nextToken.extendedTokenInfo = make_tuple(0, 0);
|
|
||||||
m_nextSkippedComment.literal.clear();
|
|
||||||
m_nextSkippedComment.extendedTokenInfo = make_tuple(0, 0);
|
|
||||||
|
|
||||||
Token token;
|
Token token;
|
||||||
// M and N are for the purposes of grabbing different type sizes
|
// M and N are for the purposes of grabbing different type sizes
|
||||||
@ -480,7 +488,7 @@ void Scanner::scanToken()
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
// Remember the position of the next token
|
// Remember the position of the next token
|
||||||
m_nextToken.location.start = sourcePos();
|
m_tokens[NextNext].location.start = sourcePos();
|
||||||
switch (m_char)
|
switch (m_char)
|
||||||
{
|
{
|
||||||
case '"':
|
case '"':
|
||||||
@ -675,9 +683,10 @@ void Scanner::scanToken()
|
|||||||
// whitespace.
|
// whitespace.
|
||||||
}
|
}
|
||||||
while (token == Token::Whitespace);
|
while (token == Token::Whitespace);
|
||||||
m_nextToken.location.end = sourcePos();
|
m_tokens[NextNext].location.end = sourcePos();
|
||||||
m_nextToken.token = token;
|
m_tokens[NextNext].location.source = m_source;
|
||||||
m_nextToken.extendedTokenInfo = make_tuple(m, n);
|
m_tokens[NextNext].token = token;
|
||||||
|
m_tokens[NextNext].extendedTokenInfo = make_tuple(m, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Scanner::scanEscape()
|
bool Scanner::scanEscape()
|
||||||
@ -927,7 +936,7 @@ tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword()
|
|||||||
while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier))
|
while (isIdentifierPart(m_char) || (m_char == '.' && m_supportPeriodInIdentifier))
|
||||||
addLiteralCharAndAdvance();
|
addLiteralCharAndAdvance();
|
||||||
literal.complete();
|
literal.complete();
|
||||||
return TokenTraits::fromIdentifierOrKeyword(m_nextToken.literal);
|
return TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace solidity::langutil
|
} // namespace solidity::langutil
|
||||||
|
@ -98,6 +98,7 @@ public:
|
|||||||
std::string const& source() const noexcept { return m_source->source(); }
|
std::string const& source() const noexcept { return m_source->source(); }
|
||||||
|
|
||||||
std::shared_ptr<CharStream> charStream() noexcept { return m_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.
|
/// Resets the scanner as if newly constructed with _source as input.
|
||||||
void reset(CharStream _source);
|
void reset(CharStream _source);
|
||||||
@ -121,32 +122,32 @@ public:
|
|||||||
/// @returns the current token
|
/// @returns the current token
|
||||||
Token currentToken() const
|
Token currentToken() const
|
||||||
{
|
{
|
||||||
return m_currentToken.token;
|
return m_tokens[Current].token;
|
||||||
}
|
}
|
||||||
ElementaryTypeNameToken currentElementaryTypeNameToken() const
|
ElementaryTypeNameToken currentElementaryTypeNameToken() const
|
||||||
{
|
{
|
||||||
unsigned firstSize;
|
unsigned firstSize;
|
||||||
unsigned secondSize;
|
unsigned secondSize;
|
||||||
std::tie(firstSize, secondSize) = m_currentToken.extendedTokenInfo;
|
std::tie(firstSize, secondSize) = m_tokens[Current].extendedTokenInfo;
|
||||||
return ElementaryTypeNameToken(m_currentToken.token, firstSize, secondSize);
|
return ElementaryTypeNameToken(m_tokens[Current].token, firstSize, secondSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceLocation currentLocation() const { return m_currentToken.location; }
|
SourceLocation currentLocation() const { return m_tokens[Current].location; }
|
||||||
std::string const& currentLiteral() const { return m_currentToken.literal; }
|
std::string const& currentLiteral() const { return m_tokens[Current].literal; }
|
||||||
std::tuple<unsigned, unsigned> const& currentTokenInfo() const { return m_currentToken.extendedTokenInfo; }
|
std::tuple<unsigned, unsigned> const& currentTokenInfo() const { return m_tokens[Current].extendedTokenInfo; }
|
||||||
|
|
||||||
/// Retrieves the last error that occurred during lexical analysis.
|
/// Retrieves the last error that occurred during lexical analysis.
|
||||||
/// @note If no error occurred, the value is undefined.
|
/// @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
|
///@name Information about the current comment token
|
||||||
|
|
||||||
SourceLocation currentCommentLocation() const { return m_skippedComment.location; }
|
SourceLocation currentCommentLocation() const { return m_skippedComments[Current].location; }
|
||||||
std::string const& currentCommentLiteral() const { return m_skippedComment.literal; }
|
std::string const& currentCommentLiteral() const { return m_skippedComments[Current].literal; }
|
||||||
/// Called by the parser during FunctionDefinition parsing to clear the current comment
|
/// 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
|
///@name Information about the next token
|
||||||
|
|
||||||
/// @returns the next token without advancing input.
|
/// @returns the next token without advancing input.
|
||||||
Token peekNextToken() const { return m_nextToken.token; }
|
Token peekNextToken() const { return m_tokens[Next].token; }
|
||||||
SourceLocation peekLocation() const { return m_nextToken.location; }
|
SourceLocation peekLocation() const { return m_tokens[Next].location; }
|
||||||
std::string const& peekLiteral() const { return m_nextToken.literal; }
|
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.
|
/// Do only use in error cases, they are quite expensive.
|
||||||
std::string lineAtPosition(int _position) const { return m_source->lineAtPosition(_position); }
|
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::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:
|
private:
|
||||||
inline Token setError(ScannerError _error) noexcept
|
inline Token setError(ScannerError _error) noexcept
|
||||||
{
|
{
|
||||||
m_nextToken.error = _error;
|
m_tokens[NextNext].error = _error;
|
||||||
return Token::Illegal;
|
return Token::Illegal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,8 +189,8 @@ private:
|
|||||||
|
|
||||||
///@{
|
///@{
|
||||||
///@name Literal buffer support
|
///@name Literal buffer support
|
||||||
inline void addLiteralChar(char c) { m_nextToken.literal.push_back(c); }
|
inline void addLiteralChar(char c) { m_tokens[NextNext].literal.push_back(c); }
|
||||||
inline void addCommentLiteralChar(char c) { m_nextSkippedComment.literal.push_back(c); }
|
inline void addCommentLiteralChar(char c) { m_skippedComments[NextNext].literal.push_back(c); }
|
||||||
inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); }
|
inline void addLiteralCharAndAdvance() { addLiteralChar(m_char); advance(); }
|
||||||
void addUnicodeAsUTF8(unsigned codepoint);
|
void addUnicodeAsUTF8(unsigned codepoint);
|
||||||
///@}
|
///@}
|
||||||
@ -233,7 +230,8 @@ private:
|
|||||||
|
|
||||||
Token scanString();
|
Token scanString();
|
||||||
Token scanHexString();
|
Token scanHexString();
|
||||||
Token scanSingleLineDocComment();
|
/// Scans a single line comment and returns its corrected end position.
|
||||||
|
int scanSingleLineDocComment();
|
||||||
Token scanMultiLineDocComment();
|
Token scanMultiLineDocComment();
|
||||||
/// Scans a slash '/' and depending on the characters returns the appropriate token
|
/// Scans a slash '/' and depending on the characters returns the appropriate token
|
||||||
Token scanSlash();
|
Token scanSlash();
|
||||||
@ -252,11 +250,10 @@ private:
|
|||||||
|
|
||||||
bool m_supportPeriodInIdentifier = false;
|
bool m_supportPeriodInIdentifier = false;
|
||||||
|
|
||||||
TokenDesc m_skippedComment; // desc for current skipped comment
|
enum TokenIndex { Current, Next, NextNext };
|
||||||
TokenDesc m_nextSkippedComment; // desc for next skipped comment
|
|
||||||
|
|
||||||
TokenDesc m_currentToken; // desc for current token (as returned by Next())
|
TokenDesc m_skippedComments[3] = {}; // desc for the current, next and nextnext skipped comment
|
||||||
TokenDesc m_nextToken; // desc for next token (one token look-ahead)
|
TokenDesc m_tokens[3] = {}; // desc for the current, next and nextnext token
|
||||||
|
|
||||||
std::shared_ptr<CharStream> m_source;
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <libsolutil/Assertions.h>
|
#include <libsolutil/Assertions.h>
|
||||||
#include <libsolutil/Common.h> // defines noexcept macro for MSVC
|
|
||||||
#include <libsolutil/Exceptions.h>
|
#include <libsolutil/Exceptions.h>
|
||||||
|
|
||||||
#include <liblangutil/CharStream.h>
|
#include <liblangutil/CharStream.h>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <ostream>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace solidity::langutil
|
namespace solidity::langutil
|
||||||
{
|
{
|
||||||
@ -46,16 +45,44 @@ struct SourceLocation
|
|||||||
return source.get() == _other.source.get() && start == _other.start && end == _other.end;
|
return source.get() == _other.source.get() && start == _other.start && end == _other.end;
|
||||||
}
|
}
|
||||||
bool operator!=(SourceLocation const& _other) const { return !operator==(_other); }
|
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
|
std::string text() const
|
||||||
{
|
{
|
||||||
assertThrow(source, SourceLocationError, "Requested text from null source.");
|
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(start <= end, SourceLocationError, "Invalid source location.");
|
||||||
assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
|
assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
|
||||||
return source->source().substr(start, end - start);
|
return source->source().substr(start, end - start);
|
||||||
@ -86,40 +113,20 @@ struct SourceLocation
|
|||||||
std::shared_ptr<CharStream> source;
|
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).
|
/// Stream output for Location (used e.g. in boost exceptions).
|
||||||
inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location)
|
inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
if (_location.isEmpty())
|
if (!_location.isValid())
|
||||||
return _out << "NO_LOCATION_SPECIFIED";
|
return _out << "NO_LOCATION_SPECIFIED";
|
||||||
|
|
||||||
if (_location.source)
|
if (_location.source)
|
||||||
_out << _location.source->name();
|
_out << _location.source->name();
|
||||||
|
|
||||||
_out << "[" << _location.start << "," << _location.end << ")";
|
_out << "[" << _location.start << "," << _location.end << "]";
|
||||||
|
|
||||||
return _out;
|
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
|
if (!_location || !_location->source.get()) // Nothing we can extract here
|
||||||
return SourceReference::MessageOnly(std::move(message));
|
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());
|
return SourceReference::MessageOnly(std::move(message), _location->source->name());
|
||||||
|
|
||||||
shared_ptr<CharStream> const& source = _location->source;
|
shared_ptr<CharStream> const& source = _location->source;
|
||||||
|
@ -69,6 +69,8 @@ void SourceReferenceFormatter::printSourceName(SourceReference const& _ref)
|
|||||||
{
|
{
|
||||||
if (_ref.position.line != -1)
|
if (_ref.position.line != -1)
|
||||||
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 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)
|
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)
|
void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _ref)
|
||||||
{
|
{
|
||||||
if (_ref.position.line < 0)
|
if (_ref.sourceName.empty())
|
||||||
return; // Nothing we can print here
|
return; // Nothing we can print here
|
||||||
|
|
||||||
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
|
int const leftpad = static_cast<int>(log10(max(_ref.position.line, 1))) + 1;
|
||||||
|
|
||||||
// line 0: source name
|
// line 0: source name
|
||||||
frameColored() << string(leftpad, ' ') << "--> ";
|
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';
|
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
|
||||||
|
|
||||||
if (!_ref.multiline)
|
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/ASTAnnotations.h
|
||||||
ast/ASTEnums.h
|
ast/ASTEnums.h
|
||||||
ast/ASTForward.h
|
ast/ASTForward.h
|
||||||
|
ast/AsmJsonImporter.cpp
|
||||||
|
ast/AsmJsonImporter.h
|
||||||
ast/ASTJsonConverter.cpp
|
ast/ASTJsonConverter.cpp
|
||||||
ast/ASTJsonConverter.h
|
ast/ASTJsonConverter.h
|
||||||
ast/ASTUtils.cpp
|
ast/ASTUtils.cpp
|
||||||
ast/ASTUtils.h
|
ast/ASTUtils.h
|
||||||
|
ast/ASTJsonImporter.cpp
|
||||||
|
ast/ASTJsonImporter.h
|
||||||
ast/ASTVisitor.h
|
ast/ASTVisitor.h
|
||||||
ast/ExperimentalFeatures.h
|
ast/ExperimentalFeatures.h
|
||||||
ast/Types.cpp
|
ast/Types.cpp
|
||||||
@ -75,8 +79,9 @@ set(sources
|
|||||||
codegen/ir/IRGeneratorForStatements.h
|
codegen/ir/IRGeneratorForStatements.h
|
||||||
codegen/ir/IRGenerationContext.cpp
|
codegen/ir/IRGenerationContext.cpp
|
||||||
codegen/ir/IRGenerationContext.h
|
codegen/ir/IRGenerationContext.h
|
||||||
codegen/ir/IRLValue.cpp
|
|
||||||
codegen/ir/IRLValue.h
|
codegen/ir/IRLValue.h
|
||||||
|
codegen/ir/IRVariable.cpp
|
||||||
|
codegen/ir/IRVariable.h
|
||||||
formal/BMC.cpp
|
formal/BMC.cpp
|
||||||
formal/BMC.h
|
formal/BMC.h
|
||||||
formal/CHC.cpp
|
formal/CHC.cpp
|
||||||
|
@ -163,7 +163,7 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
|||||||
std::set<SourceLocation> unreachable;
|
std::set<SourceLocation> unreachable;
|
||||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
||||||
[&](CFGNode const* _node, auto&& _addChild) {
|
[&](CFGNode const* _node, auto&& _addChild) {
|
||||||
if (!reachable.count(_node) && !_node->location.isEmpty())
|
if (!reachable.count(_node) && _node->location.isValid())
|
||||||
unreachable.insert(_node->location);
|
unreachable.insert(_node->location);
|
||||||
for (CFGNode const* entry: _node->entries)
|
for (CFGNode const* entry: _node->entries)
|
||||||
_addChild(entry);
|
_addChild(entry);
|
||||||
|
@ -47,6 +47,6 @@ FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const
|
|||||||
|
|
||||||
CFGNode* CFG::NodeContainer::newNode()
|
CFGNode* CFG::NodeContainer::newNode()
|
||||||
{
|
{
|
||||||
m_nodes.emplace_back(new CFGNode());
|
m_nodes.emplace_back(std::make_unique<CFGNode>());
|
||||||
return m_nodes.back().get();
|
return m_nodes.back().get();
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,8 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
|
|||||||
|
|
||||||
void DocStringAnalyser::checkParameters(
|
void DocStringAnalyser::checkParameters(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumented const& _node,
|
||||||
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
set<string> validParams;
|
set<string> validParams;
|
||||||
@ -86,6 +87,7 @@ void DocStringAnalyser::checkParameters(
|
|||||||
for (auto i = paramRange.first; i != paramRange.second; ++i)
|
for (auto i = paramRange.first; i != paramRange.second; ++i)
|
||||||
if (!validParams.count(i->second.paramName))
|
if (!validParams.count(i->second.paramName))
|
||||||
appendError(
|
appendError(
|
||||||
|
_node.documentation()->location(),
|
||||||
"Documented parameter \"" +
|
"Documented parameter \"" +
|
||||||
i->second.paramName +
|
i->second.paramName +
|
||||||
"\" not found in the parameter list of the function."
|
"\" not found in the parameter list of the function."
|
||||||
@ -95,37 +97,37 @@ void DocStringAnalyser::checkParameters(
|
|||||||
|
|
||||||
void DocStringAnalyser::handleConstructor(
|
void DocStringAnalyser::handleConstructor(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "param"};
|
static set<string> const validTags = set<string>{"author", "dev", "notice", "param"};
|
||||||
parseDocStrings(_node, _annotation, validTags, "constructor");
|
parseDocStrings(_node, _annotation, validTags, "constructor");
|
||||||
checkParameters(_callable, _annotation);
|
checkParameters(_callable, _node, _annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocStringAnalyser::handleCallable(
|
void DocStringAnalyser::handleCallable(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||||
checkParameters(_callable, _annotation);
|
checkParameters(_callable, _node, _annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocStringAnalyser::parseDocStrings(
|
void DocStringAnalyser::parseDocStrings(
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation,
|
StructurallyDocumentedAnnotation& _annotation,
|
||||||
set<string> const& _validTags,
|
set<string> const& _validTags,
|
||||||
string const& _nodeName
|
string const& _nodeName
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
DocStringParser parser;
|
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;
|
m_errorOccured = true;
|
||||||
_annotation.docTags = parser.tags();
|
_annotation.docTags = parser.tags();
|
||||||
}
|
}
|
||||||
@ -134,7 +136,10 @@ void DocStringAnalyser::parseDocStrings(
|
|||||||
for (auto const& docTag: _annotation.docTags)
|
for (auto const& docTag: _annotation.docTags)
|
||||||
{
|
{
|
||||||
if (!_validTags.count(docTag.first))
|
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
|
else
|
||||||
if (docTag.first == "return")
|
if (docTag.first == "return")
|
||||||
{
|
{
|
||||||
@ -145,14 +150,18 @@ void DocStringAnalyser::parseDocStrings(
|
|||||||
string firstWord = content.substr(0, content.find_first_of(" \t"));
|
string firstWord = content.substr(0, content.find_first_of(" \t"));
|
||||||
|
|
||||||
if (returnTagsVisited > function->returnParameters().size())
|
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."
|
" exceedes the number of return parameters."
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto parameter = function->returnParameters().at(returnTagsVisited - 1);
|
auto parameter = function->returnParameters().at(returnTagsVisited - 1);
|
||||||
if (!parameter->name().empty() && parameter->name() != firstWord)
|
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."
|
" 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_errorOccured = true;
|
||||||
m_errorReporter.docstringParsingError(_description);
|
m_errorReporter.docstringParsingError(_location, _description);
|
||||||
}
|
}
|
||||||
|
@ -51,29 +51,30 @@ private:
|
|||||||
|
|
||||||
void checkParameters(
|
void checkParameters(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumented const& _node,
|
||||||
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
);
|
);
|
||||||
|
|
||||||
void handleConstructor(
|
void handleConstructor(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
);
|
);
|
||||||
|
|
||||||
void handleCallable(
|
void handleCallable(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
);
|
);
|
||||||
|
|
||||||
void parseDocStrings(
|
void parseDocStrings(
|
||||||
Documented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
DocumentedAnnotation& _annotation,
|
StructurallyDocumentedAnnotation& _annotation,
|
||||||
std::set<std::string> const& _validTags,
|
std::set<std::string> const& _validTags,
|
||||||
std::string const& _nodeName
|
std::string const& _nodeName
|
||||||
);
|
);
|
||||||
|
|
||||||
void appendError(std::string const& _description);
|
void appendError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
bool m_errorOccured = false;
|
bool m_errorOccured = false;
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
@ -33,10 +33,44 @@ using namespace std;
|
|||||||
namespace solidity::frontend
|
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()
|
inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariables()
|
||||||
{
|
{
|
||||||
static auto const magicVarDecl = [](string const& _name, Type const* _type) {
|
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 {
|
return {
|
||||||
@ -97,7 +131,7 @@ vector<Declaration const*> GlobalContext::declarations() const
|
|||||||
MagicVariableDeclaration const* GlobalContext::currentThis() const
|
MagicVariableDeclaration const* GlobalContext::currentThis() const
|
||||||
{
|
{
|
||||||
if (!m_thisPointer[m_currentContract])
|
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();
|
return m_thisPointer[m_currentContract].get();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -105,7 +139,7 @@ MagicVariableDeclaration const* GlobalContext::currentThis() const
|
|||||||
MagicVariableDeclaration const* GlobalContext::currentSuper() const
|
MagicVariableDeclaration const* GlobalContext::currentSuper() const
|
||||||
{
|
{
|
||||||
if (!m_superPointer[m_currentContract])
|
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();
|
return m_superPointer[m_currentContract].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,13 +405,13 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract)
|
|||||||
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
|
_contract.annotation().contractDependencies.insert(result.begin() + 1, result.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class _T>
|
template <class T>
|
||||||
vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMerge)
|
vector<T const*> NameAndTypeResolver::cThreeMerge(list<list<T const*>>& _toMerge)
|
||||||
{
|
{
|
||||||
// returns true iff _candidate appears only as last element of the lists
|
// 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(), "");
|
solAssert(!bases.empty(), "");
|
||||||
if (find(++bases.begin(), bases.end(), _candidate) != bases.end())
|
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;
|
return true;
|
||||||
};
|
};
|
||||||
// returns the next candidate to append to the linearized list or nullptr on failure
|
// 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(), "");
|
solAssert(!bases.empty(), "");
|
||||||
if (appearsOnlyAtHead(bases.front()))
|
if (appearsOnlyAtHead(bases.front()))
|
||||||
@ -431,7 +431,7 @@ vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _toMer
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
};
|
};
|
||||||
// removes the given contract from all lists
|
// 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();)
|
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(); });
|
_toMerge.remove_if([](list<T const*> const& _bases) { return _bases.empty(); });
|
||||||
vector<_T const*> result;
|
vector<T const*> result;
|
||||||
while (!_toMerge.empty())
|
while (!_toMerge.empty())
|
||||||
{
|
{
|
||||||
_T const* candidate = nextCandidate();
|
T const* candidate = nextCandidate();
|
||||||
if (!candidate)
|
if (!candidate)
|
||||||
return vector<_T const*>();
|
return vector<T const*>();
|
||||||
result.push_back(candidate);
|
result.push_back(candidate);
|
||||||
removeCandidate(candidate);
|
removeCandidate(candidate);
|
||||||
}
|
}
|
||||||
@ -625,7 +625,6 @@ bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function)
|
|||||||
{
|
{
|
||||||
registerDeclaration(_function, true);
|
registerDeclaration(_function, true);
|
||||||
m_currentFunction = &_function;
|
m_currentFunction = &_function;
|
||||||
_function.annotation().contract = m_currentContract;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,6 +759,7 @@ void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaratio
|
|||||||
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
|
registerDeclaration(*m_scopes[m_currentScope], _declaration, nullptr, nullptr, warnAboutShadowing, inactive, m_errorReporter);
|
||||||
|
|
||||||
_declaration.annotation().scope = m_currentScope;
|
_declaration.annotation().scope = m_currentScope;
|
||||||
|
_declaration.annotation().contract = m_currentContract;
|
||||||
if (_opensScope)
|
if (_opensScope)
|
||||||
enterNewSubScope(_declaration);
|
enterNewSubScope(_declaration);
|
||||||
}
|
}
|
||||||
|
@ -122,8 +122,8 @@ private:
|
|||||||
void linearizeBaseContracts(ContractDefinition& _contract);
|
void linearizeBaseContracts(ContractDefinition& _contract);
|
||||||
/// Computes the C3-merge of the given list of lists of bases.
|
/// 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.
|
/// @returns the linearized vector or an empty vector if linearization is not possible.
|
||||||
template <class _T>
|
template <class T>
|
||||||
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
|
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,
|
/// 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
|
/// 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 =
|
Declaration const* baseDecl =
|
||||||
specifier->name().annotation().referencedDeclaration;
|
specifier->name().annotation().referencedDeclaration;
|
||||||
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
|
auto contract = dynamic_cast<ContractDefinition const*>(baseDecl);
|
||||||
solAssert(contract, "contract is null");
|
if (contract)
|
||||||
resolvedContracts.emplace_back(contract);
|
resolvedContracts.emplace_back(contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedContracts;
|
return resolvedContracts;
|
||||||
|
@ -129,6 +129,7 @@ private:
|
|||||||
class OverrideChecker
|
class OverrideChecker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
|
||||||
|
|
||||||
/// @param _errorReporter provides the error logging functionality.
|
/// @param _errorReporter provides the error logging functionality.
|
||||||
explicit OverrideChecker(langutil::ErrorReporter& _errorReporter):
|
explicit OverrideChecker(langutil::ErrorReporter& _errorReporter):
|
||||||
@ -137,12 +138,17 @@ public:
|
|||||||
|
|
||||||
void check(ContractDefinition const& _contract);
|
void check(ContractDefinition const& _contract);
|
||||||
|
|
||||||
private:
|
|
||||||
struct CompareByID
|
struct CompareByID
|
||||||
{
|
{
|
||||||
bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const;
|
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);
|
void checkIllegalOverrides(ContractDefinition const& _contract);
|
||||||
/// Performs various checks related to @a _overriding overriding @a _super like
|
/// Performs various checks related to @a _overriding overriding @a _super like
|
||||||
/// different return type, invalid visibility change, etc.
|
/// different return type, invalid visibility change, etc.
|
||||||
@ -174,15 +180,8 @@ private:
|
|||||||
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
|
/// Resolves an override list of UserDefinedTypeNames to a list of contracts.
|
||||||
std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
|
std::set<ContractDefinition const*, CompareByID> resolveOverrideList(OverrideSpecifier const& _overrides) const;
|
||||||
|
|
||||||
using OverrideProxyBySignatureMultiSet = std::multiset<OverrideProxy, OverrideProxy::CompareBySignature>;
|
|
||||||
|
|
||||||
void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited);
|
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;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
/// Cache for inheritedFunctions().
|
/// Cache for inheritedFunctions().
|
||||||
|
@ -133,9 +133,9 @@ struct ConstStateVarCircularReferenceChecker: public PostTypeChecker::Checker
|
|||||||
|
|
||||||
bool visit(VariableDeclaration const& _variable) override
|
bool visit(VariableDeclaration const& _variable) override
|
||||||
{
|
{
|
||||||
solAssert(!m_currentConstVariable, "");
|
|
||||||
if (_variable.isConstant())
|
if (_variable.isConstant())
|
||||||
{
|
{
|
||||||
|
solAssert(!m_currentConstVariable, "");
|
||||||
m_currentConstVariable = &_variable;
|
m_currentConstVariable = &_variable;
|
||||||
m_constVariables.push_back(&_variable);
|
m_constVariables.push_back(&_variable);
|
||||||
}
|
}
|
||||||
|
@ -224,15 +224,36 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
|
|||||||
_typeName.annotation().type = TypeProvider::function(_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;
|
if (auto const* typeName = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
|
||||||
TypePointer valueType = _typeName.valueType().annotation().type;
|
{
|
||||||
|
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.
|
// Convert key type to memory.
|
||||||
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);
|
||||||
|
|
||||||
// Convert value type to storage reference.
|
// Convert value type to storage reference.
|
||||||
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
|
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)
|
void ReferencesResolver::endVisit(ArrayTypeName const& _typeName)
|
||||||
@ -270,87 +291,10 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
{
|
{
|
||||||
m_resolver.warnVariablesNamedLikeInstructions();
|
m_resolver.warnVariablesNamedLikeInstructions();
|
||||||
|
|
||||||
// Errors created in this stage are completely ignored because we do not yet know
|
m_yulAnnotation = &_inlineAssembly.annotation();
|
||||||
// the type and size of external identifiers, which would result in false errors.
|
(*this)(_inlineAssembly.operations());
|
||||||
// The only purpose of this step is to fill the inline assembly annotation with
|
m_yulAnnotation = nullptr;
|
||||||
// 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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,6 +412,90 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
|
|||||||
_variable.annotation().type = type;
|
_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)
|
void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description)
|
||||||
{
|
{
|
||||||
m_errorOccurred = true;
|
m_errorOccurred = true;
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
#include <libsolidity/ast/ASTAnnotations.h>
|
#include <libsolidity/ast/ASTAnnotations.h>
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <list>
|
#include <list>
|
||||||
@ -45,7 +46,7 @@ class NameAndTypeResolver;
|
|||||||
* Resolves references to declarations (of variables and types) and also establishes the link
|
* Resolves references to declarations (of variables and types) and also establishes the link
|
||||||
* between a return statement and the return parameter list.
|
* between a return statement and the return parameter list.
|
||||||
*/
|
*/
|
||||||
class ReferencesResolver: private ASTConstVisitor
|
class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ReferencesResolver(
|
ReferencesResolver(
|
||||||
@ -64,6 +65,9 @@ public:
|
|||||||
bool resolve(ASTNode const& _root);
|
bool resolve(ASTNode const& _root);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using yul::ASTWalker::visit;
|
||||||
|
using yul::ASTWalker::operator();
|
||||||
|
|
||||||
bool visit(Block const& _block) override;
|
bool visit(Block const& _block) override;
|
||||||
void endVisit(Block const& _block) override;
|
void endVisit(Block const& _block) override;
|
||||||
bool visit(ForStatement const& _for) override;
|
bool visit(ForStatement const& _for) override;
|
||||||
@ -77,12 +81,16 @@ private:
|
|||||||
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
void endVisit(ModifierDefinition const& _modifierDefinition) override;
|
||||||
void endVisit(UserDefinedTypeName const& _typeName) override;
|
void endVisit(UserDefinedTypeName const& _typeName) override;
|
||||||
void endVisit(FunctionTypeName 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;
|
void endVisit(ArrayTypeName const& _typeName) override;
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||||
bool visit(Return const& _return) override;
|
bool visit(Return const& _return) override;
|
||||||
void endVisit(VariableDeclaration const& _variable) 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.
|
/// Adds a new error to the list of errors.
|
||||||
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
void typeError(langutil::SourceLocation const& _location, std::string const& _description);
|
||||||
|
|
||||||
@ -105,6 +113,9 @@ private:
|
|||||||
std::vector<ParameterList const*> m_returnParameters;
|
std::vector<ParameterList const*> m_returnParameters;
|
||||||
bool const m_resolveInsideCode;
|
bool const m_resolveInsideCode;
|
||||||
bool m_errorOccurred = false;
|
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()) +
|
to_string(recommendedVersion.patch()) +
|
||||||
string(";\"");
|
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;
|
m_sourceUnit = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -237,8 +237,8 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
|
|||||||
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
|
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
|
||||||
solAssert(base, "Base contract not available.");
|
solAssert(base, "Base contract not available.");
|
||||||
|
|
||||||
if (m_scope->isInterface())
|
if (m_scope->isInterface() && !base->isInterface())
|
||||||
m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit.");
|
m_errorReporter.typeError(_inheritance.location(), "Interfaces can only inherit from other interfaces.");
|
||||||
|
|
||||||
if (base->isLibrary())
|
if (base->isLibrary())
|
||||||
m_errorReporter.typeError(_inheritance.location(), "Libraries cannot be inherited from.");
|
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.");
|
m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables.");
|
||||||
return size_t(-1);
|
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.");
|
if (var->isStateVariable())
|
||||||
return size_t(-1);
|
{
|
||||||
|
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())
|
else if (!var->isConstant() && var->isStateVariable())
|
||||||
@ -1695,11 +1705,20 @@ void TypeChecker::typeCheckFunctionCall(
|
|||||||
{
|
{
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
_functionCall.location(),
|
_functionCall.location(),
|
||||||
"Cannot call function via contract name."
|
"Cannot call function via contract type name."
|
||||||
);
|
);
|
||||||
return;
|
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
|
// Check for unsupported use of bare static call
|
||||||
if (
|
if (
|
||||||
@ -2145,7 +2164,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
|
m_errorReporter.fatalTypeError(_functionCall.location(), "Type is not callable");
|
||||||
funcCallAnno.kind = FunctionCallKind::Unset;
|
funcCallAnno.kind = FunctionCallKind::Unset;
|
||||||
funcCallAnno.isPure = argumentsArePure;
|
funcCallAnno.isPure = argumentsArePure;
|
||||||
break;
|
break;
|
||||||
@ -2215,6 +2234,124 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
return false;
|
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)
|
void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||||
{
|
{
|
||||||
TypePointer type = _newExpression.typeName().annotation().type;
|
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))
|
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
|
||||||
{
|
{
|
||||||
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
|
if (ContractType const* contractType = dynamic_cast<decltype(contractType)>(typeType->actualType()))
|
||||||
|
{
|
||||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
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
|
// 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 (auto tt = dynamic_cast<TypeType const*>(exprType))
|
||||||
if (tt->actualType()->category() == Type::Category::Enum)
|
if (tt->actualType()->category() == Type::Category::Enum)
|
||||||
annotation.isPure = true;
|
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 (auto magicType = dynamic_cast<MagicType const*>(exprType))
|
||||||
{
|
{
|
||||||
if (magicType->kind() == MagicType::Kind::ABI)
|
if (magicType->kind() == MagicType::Kind::ABI)
|
||||||
@ -2635,7 +2794,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
|||||||
SecondarySourceLocation ssl;
|
SecondarySourceLocation ssl;
|
||||||
|
|
||||||
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
for (Declaration const* declaration: annotation.overloadedDeclarations)
|
||||||
if (declaration->location().isEmpty())
|
if (!declaration->location().isValid())
|
||||||
{
|
{
|
||||||
// Try to re-construct function definition
|
// Try to re-construct function definition
|
||||||
string description;
|
string description;
|
||||||
@ -2660,8 +2819,7 @@ bool TypeChecker::visit(Identifier const& _identifier)
|
|||||||
);
|
);
|
||||||
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
annotation.isLValue = annotation.referencedDeclaration->isLValue();
|
||||||
annotation.type = annotation.referencedDeclaration->type();
|
annotation.type = annotation.referencedDeclaration->type();
|
||||||
if (!annotation.type)
|
solAssert(annotation.type, "Declaration referenced before type could be determined.");
|
||||||
m_errorReporter.fatalTypeError(_identifier.location(), "Declaration referenced before type could be determined.");
|
|
||||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
|
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(annotation.referencedDeclaration))
|
||||||
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
annotation.isPure = annotation.isConstant = variableDeclaration->isConstant();
|
||||||
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
else if (dynamic_cast<MagicVariableDeclaration const*>(annotation.referencedDeclaration))
|
||||||
|
@ -135,6 +135,7 @@ private:
|
|||||||
void endVisit(BinaryOperation const& _operation) override;
|
void endVisit(BinaryOperation const& _operation) override;
|
||||||
bool visit(UnaryOperation const& _operation) override;
|
bool visit(UnaryOperation const& _operation) override;
|
||||||
bool visit(FunctionCall const& _functionCall) override;
|
bool visit(FunctionCall const& _functionCall) override;
|
||||||
|
bool visit(FunctionCallOptions const& _functionCallOptions) override;
|
||||||
void endVisit(NewExpression const& _newExpression) override;
|
void endVisit(NewExpression const& _newExpression) override;
|
||||||
bool visit(MemberAccess const& _memberAccess) override;
|
bool visit(MemberAccess const& _memberAccess) override;
|
||||||
bool visit(IndexAccess const& _indexAccess) override;
|
bool visit(IndexAccess const& _indexAccess) override;
|
||||||
|
@ -35,31 +35,12 @@ using namespace std;
|
|||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
|
||||||
class IDDispenser
|
ASTNode::ASTNode(int64_t _id, SourceLocation const& _location):
|
||||||
{
|
m_id(_id),
|
||||||
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()),
|
|
||||||
m_location(_location)
|
m_location(_location)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ASTNode::resetID()
|
|
||||||
{
|
|
||||||
IDDispenser::reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
ASTAnnotation& ASTNode::annotation() const
|
ASTAnnotation& ASTNode::annotation() const
|
||||||
{
|
{
|
||||||
if (!m_annotation)
|
if (!m_annotation)
|
||||||
@ -110,6 +91,11 @@ vector<VariableDeclaration const*> ContractDefinition::stateVariablesIncludingIn
|
|||||||
return stateVars;
|
return stateVars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ContractDefinition::derivesFrom(ContractDefinition const& _base) const
|
||||||
|
{
|
||||||
|
return util::contains(annotation().linearizedBaseContracts, &_base);
|
||||||
|
}
|
||||||
|
|
||||||
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
map<util::FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
||||||
{
|
{
|
||||||
auto exportedFunctionList = interfaceFunctionList();
|
auto exportedFunctionList = interfaceFunctionList();
|
||||||
@ -221,36 +207,6 @@ vector<pair<util::FixedHash<4>, FunctionTypePointer>> const& ContractDefinition:
|
|||||||
return *m_interfaceFunctionList;
|
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
|
TypePointer ContractDefinition::type() const
|
||||||
{
|
{
|
||||||
return TypeProvider::typeType(TypeProvider::contract(*this));
|
return TypeProvider::typeType(TypeProvider::contract(*this));
|
||||||
@ -341,6 +297,13 @@ TypePointer FunctionDefinition::type() const
|
|||||||
return TypeProvider::function(*this, FunctionType::Kind::Internal);
|
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
|
string FunctionDefinition::externalSignature() const
|
||||||
{
|
{
|
||||||
return TypeProvider::function(*this)->externalSignature();
|
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.
|
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.
|
/// Mapping docstring tag name -> content.
|
||||||
std::multimap<std::string, DocTag> docTags;
|
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.
|
/// The scope this declaration resides in. Can be nullptr if it is the global scope.
|
||||||
/// Available only after name and type resolution step.
|
/// Available only after name and type resolution step.
|
||||||
ASTNode const* scope = nullptr;
|
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
|
struct DeclarationAnnotation: ASTAnnotation, ScopableAnnotation
|
||||||
@ -98,7 +101,7 @@ struct TypeDeclarationAnnotation: DeclarationAnnotation
|
|||||||
std::string canonicalName;
|
std::string canonicalName;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation
|
struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||||
{
|
{
|
||||||
/// List of functions without a body. Can also contain functions from base classes.
|
/// List of functions without a body. Can also contain functions from base classes.
|
||||||
std::vector<FunctionDefinition const*> unimplementedFunctions;
|
std::vector<FunctionDefinition const*> unimplementedFunctions;
|
||||||
@ -119,17 +122,15 @@ struct CallableDeclarationAnnotation: DeclarationAnnotation
|
|||||||
std::set<CallableDeclaration const*> baseFunctions;
|
std::set<CallableDeclaration const*> baseFunctions;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
struct FunctionDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||||
{
|
|
||||||
/// Pointer to the contract this function is defined in
|
|
||||||
ContractDefinition const* contract = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, DocumentedAnnotation
|
struct EventDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, StructurallyDocumentedAnnotation
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -141,7 +142,7 @@ struct VariableDeclarationAnnotation: DeclarationAnnotation
|
|||||||
std::set<CallableDeclaration const*> baseFunctions;
|
std::set<CallableDeclaration const*> baseFunctions;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation
|
struct StatementAnnotation: ASTAnnotation
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -93,6 +93,7 @@ class PrimaryExpression;
|
|||||||
class Identifier;
|
class Identifier;
|
||||||
class ElementaryTypeNameExpression;
|
class ElementaryTypeNameExpression;
|
||||||
class Literal;
|
class Literal;
|
||||||
|
class StructuredDocumentation;
|
||||||
|
|
||||||
class VariableScope;
|
class VariableScope;
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
#include <libyul/AsmJsonConverter.h>
|
#include <libyul/AsmJsonConverter.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/AsmPrinter.h>
|
#include <libyul/AsmPrinter.h>
|
||||||
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
|
||||||
#include <libsolutil/JSON.h>
|
#include <libsolutil/JSON.h>
|
||||||
#include <libsolutil/UTF8.h>
|
#include <libsolutil/UTF8.h>
|
||||||
|
|
||||||
@ -266,7 +268,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
|||||||
{
|
{
|
||||||
setJsonNode(_node, "ContractDefinition", {
|
setJsonNode(_node, "ContractDefinition", {
|
||||||
make_pair("name", _node.name()),
|
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("contractKind", contractKind(_node.contractKind())),
|
||||||
make_pair("abstract", _node.abstract()),
|
make_pair("abstract", _node.abstract()),
|
||||||
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
||||||
@ -347,7 +349,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
|
|||||||
{
|
{
|
||||||
std::vector<pair<string, Json::Value>> attributes = {
|
std::vector<pair<string, Json::Value>> attributes = {
|
||||||
make_pair("name", _node.name()),
|
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("kind", TokenTraits::toString(_node.kind())),
|
||||||
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
|
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
|
||||||
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||||
@ -398,7 +400,7 @@ bool ASTJsonConverter::visit(ModifierDefinition const& _node)
|
|||||||
{
|
{
|
||||||
std::vector<pair<string, Json::Value>> attributes = {
|
std::vector<pair<string, Json::Value>> attributes = {
|
||||||
make_pair("name", _node.name()),
|
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("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||||
make_pair("parameters", toJson(_node.parameterList())),
|
make_pair("parameters", toJson(_node.parameterList())),
|
||||||
make_pair("virtual", _node.markedVirtual()),
|
make_pair("virtual", _node.markedVirtual()),
|
||||||
@ -425,7 +427,7 @@ bool ASTJsonConverter::visit(EventDefinition const& _node)
|
|||||||
m_inEvent = true;
|
m_inEvent = true;
|
||||||
setJsonNode(_node, "EventDefinition", {
|
setJsonNode(_node, "EventDefinition", {
|
||||||
make_pair("name", _node.name()),
|
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("parameters", toJson(_node.parameterList())),
|
||||||
make_pair("anonymous", _node.isAnonymous())
|
make_pair("anonymous", _node.isAnonymous())
|
||||||
});
|
});
|
||||||
@ -492,13 +494,16 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
|
|||||||
bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
||||||
{
|
{
|
||||||
vector<pair<string, Json::Value>> externalReferences;
|
vector<pair<string, Json::Value>> externalReferences;
|
||||||
|
|
||||||
for (auto const& it: _node.annotation().externalReferences)
|
for (auto const& it: _node.annotation().externalReferences)
|
||||||
if (it.first)
|
if (it.first)
|
||||||
externalReferences.emplace_back(make_pair(
|
externalReferences.emplace_back(make_pair(
|
||||||
it.first->name.str(),
|
it.first->name.str(),
|
||||||
inlineAssemblyIdentifierToJson(it)
|
inlineAssemblyIdentifierToJson(it)
|
||||||
));
|
));
|
||||||
|
|
||||||
Json::Value externalReferencesJson = Json::arrayValue;
|
Json::Value externalReferencesJson = Json::arrayValue;
|
||||||
|
|
||||||
for (auto&& it: boost::range::sort(externalReferences))
|
for (auto&& it: boost::range::sort(externalReferences))
|
||||||
externalReferencesJson.append(std::move(it.second));
|
externalReferencesJson.append(std::move(it.second));
|
||||||
|
|
||||||
@ -506,8 +511,10 @@ bool ASTJsonConverter::visit(InlineAssembly const& _node)
|
|||||||
m_legacy ?
|
m_legacy ?
|
||||||
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) :
|
make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) :
|
||||||
make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,6 +724,23 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
|
|||||||
return false;
|
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)
|
bool ASTJsonConverter::visit(NewExpression const& _node)
|
||||||
{
|
{
|
||||||
std::vector<pair<string, Json::Value>> attributes = {
|
std::vector<pair<string, Json::Value>> attributes = {
|
||||||
@ -809,6 +833,17 @@ bool ASTJsonConverter::visit(Literal const& _node)
|
|||||||
return false;
|
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&)
|
void ASTJsonConverter::endVisit(EventDefinition const&)
|
||||||
{
|
{
|
||||||
|
@ -111,6 +111,7 @@ public:
|
|||||||
bool visit(UnaryOperation const& _node) override;
|
bool visit(UnaryOperation const& _node) override;
|
||||||
bool visit(BinaryOperation const& _node) override;
|
bool visit(BinaryOperation const& _node) override;
|
||||||
bool visit(FunctionCall const& _node) override;
|
bool visit(FunctionCall const& _node) override;
|
||||||
|
bool visit(FunctionCallOptions const& _node) override;
|
||||||
bool visit(NewExpression const& _node) override;
|
bool visit(NewExpression const& _node) override;
|
||||||
bool visit(MemberAccess const& _node) override;
|
bool visit(MemberAccess const& _node) override;
|
||||||
bool visit(IndexAccess const& _node) override;
|
bool visit(IndexAccess const& _node) override;
|
||||||
@ -118,6 +119,7 @@ public:
|
|||||||
bool visit(Identifier const& _node) override;
|
bool visit(Identifier const& _node) override;
|
||||||
bool visit(ElementaryTypeNameExpression const& _node) override;
|
bool visit(ElementaryTypeNameExpression const& _node) override;
|
||||||
bool visit(Literal const& _node) override;
|
bool visit(Literal const& _node) override;
|
||||||
|
bool visit(StructuredDocumentation const& _node) override;
|
||||||
|
|
||||||
void endVisit(EventDefinition const&) 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(UnaryOperation& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); }
|
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(FunctionCall& _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(NewExpression& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
|
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(IndexAccess& _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(Identifier& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
|
virtual bool visit(ElementaryTypeNameExpression& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(Literal& _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(SourceUnit& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(PragmaDirective& _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(UnaryOperation& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); }
|
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(FunctionCall& _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(NewExpression& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
|
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(IndexAccess& _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(Identifier& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
|
virtual void endVisit(ElementaryTypeNameExpression& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(Literal& _node) { endVisitNode(_node); }
|
virtual void endVisit(Literal& _node) { endVisitNode(_node); }
|
||||||
|
virtual void endVisit(StructuredDocumentation& _node) { endVisitNode(_node); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Generic function called by default for each node, to be overridden by derived classes
|
/// 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(UnaryOperation const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(BinaryOperation 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(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(NewExpression const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
|
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(IndexAccess 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(Identifier const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(ElementaryTypeNameExpression 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(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(SourceUnit const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(PragmaDirective 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(UnaryOperation const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); }
|
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(FunctionCall 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(NewExpression const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
|
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(IndexAccess 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(Identifier const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
|
virtual void endVisit(ElementaryTypeNameExpression const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(Literal const& _node) { endVisitNode(_node); }
|
virtual void endVisit(Literal const& _node) { endVisitNode(_node); }
|
||||||
|
virtual void endVisit(StructuredDocumentation const& _node) { endVisitNode(_node); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Generic function called by default for each node, to be overridden by derived classes
|
/// 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);
|
_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)
|
void ContractDefinition::accept(ASTVisitor& _visitor)
|
||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
listAccept(m_baseContracts, _visitor);
|
listAccept(m_baseContracts, _visitor);
|
||||||
listAccept(m_subNodes, _visitor);
|
listAccept(m_subNodes, _visitor);
|
||||||
}
|
}
|
||||||
@ -81,6 +95,8 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const
|
|||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
listAccept(m_baseContracts, _visitor);
|
listAccept(m_baseContracts, _visitor);
|
||||||
listAccept(m_subNodes, _visitor);
|
listAccept(m_subNodes, _visitor);
|
||||||
}
|
}
|
||||||
@ -203,6 +219,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor)
|
|||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
@ -219,6 +237,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
|
|||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
@ -263,6 +283,8 @@ void ModifierDefinition::accept(ASTVisitor& _visitor)
|
|||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
@ -275,6 +297,8 @@ void ModifierDefinition::accept(ASTConstVisitor& _visitor) const
|
|||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
{
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
if (m_overrides)
|
if (m_overrides)
|
||||||
m_overrides->accept(_visitor);
|
m_overrides->accept(_visitor);
|
||||||
@ -308,14 +332,22 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const
|
|||||||
void EventDefinition::accept(ASTVisitor& _visitor)
|
void EventDefinition::accept(ASTVisitor& _visitor)
|
||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
|
}
|
||||||
_visitor.endVisit(*this);
|
_visitor.endVisit(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventDefinition::accept(ASTConstVisitor& _visitor) const
|
void EventDefinition::accept(ASTConstVisitor& _visitor) const
|
||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
|
{
|
||||||
|
if (m_documentation)
|
||||||
|
m_documentation->accept(_visitor);
|
||||||
m_parameters->accept(_visitor);
|
m_parameters->accept(_visitor);
|
||||||
|
}
|
||||||
_visitor.endVisit(*this);
|
_visitor.endVisit(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -781,6 +813,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
|
|||||||
_visitor.endVisit(*this);
|
_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)
|
void NewExpression::accept(ASTVisitor& _visitor)
|
||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
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,
|
Declaration const* _declaration,
|
||||||
bool _gasSet,
|
bool _gasSet,
|
||||||
bool _valueSet,
|
bool _valueSet,
|
||||||
bool _bound
|
bool _bound,
|
||||||
|
bool _saltSet
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return createAndGet<FunctionType>(
|
return createAndGet<FunctionType>(
|
||||||
@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function(
|
|||||||
_declaration,
|
_declaration,
|
||||||
_gasSet,
|
_gasSet,
|
||||||
_valueSet,
|
_valueSet,
|
||||||
_bound
|
_bound,
|
||||||
|
_saltSet
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,8 @@ public:
|
|||||||
Declaration const* _declaration = nullptr,
|
Declaration const* _declaration = nullptr,
|
||||||
bool _gasSet = false,
|
bool _gasSet = false,
|
||||||
bool _valueSet = 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
|
/// 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
|
void Type::clearCache() const
|
||||||
{
|
{
|
||||||
m_members.clear();
|
m_members.clear();
|
||||||
|
m_stackItems.reset();
|
||||||
|
m_stackSize.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StorageOffsets::computeOffsets(TypePointers const& _types)
|
void StorageOffsets::computeOffsets(TypePointers const& _types)
|
||||||
@ -210,7 +212,7 @@ pair<u256, unsigned> const* MemberList::memberStorageOffset(string const& _name)
|
|||||||
memberTypes.reserve(m_memberTypes.size());
|
memberTypes.reserve(m_memberTypes.size());
|
||||||
for (auto const& member: m_memberTypes)
|
for (auto const& member: m_memberTypes)
|
||||||
memberTypes.push_back(member.type);
|
memberTypes.push_back(member.type);
|
||||||
m_storageOffsets.reset(new StorageOffsets());
|
m_storageOffsets = std::make_unique<StorageOffsets>();
|
||||||
m_storageOffsets->computeOffsets(memberTypes);
|
m_storageOffsets->computeOffsets(memberTypes);
|
||||||
}
|
}
|
||||||
for (size_t index = 0; index < m_memberTypes.size(); ++index)
|
for (size_t index = 0; index < m_memberTypes.size(); ++index)
|
||||||
@ -1701,15 +1703,22 @@ u256 ArrayType::storageSize() const
|
|||||||
return max<u256>(1, u256(size));
|
return max<u256>(1, u256(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned ArrayType::sizeOnStack() const
|
vector<tuple<string, TypePointer>> ArrayType::makeStackItems() const
|
||||||
{
|
{
|
||||||
if (m_location == DataLocation::CallData)
|
switch (m_location)
|
||||||
// offset [length] (stack top)
|
{
|
||||||
return 1 + (isDynamicallySized() ? 1 : 0);
|
case DataLocation::CallData:
|
||||||
else
|
if (isDynamicallySized())
|
||||||
// storage slot or memory offset
|
return {std::make_tuple("offset", TypeProvider::uint256()), std::make_tuple("length", TypeProvider::uint256())};
|
||||||
// byte offset inside storage value is omitted
|
else
|
||||||
return 1;
|
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
|
string ArrayType::toString(bool _short) const
|
||||||
@ -1891,6 +1900,11 @@ string ArraySliceType::toString(bool _short) const
|
|||||||
return m_arrayType.toString(_short) + " slice";
|
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
|
string ContractType::richIdentifier() const
|
||||||
{
|
{
|
||||||
return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + to_string(m_contract.id());
|
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;
|
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
|
void StructType::clearCache() const
|
||||||
{
|
{
|
||||||
Type::clearCache();
|
Type::clearCache();
|
||||||
@ -2354,7 +2376,7 @@ string EnumType::canonicalName() const
|
|||||||
size_t EnumType::numberOfMembers() const
|
size_t EnumType::numberOfMembers() const
|
||||||
{
|
{
|
||||||
return m_enum.members().size();
|
return m_enum.members().size();
|
||||||
};
|
}
|
||||||
|
|
||||||
BoolResult EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const
|
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.");
|
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())
|
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
|
TypePointer TupleType::mobileType() const
|
||||||
@ -2741,6 +2768,8 @@ string FunctionType::richIdentifier() const
|
|||||||
id += "gas";
|
id += "gas";
|
||||||
if (m_valueSet)
|
if (m_valueSet)
|
||||||
id += "value";
|
id += "value";
|
||||||
|
if (m_saltSet)
|
||||||
|
id += "salt";
|
||||||
if (bound())
|
if (bound())
|
||||||
id += "bound_to" + identifierList(selfType());
|
id += "bound_to" + identifierList(selfType());
|
||||||
return id;
|
return id;
|
||||||
@ -2881,8 +2910,9 @@ unsigned FunctionType::storageBytes() const
|
|||||||
solAssert(false, "Storage size of non-storable function type requested.");
|
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;
|
Kind kind = m_kind;
|
||||||
if (m_kind == Kind::SetGas || m_kind == Kind::SetValue)
|
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;
|
kind = dynamic_cast<FunctionType const&>(*m_returnParameterTypes.front()).m_kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned size = 0;
|
|
||||||
|
|
||||||
switch (kind)
|
switch (kind)
|
||||||
{
|
{
|
||||||
case Kind::External:
|
case Kind::External:
|
||||||
case Kind::DelegateCall:
|
case Kind::DelegateCall:
|
||||||
size = 2;
|
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
|
||||||
break;
|
break;
|
||||||
case Kind::BareCall:
|
case Kind::BareCall:
|
||||||
case Kind::BareCallCode:
|
case Kind::BareCallCode:
|
||||||
case Kind::BareDelegateCall:
|
case Kind::BareDelegateCall:
|
||||||
case Kind::BareStaticCall:
|
case Kind::BareStaticCall:
|
||||||
|
case Kind::Transfer:
|
||||||
|
case Kind::Send:
|
||||||
|
slots = {make_tuple("address", TypeProvider::address())};
|
||||||
|
break;
|
||||||
case Kind::Internal:
|
case Kind::Internal:
|
||||||
|
slots = {make_tuple("functionIdentifier", TypeProvider::uint256())};
|
||||||
|
break;
|
||||||
case Kind::ArrayPush:
|
case Kind::ArrayPush:
|
||||||
case Kind::ArrayPop:
|
case Kind::ArrayPop:
|
||||||
case Kind::ByteArrayPush:
|
case Kind::ByteArrayPush:
|
||||||
case Kind::Transfer:
|
slots = {make_tuple("slot", TypeProvider::uint256())};
|
||||||
case Kind::Send:
|
|
||||||
size = 1;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_gasSet)
|
if (m_gasSet)
|
||||||
size++;
|
slots.emplace_back("gas", TypeProvider::uint256());
|
||||||
if (m_valueSet)
|
if (m_valueSet)
|
||||||
size++;
|
slots.emplace_back("value", TypeProvider::uint256());
|
||||||
|
if (m_saltSet)
|
||||||
|
slots.emplace_back("salt", TypeProvider::uint256());
|
||||||
if (bound())
|
if (bound())
|
||||||
size += m_parameterTypes.front()->sizeOnStack();
|
for (auto const& [boundName, boundType]: m_parameterTypes.front()->stackItems())
|
||||||
return size;
|
slots.emplace_back("self_" + boundName, boundType);
|
||||||
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypePointer FunctionType::interfaceFunctionType() const
|
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)
|
switch (m_kind)
|
||||||
{
|
{
|
||||||
case Kind::Declaration:
|
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::External:
|
||||||
case Kind::Creation:
|
case Kind::Creation:
|
||||||
case Kind::BareCall:
|
case Kind::BareCall:
|
||||||
@ -2983,7 +3036,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
"value",
|
"value",
|
||||||
TypeProvider::function(
|
TypeProvider::function(
|
||||||
parseElementaryTypeVector({"uint"}),
|
parseElementaryTypeVector({"uint"}),
|
||||||
TypePointers{copyAndSetGasOrValue(false, true)},
|
TypePointers{copyAndSetCallOptions(false, true, false)},
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
Kind::SetValue,
|
Kind::SetValue,
|
||||||
@ -2991,7 +3044,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
StateMutability::Pure,
|
StateMutability::Pure,
|
||||||
nullptr,
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet,
|
||||||
|
m_saltSet
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3000,7 +3054,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
"gas",
|
"gas",
|
||||||
TypeProvider::function(
|
TypeProvider::function(
|
||||||
parseElementaryTypeVector({"uint"}),
|
parseElementaryTypeVector({"uint"}),
|
||||||
TypePointers{copyAndSetGasOrValue(true, false)},
|
TypePointers{copyAndSetCallOptions(true, false, false)},
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
Kind::SetGas,
|
Kind::SetGas,
|
||||||
@ -3008,7 +3062,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
StateMutability::Pure,
|
StateMutability::Pure,
|
||||||
nullptr,
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet,
|
||||||
|
m_saltSet
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return members;
|
return members;
|
||||||
@ -3131,7 +3186,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
//@todo this is ugly, but cannot be prevented right now
|
//@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;
|
return false;
|
||||||
|
|
||||||
if (bound() != _other.bound())
|
if (bound() != _other.bound())
|
||||||
@ -3232,7 +3287,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
|
|||||||
return pointers;
|
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, "");
|
solAssert(m_kind != Kind::Declaration, "");
|
||||||
return TypeProvider::function(
|
return TypeProvider::function(
|
||||||
@ -3246,6 +3301,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
|
|||||||
m_declaration,
|
m_declaration,
|
||||||
m_gasSet || _setGas,
|
m_gasSet || _setGas,
|
||||||
m_valueSet || _setValue,
|
m_valueSet || _setValue,
|
||||||
|
m_saltSet || _setSalt,
|
||||||
m_bound
|
m_bound
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3286,6 +3342,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun
|
|||||||
m_declaration,
|
m_declaration,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet,
|
m_valueSet,
|
||||||
|
m_saltSet,
|
||||||
_bound
|
_bound
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3297,13 +3354,13 @@ Type const* FunctionType::selfType() const
|
|||||||
return m_parameterTypes.at(0);
|
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)
|
if (function)
|
||||||
return function->documentation();
|
return function->documentation();
|
||||||
|
|
||||||
return ASTPointer<ASTString>();
|
return ASTPointer<StructuredDocumentation>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FunctionType::padArguments() const
|
bool FunctionType::padArguments() const
|
||||||
@ -3392,12 +3449,12 @@ u256 TypeType::storageSize() const
|
|||||||
solAssert(false, "Storage size of non-storable type type requested.");
|
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 (auto contractType = dynamic_cast<ContractType const*>(m_actualType))
|
||||||
if (contractType->contractDefinition().isLibrary())
|
if (contractType->contractDefinition().isLibrary())
|
||||||
return 1;
|
return {make_tuple("address", TypeProvider::address())};
|
||||||
return 0;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _currentScope) const
|
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)
|
if (m_actualType->category() == Category::Contract)
|
||||||
{
|
{
|
||||||
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).contractDefinition();
|
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_actualType).contractDefinition();
|
||||||
bool isBase = false;
|
bool inDerivingScope = _currentScope && _currentScope->derivesFrom(contract);
|
||||||
if (_currentScope != nullptr)
|
|
||||||
|
for (auto const* declaration: contract.declarations())
|
||||||
{
|
{
|
||||||
auto const& currentBases = _currentScope->annotation().linearizedBaseContracts;
|
if (dynamic_cast<ModifierDefinition const*>(declaration))
|
||||||
isBase = (find(currentBases.begin(), currentBases.end(), &contract) != currentBases.end());
|
continue;
|
||||||
}
|
|
||||||
if (isBase)
|
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
||||||
{
|
members.emplace_back(declaration->name(), declaration->type(), declaration);
|
||||||
// We are accessing the type of a base contract, so add all public and protected
|
else if (
|
||||||
// members. Note that this does not add inherited functions on purpose.
|
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
||||||
for (Declaration const* decl: contract.inheritableMembers())
|
declaration->isVisibleViaContractTypeAccess()
|
||||||
members.emplace_back(decl->name(), decl->type(), decl);
|
)
|
||||||
}
|
members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_actualType->category() == Category::Enum)
|
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,
|
/// 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.
|
/// i.e. it behaves differently in lvalue context and in value context.
|
||||||
virtual bool isValueType() const { return false; }
|
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
|
/// If it is possible to initialize such a value in memory by just writing zeros
|
||||||
/// of the size memoryHeadSize().
|
/// of the size memoryHeadSize().
|
||||||
virtual bool hasSimpleZeroValueInMemory() const { return true; }
|
virtual bool hasSimpleZeroValueInMemory() const { return true; }
|
||||||
@ -336,9 +362,18 @@ protected:
|
|||||||
{
|
{
|
||||||
return MemberList::MemberMap();
|
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.
|
/// List of member types (parameterised by scape), will be lazy-initialized.
|
||||||
mutable std::map<ContractDefinition const*, std::unique_ptr<MemberList>> m_members;
|
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 canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
unsigned sizeOnStack() const override { return 0; }
|
|
||||||
|
|
||||||
std::string toString(bool) const override;
|
std::string toString(bool) const override;
|
||||||
TypePointer mobileType() const override;
|
TypePointer mobileType() const override;
|
||||||
@ -571,6 +605,8 @@ public:
|
|||||||
|
|
||||||
std::string const& value() const { return m_value; }
|
std::string const& value() const { return m_value; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||||
private:
|
private:
|
||||||
std::string m_value;
|
std::string m_value;
|
||||||
};
|
};
|
||||||
@ -725,7 +761,6 @@ public:
|
|||||||
bool isDynamicallyEncoded() const override;
|
bool isDynamicallyEncoded() const override;
|
||||||
u256 storageSize() const override;
|
u256 storageSize() const override;
|
||||||
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
|
bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
|
||||||
unsigned sizeOnStack() const override;
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
std::string canonicalName() const override;
|
std::string canonicalName() const override;
|
||||||
std::string signatureInExternalFunction(bool _structsByName) const override;
|
std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||||
@ -756,6 +791,8 @@ public:
|
|||||||
|
|
||||||
void clearCache() const override;
|
void clearCache() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
/// String is interpreted as a subtype of Bytes.
|
/// String is interpreted as a subtype of Bytes.
|
||||||
enum class ArrayKind { Ordinary, Bytes, String };
|
enum class ArrayKind { Ordinary, Bytes, String };
|
||||||
@ -785,7 +822,6 @@ public:
|
|||||||
bool isDynamicallySized() const override { return true; }
|
bool isDynamicallySized() const override { return true; }
|
||||||
bool isDynamicallyEncoded() const override { return true; }
|
bool isDynamicallyEncoded() const override { return true; }
|
||||||
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); }
|
||||||
unsigned sizeOnStack() const override { return 2; }
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
/// @returns true if this is valid to be stored in calldata
|
/// @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, ""); }
|
std::unique_ptr<ReferenceType> copyForLocation(DataLocation, bool) const override { solAssert(false, ""); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
ArrayType const& m_arrayType;
|
ArrayType const& m_arrayType;
|
||||||
};
|
};
|
||||||
@ -825,7 +863,6 @@ public:
|
|||||||
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
|
unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
|
||||||
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
|
bool leftAligned() const override { solAssert(!isSuper(), ""); return false; }
|
||||||
bool canLiveOutsideStorage() const override { return !isSuper(); }
|
bool canLiveOutsideStorage() const override { return !isSuper(); }
|
||||||
unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
|
||||||
bool isValueType() const override { return !isSuper(); }
|
bool isValueType() const override { return !isSuper(); }
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
std::string canonicalName() 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
|
/// @returns a list of all state variables (including inherited) of the contract and their
|
||||||
/// offsets in storage.
|
/// offsets in storage.
|
||||||
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
|
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
ContractDefinition const& m_contract;
|
ContractDefinition const& m_contract;
|
||||||
/// If true, this is a special "super" type of m_contract containing only members that m_contract inherited
|
/// 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; }
|
bool canBeStored() const override { return false; }
|
||||||
u256 storageSize() const override;
|
u256 storageSize() const override;
|
||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
unsigned sizeOnStack() const override;
|
|
||||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||||
TypePointer mobileType() const override;
|
TypePointer mobileType() const override;
|
||||||
/// Converts components to their temporary types and performs some wildcard matching.
|
/// 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; }
|
std::vector<TypePointer> const& components() const { return m_components; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
std::vector<TypePointer> const m_components;
|
std::vector<TypePointer> const m_components;
|
||||||
};
|
};
|
||||||
@ -1055,7 +1094,7 @@ public:
|
|||||||
/// Refers to a function declaration without calling context
|
/// Refers to a function declaration without calling context
|
||||||
/// (i.e. when accessed directly via the name of the containing contract).
|
/// (i.e. when accessed directly via the name of the containing contract).
|
||||||
/// Cannot be called.
|
/// Cannot be called.
|
||||||
Declaration
|
Declaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates the type of a function.
|
/// Creates the type of a function.
|
||||||
@ -1098,6 +1137,7 @@ public:
|
|||||||
Declaration const* _declaration = nullptr,
|
Declaration const* _declaration = nullptr,
|
||||||
bool _gasSet = false,
|
bool _gasSet = false,
|
||||||
bool _valueSet = false,
|
bool _valueSet = false,
|
||||||
|
bool _saltSet = false,
|
||||||
bool _bound = false
|
bool _bound = false
|
||||||
):
|
):
|
||||||
m_parameterTypes(_parameterTypes),
|
m_parameterTypes(_parameterTypes),
|
||||||
@ -1110,7 +1150,8 @@ public:
|
|||||||
m_gasSet(_gasSet),
|
m_gasSet(_gasSet),
|
||||||
m_valueSet(_valueSet),
|
m_valueSet(_valueSet),
|
||||||
m_bound(_bound),
|
m_bound(_bound),
|
||||||
m_declaration(_declaration)
|
m_declaration(_declaration),
|
||||||
|
m_saltSet(_saltSet)
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
m_parameterNames.size() == m_parameterTypes.size(),
|
m_parameterNames.size() == m_parameterTypes.size(),
|
||||||
@ -1156,7 +1197,6 @@ public:
|
|||||||
unsigned storageBytes() const override;
|
unsigned storageBytes() const override;
|
||||||
bool isValueType() const override { return true; }
|
bool isValueType() const override { return true; }
|
||||||
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||||
unsigned sizeOnStack() const override;
|
|
||||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||||
TypePointer encodingType() const override;
|
TypePointer encodingType() const override;
|
||||||
@ -1206,9 +1246,9 @@ public:
|
|||||||
/// Currently, this will only return true for internal functions like keccak and ecrecover.
|
/// Currently, this will only return true for internal functions like keccak and ecrecover.
|
||||||
bool isPure() const;
|
bool isPure() const;
|
||||||
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
|
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
|
||||||
/// @return A shared pointer of an ASTString.
|
/// @return A shared pointer of StructuredDocumentation.
|
||||||
/// Can contain a nullptr in which case indicates absence of documentation
|
/// Can contain a nullptr in which case indicates absence of documentation.
|
||||||
ASTPointer<ASTString> documentation() const;
|
ASTPointer<StructuredDocumentation> documentation() const;
|
||||||
|
|
||||||
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
|
/// 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
|
/// 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 gasSet() const { return m_gasSet; }
|
||||||
bool valueSet() const { return m_valueSet; }
|
bool valueSet() const { return m_valueSet; }
|
||||||
|
bool saltSet() const { return m_saltSet; }
|
||||||
bool bound() const { return m_bound; }
|
bool bound() const { return m_bound; }
|
||||||
|
|
||||||
/// @returns a copy of this type, where gas or value are set manually. This will never set one
|
/// @returns a copy of this type, where gas or value are set manually. This will never set one
|
||||||
/// of the parameters to false.
|
/// 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
|
/// @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
|
/// 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.
|
/// @param _bound if true, the function type is set to be bound.
|
||||||
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
|
FunctionTypePointer asCallableFunction(bool _inLibrary, bool _bound = false) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
static TypePointers parseElementaryTypeVector(strings const& _types);
|
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_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)
|
bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn)
|
||||||
Declaration const* m_declaration = nullptr;
|
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; }
|
bool canBeStored() const override { return false; }
|
||||||
u256 storageSize() const override;
|
u256 storageSize() const override;
|
||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
unsigned sizeOnStack() const override;
|
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||||
|
|
||||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override;
|
||||||
private:
|
private:
|
||||||
TypePointer m_actualType;
|
TypePointer m_actualType;
|
||||||
};
|
};
|
||||||
@ -1342,12 +1387,13 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
u256 storageSize() const override;
|
u256 storageSize() const override;
|
||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
unsigned sizeOnStack() const override { return 0; }
|
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
std::string richIdentifier() const override;
|
std::string richIdentifier() const override;
|
||||||
bool operator==(Type const& _other) const override;
|
bool operator==(Type const& _other) const override;
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||||
private:
|
private:
|
||||||
TypePointers m_parameterTypes;
|
TypePointers m_parameterTypes;
|
||||||
};
|
};
|
||||||
@ -1370,11 +1416,12 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return true; }
|
bool canLiveOutsideStorage() const override { return true; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
unsigned sizeOnStack() const override { return 0; }
|
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||||
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||||
private:
|
private:
|
||||||
SourceUnit const& m_sourceUnit;
|
SourceUnit const& m_sourceUnit;
|
||||||
};
|
};
|
||||||
@ -1409,7 +1456,6 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return true; }
|
bool canLiveOutsideStorage() const override { return true; }
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
unsigned sizeOnStack() const override { return 0; }
|
|
||||||
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
|
||||||
|
|
||||||
std::string toString(bool _short) const override;
|
std::string toString(bool _short) const override;
|
||||||
@ -1418,6 +1464,8 @@ public:
|
|||||||
|
|
||||||
TypePointer typeArgument() const;
|
TypePointer typeArgument() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::tuple<std::string, TypePointer>> makeStackItems() const override { return {}; }
|
||||||
private:
|
private:
|
||||||
Kind m_kind;
|
Kind m_kind;
|
||||||
/// Contract type used for contract metadata magic.
|
/// Contract type used for contract metadata magic.
|
||||||
@ -1441,7 +1489,6 @@ public:
|
|||||||
bool canBeStored() const override { return false; }
|
bool canBeStored() const override { return false; }
|
||||||
bool canLiveOutsideStorage() const override { return false; }
|
bool canLiveOutsideStorage() const override { return false; }
|
||||||
bool isValueType() const override { return true; }
|
bool isValueType() const override { return true; }
|
||||||
unsigned sizeOnStack() const override { return 1; }
|
|
||||||
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
|
||||||
std::string toString(bool) const override { return "inaccessible dynamic type"; }
|
std::string toString(bool) const override { return "inaccessible dynamic type"; }
|
||||||
TypePointer decodingType() const override;
|
TypePointer decodingType() const override;
|
||||||
|
@ -180,11 +180,12 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
|
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
||||||
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
|
if slt(sub(dataEnd, headStart), <minimumSize>) { <revertString> }
|
||||||
<decodeElements>
|
<decodeElements>
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
|
templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short"));
|
||||||
templ("minimumSize", to_string(headSize(decodingTypes)));
|
templ("minimumSize", to_string(headSize(decodingTypes)));
|
||||||
|
|
||||||
string decodeElements;
|
string decodeElements;
|
||||||
@ -211,7 +212,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
R"(
|
R"(
|
||||||
{
|
{
|
||||||
let offset := <load>(add(headStart, <pos>))
|
let offset := <load>(add(headStart, <pos>))
|
||||||
if gt(offset, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(offset, 0xffffffffffffffff) { <revertString> }
|
||||||
<values> := <abiDecode>(add(headStart, offset), dataEnd)
|
<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("load", _fromMemory ? "mload" : "calldataload");
|
||||||
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
|
elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
|
||||||
elementTempl("pos", to_string(headPos));
|
elementTempl("pos", to_string(headPos));
|
||||||
@ -453,12 +456,14 @@ string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup(
|
|||||||
else
|
else
|
||||||
templ("scaleLengthByStride",
|
templ("scaleLengthByStride",
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
if gt(length, <maxLength>) { revert(0, 0) }
|
if gt(length, <maxLength>) { <revertString> }
|
||||||
length := mul(length, <stride>)
|
length := mul(length, <stride>)
|
||||||
)")
|
)")
|
||||||
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
|
("stride", toCompactHexWithPrefix(fromArrayType.calldataStride()))
|
||||||
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
|
("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride()))
|
||||||
|
("revertString", revertReasonIfDebug("ABI encoding: array data too long"))
|
||||||
.render()
|
.render()
|
||||||
|
// TODO add revert test
|
||||||
);
|
);
|
||||||
templ("readableTypeNameFrom", _from.toString(true));
|
templ("readableTypeNameFrom", _from.toString(true));
|
||||||
templ("readableTypeNameTo", _to.toString(true));
|
templ("readableTypeNameTo", _to.toString(true));
|
||||||
@ -1124,7 +1129,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
|||||||
R"(
|
R"(
|
||||||
// <readableTypeName>
|
// <readableTypeName>
|
||||||
function <functionName>(offset, end) -> array {
|
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>
|
let length := <retrieveLength>
|
||||||
array := <allocate>(<allocationSize>(length))
|
array := <allocate>(<allocationSize>(length))
|
||||||
let dst := array
|
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("functionName", functionName);
|
||||||
templ("readableTypeName", _type.toString(true));
|
templ("readableTypeName", _type.toString(true));
|
||||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||||
@ -1159,7 +1166,12 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
|||||||
}
|
}
|
||||||
else
|
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("retrieveElementPos", "src");
|
||||||
}
|
}
|
||||||
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
||||||
@ -1184,11 +1196,11 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
|||||||
templ = R"(
|
templ = R"(
|
||||||
// <readableTypeName>
|
// <readableTypeName>
|
||||||
function <functionName>(offset, end) -> arrayPos, length {
|
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)
|
length := calldataload(offset)
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||||
arrayPos := add(offset, 0x20)
|
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
|
else
|
||||||
@ -1196,10 +1208,14 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
|||||||
// <readableTypeName>
|
// <readableTypeName>
|
||||||
function <functionName>(offset, end) -> arrayPos {
|
function <functionName>(offset, end) -> arrayPos {
|
||||||
arrayPos := offset
|
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};
|
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("functionName", functionName);
|
||||||
w("readableTypeName", _type.toString(true));
|
w("readableTypeName", _type.toString(true));
|
||||||
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
|
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
|
||||||
@ -1223,17 +1239,20 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
|||||||
Whiskers templ(
|
Whiskers templ(
|
||||||
R"(
|
R"(
|
||||||
function <functionName>(offset, end) -> array {
|
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)
|
let length := <load>(offset)
|
||||||
array := <allocate>(<allocationSize>(length))
|
array := <allocate>(<allocationSize>(length))
|
||||||
mstore(array, length)
|
mstore(array, length)
|
||||||
let src := add(offset, 0x20)
|
let src := add(offset, 0x20)
|
||||||
let dst := add(array, 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)
|
<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("functionName", functionName);
|
||||||
templ("load", _fromMemory ? "mload" : "calldataload");
|
templ("load", _fromMemory ? "mload" : "calldataload");
|
||||||
templ("allocate", m_utils.allocationFunction());
|
templ("allocate", m_utils.allocationFunction());
|
||||||
@ -1254,10 +1273,12 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
|||||||
Whiskers w{R"(
|
Whiskers w{R"(
|
||||||
// <readableTypeName>
|
// <readableTypeName>
|
||||||
function <functionName>(offset, end) -> value {
|
function <functionName>(offset, end) -> value {
|
||||||
if slt(sub(end, offset), <minimumSize>) { revert(0, 0) }
|
if slt(sub(end, offset), <minimumSize>) { <revertString> }
|
||||||
value := offset
|
value := offset
|
||||||
}
|
}
|
||||||
)"};
|
)"};
|
||||||
|
// TODO add test
|
||||||
|
w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short"));
|
||||||
w("functionName", functionName);
|
w("functionName", functionName);
|
||||||
w("readableTypeName", _type.toString(true));
|
w("readableTypeName", _type.toString(true));
|
||||||
w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(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"(
|
Whiskers templ(R"(
|
||||||
// <readableTypeName>
|
// <readableTypeName>
|
||||||
function <functionName>(headStart, end) -> value {
|
function <functionName>(headStart, end) -> value {
|
||||||
if slt(sub(end, headStart), <minimumSize>) { revert(0, 0) }
|
if slt(sub(end, headStart), <minimumSize>) { <revertString> }
|
||||||
value := <allocate>(<memorySize>)
|
value := <allocate>(<memorySize>)
|
||||||
<#members>
|
<#members>
|
||||||
{
|
{
|
||||||
@ -1287,6 +1308,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
|||||||
</members>
|
</members>
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
|
// TODO add test
|
||||||
|
templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short"));
|
||||||
templ("functionName", functionName);
|
templ("functionName", functionName);
|
||||||
templ("readableTypeName", _type.toString(true));
|
templ("readableTypeName", _type.toString(true));
|
||||||
templ("allocate", m_utils.allocationFunction());
|
templ("allocate", m_utils.allocationFunction());
|
||||||
@ -1305,7 +1328,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
|||||||
dynamic ?
|
dynamic ?
|
||||||
R"(
|
R"(
|
||||||
let offset := <load>(add(headStart, <pos>))
|
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))
|
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
|
||||||
)" :
|
)" :
|
||||||
R"(
|
R"(
|
||||||
@ -1313,6 +1336,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
|||||||
mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
|
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("load", _fromMemory ? "mload" : "calldataload");
|
||||||
memberTempl("pos", to_string(headPos));
|
memberTempl("pos", to_string(headPos));
|
||||||
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
|
memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
|
||||||
@ -1380,7 +1405,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
|||||||
Whiskers w(R"(
|
Whiskers w(R"(
|
||||||
function <functionName>(base_ref, ptr) -> <return> {
|
function <functionName>(base_ref, ptr) -> <return> {
|
||||||
let rel_offset_of_tail := calldataload(ptr)
|
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)
|
value := add(rel_offset_of_tail, base_ref)
|
||||||
<handleLength>
|
<handleLength>
|
||||||
}
|
}
|
||||||
@ -1392,9 +1417,15 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
|||||||
w("handleLength", Whiskers(R"(
|
w("handleLength", Whiskers(R"(
|
||||||
length := calldataload(value)
|
length := calldataload(value)
|
||||||
value := add(value, 0x20)
|
value := add(value, 0x20)
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(length, 0xffffffffffffffff) { <revertStringLength> }
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { <revertStringStride> }
|
||||||
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
|
)")
|
||||||
|
("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");
|
w("return", "value, length");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1404,6 +1435,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
|||||||
}
|
}
|
||||||
w("neededLength", toCompactHexWithPrefix(tailSize));
|
w("neededLength", toCompactHexWithPrefix(tailSize));
|
||||||
w("functionName", functionName);
|
w("functionName", functionName);
|
||||||
|
w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset"));
|
||||||
return w.render();
|
return w.render();
|
||||||
}
|
}
|
||||||
else if (_type.isValueType())
|
else if (_type.isValueType())
|
||||||
@ -1493,3 +1525,8 @@ size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions cons
|
|||||||
else
|
else
|
||||||
return _type.sizeOnStack();
|
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/MultiUseYulFunctionCollector.h>
|
||||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||||
|
|
||||||
|
#include <libsolidity/interface/DebugSettings.h>
|
||||||
|
|
||||||
#include <liblangutil/EVMVersion.h>
|
#include <liblangutil/EVMVersion.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -55,11 +57,13 @@ class ABIFunctions
|
|||||||
public:
|
public:
|
||||||
explicit ABIFunctions(
|
explicit ABIFunctions(
|
||||||
langutil::EVMVersion _evmVersion,
|
langutil::EVMVersion _evmVersion,
|
||||||
|
RevertStrings _revertStrings,
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
|
m_revertStrings(_revertStrings),
|
||||||
m_functionCollector(std::move(_functionCollector)),
|
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
|
/// @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 _fromMemory if decoding from memory instead of from calldata
|
||||||
/// @param _forUseOnStack if the decoded value is stored on stack or in memory.
|
/// @param _forUseOnStack if the decoded value is stored on stack or in memory.
|
||||||
std::string abiDecodingFunction(
|
std::string abiDecodingFunction(
|
||||||
Type const& _Type,
|
Type const& _type,
|
||||||
bool _fromMemory,
|
bool _fromMemory,
|
||||||
bool _forUseOnStack
|
bool _forUseOnStack
|
||||||
);
|
);
|
||||||
@ -249,7 +253,12 @@ private:
|
|||||||
/// is true), for which it is two.
|
/// is true), for which it is two.
|
||||||
static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options);
|
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;
|
langutil::EVMVersion m_evmVersion;
|
||||||
|
RevertStrings const m_revertStrings;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||||
std::set<std::string> m_externallyUsedFunctions;
|
std::set<std::string> m_externallyUsedFunctions;
|
||||||
YulUtilFunctions m_utils;
|
YulUtilFunctions m_utils;
|
||||||
|
@ -35,7 +35,7 @@ void Compiler::compileContract(
|
|||||||
bytes const& _metadata
|
bytes const& _metadata
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings, m_revertStrings);
|
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
|
||||||
runtimeCompiler.compileContract(_contract, _otherCompilers);
|
runtimeCompiler.compileContract(_contract, _otherCompilers);
|
||||||
m_runtimeContext.appendAuxiliaryData(_metadata);
|
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
|
// The creation code will be executed at most once, so we modify the optimizer
|
||||||
// settings accordingly.
|
// settings accordingly.
|
||||||
creationSettings.expectedExecutionsPerDeployment = 1;
|
creationSettings.expectedExecutionsPerDeployment = 1;
|
||||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings, m_revertStrings);
|
ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings);
|
||||||
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
|
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
|
||||||
|
|
||||||
m_context.optimise(m_optimiserSettings);
|
m_context.optimise(m_optimiserSettings);
|
||||||
|
@ -37,9 +37,8 @@ class Compiler
|
|||||||
public:
|
public:
|
||||||
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
|
Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings):
|
||||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
m_optimiserSettings(std::move(_optimiserSettings)),
|
||||||
m_revertStrings(_revertStrings),
|
m_runtimeContext(_evmVersion, _revertStrings),
|
||||||
m_runtimeContext(_evmVersion),
|
m_context(_evmVersion, _revertStrings, &m_runtimeContext)
|
||||||
m_context(_evmVersion, &m_runtimeContext)
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// Compiles a contract.
|
/// Compiles a contract.
|
||||||
@ -65,9 +64,9 @@ public:
|
|||||||
return m_context.assemblyString(_sourceCodes);
|
return m_context.assemblyString(_sourceCodes);
|
||||||
}
|
}
|
||||||
/// @arg _sourceCodes is the map of input files to source code strings
|
/// @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
|
/// @returns Assembly items of the normal compiler context
|
||||||
evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
|
evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
|
||||||
@ -80,7 +79,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
OptimiserSettings const m_optimiserSettings;
|
OptimiserSettings const m_optimiserSettings;
|
||||||
RevertStrings const m_revertStrings;
|
|
||||||
CompilerContext m_runtimeContext;
|
CompilerContext m_runtimeContext;
|
||||||
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
|
size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present.
|
||||||
CompilerContext m_context;
|
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