mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8468 from ethereum/develop
Merge develop into release for 0.6.4
This commit is contained in:
commit
1dca32f352
@ -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:
|
||||||
|
|
||||||
@ -113,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:
|
||||||
@ -126,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:
|
||||||
@ -160,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:
|
||||||
@ -190,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
|
||||||
@ -312,7 +337,7 @@ 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++
|
||||||
@ -324,7 +349,7 @@ 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 >>
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- run: *run_build
|
- run: *run_build
|
||||||
@ -339,7 +364,7 @@ jobs:
|
|||||||
|
|
||||||
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
|
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
|
||||||
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
||||||
@ -391,12 +416,13 @@ jobs:
|
|||||||
- 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
|
||||||
@ -405,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:
|
||||||
@ -546,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
|
||||||
@ -571,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:
|
||||||
@ -793,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 \
|
@ -10,7 +10,7 @@ include(EthPolicy)
|
|||||||
eth_policy()
|
eth_policy()
|
||||||
|
|
||||||
# project name and version should be set after cmake_policy CMP0048
|
# project name and version should be set after cmake_policy CMP0048
|
||||||
set(PROJECT_VERSION "0.6.3")
|
set(PROJECT_VERSION "0.6.4")
|
||||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||||
|
|
||||||
include(TestBigEndian)
|
include(TestBigEndian)
|
||||||
|
23
Changelog.md
23
Changelog.md
@ -1,3 +1,24 @@
|
|||||||
|
### 0.6.4 (2020-03-10)
|
||||||
|
|
||||||
|
Language Features:
|
||||||
|
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
|
||||||
|
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
|
||||||
|
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.
|
||||||
|
|
||||||
|
|
||||||
|
Compiler Features:
|
||||||
|
* AssemblyStack: Support for source locations (source mappings) and thus debugging Yul sources.
|
||||||
|
* Commandline Interface: Enable output of experimental optimized IR via ``--ir-optimized``.
|
||||||
|
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Inheritance: Fix incorrect error on calling unimplemented base functions.
|
||||||
|
* Reference Resolver: Fix scoping issue following try/catch statements.
|
||||||
|
* Standard-JSON-Interface: Fix a bug related to empty filenames and imports.
|
||||||
|
* SMTChecker: Fix internal errors when analysing tuples.
|
||||||
|
* Yul AST Import: correctly import blocks as statements, switch statements and string literals.
|
||||||
|
|
||||||
|
|
||||||
### 0.6.3 (2020-02-18)
|
### 0.6.3 (2020-02-18)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
@ -6,7 +27,6 @@ Language Features:
|
|||||||
* Report source locations for structured documentation errors.
|
* Report source locations for structured documentation errors.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
|
* 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.
|
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
|
||||||
@ -20,7 +40,6 @@ Bugfixes:
|
|||||||
* Type Checker: Make invalid calls to uncallable types fatal errors instead of regular.
|
* Type Checker: Make invalid calls to uncallable types fatal errors instead of regular.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 0.6.2 (2020-01-27)
|
### 0.6.2 (2020-01-27)
|
||||||
|
|
||||||
Language Features:
|
Language Features:
|
||||||
|
@ -64,7 +64,7 @@ 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%
|
||||||
|
@ -8,4 +8,8 @@ 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 UBSan instrumentation
|
# clang/libfuzzer specific flags for UBSan instrumentation
|
||||||
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=undefined -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)
|
||||||
|
@ -11,7 +11,7 @@ You can interleave Solidity statements with inline assembly in a language close
|
|||||||
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
|
to the one of the Ethereum virtual machine. This gives you more fine-grained control,
|
||||||
which is especially useful when you are enhancing the language by writing libraries.
|
which is especially useful when you are enhancing the language by writing libraries.
|
||||||
|
|
||||||
The language used for inline assembly in Solidity is called `Yul <yul>`_
|
The language used for inline assembly in Solidity is called :ref:`Yul <yul>`
|
||||||
and it is documented in its own section. This section will only cover
|
and it is documented in its own section. This section will only cover
|
||||||
how the inline assembly code can interface with the surrounding Solidity code.
|
how the inline assembly code can interface with the surrounding Solidity code.
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ how the inline assembly code can interface with the surrounding Solidity code.
|
|||||||
|
|
||||||
|
|
||||||
An inline assembly block is marked by ``assembly { ... }``, where the code inside
|
An inline assembly block is marked by ``assembly { ... }``, where the code inside
|
||||||
the curly braces is code in the `Yul <yul>`_ language.
|
the curly braces is code in the :ref:`Yul <yul>` language.
|
||||||
|
|
||||||
The inline assembly code can access local Solidity variables as explained below.
|
The inline assembly code can access local Solidity variables as explained below.
|
||||||
|
|
||||||
@ -172,6 +172,11 @@ 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.
|
||||||
|
|
||||||
|
You can assign to the ``_slot`` part of a local storage variable pointer.
|
||||||
|
For these (structs, arrays or mappings), the ``_offset`` part is always zero.
|
||||||
|
It is not possible to assign to the ``_slot`` or ``_offset`` part of a state variable,
|
||||||
|
though.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Things to Avoid
|
Things to Avoid
|
||||||
@ -225,4 +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.
|
||||||
|
|
||||||
|
@ -888,5 +888,9 @@
|
|||||||
"0.6.3": {
|
"0.6.3": {
|
||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2020-02-18"
|
"released": "2020-02-18"
|
||||||
|
},
|
||||||
|
"0.6.4": {
|
||||||
|
"bugs": [],
|
||||||
|
"released": "2020-03-10"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -252,7 +252,7 @@ which only need to be created if there is a dispute.
|
|||||||
/// This complicated expression just tells you how the address
|
/// This complicated expression just tells you how the address
|
||||||
/// can be pre-computed. It is just there for illustration.
|
/// can be pre-computed. It is just there for illustration.
|
||||||
/// You actually only need ``new D{salt: salt}(arg)``.
|
/// You actually only need ``new D{salt: salt}(arg)``.
|
||||||
address predictedAddress = address(bytes20(keccak256(abi.encodePacked(
|
address predictedAddress = address(uint(keccak256(abi.encodePacked(
|
||||||
byte(0xff),
|
byte(0xff),
|
||||||
address(this),
|
address(this),
|
||||||
salt,
|
salt,
|
||||||
@ -343,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``
|
||||||
|
@ -646,7 +646,7 @@ External (or public) functions have the following members:
|
|||||||
Example that shows how to use the members::
|
Example that shows how to use the members::
|
||||||
|
|
||||||
pragma solidity >=0.4.16 <0.7.0;
|
pragma solidity >=0.4.16 <0.7.0;
|
||||||
|
// This will report a warning
|
||||||
|
|
||||||
contract Example {
|
contract Example {
|
||||||
function f() public payable returns (bytes4) {
|
function f() public payable returns (bytes4) {
|
||||||
|
@ -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;
|
||||||
|
@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(AssemblyItem const& _item)
|
||||||
|
{
|
||||||
|
if (_item.type() != Operation)
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return reverts(_item.instruction());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::reverts(Instruction _instruction)
|
||||||
|
{
|
||||||
|
switch (_instruction)
|
||||||
|
{
|
||||||
|
case Instruction::INVALID:
|
||||||
|
case Instruction::REVERT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
if (_item.type() != Operation)
|
if (_item.type() != Operation)
|
||||||
|
@ -47,6 +47,8 @@ struct SemanticInformation
|
|||||||
static bool altersControlFlow(AssemblyItem const& _item);
|
static bool altersControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(AssemblyItem const& _item);
|
static bool terminatesControlFlow(AssemblyItem const& _item);
|
||||||
static bool terminatesControlFlow(Instruction _instruction);
|
static bool terminatesControlFlow(Instruction _instruction);
|
||||||
|
static bool reverts(AssemblyItem const& _item);
|
||||||
|
static bool reverts(Instruction _instruction);
|
||||||
/// @returns false if the value put on the stack by _item depends on anything else than
|
/// @returns false if the value put on the stack by _item depends on anything else than
|
||||||
/// the information in the current block header, memory, storage or stack.
|
/// the information in the current block header, memory, storage or stack.
|
||||||
static bool isDeterministic(AssemblyItem const& _item);
|
static bool isDeterministic(AssemblyItem const& _item);
|
||||||
|
@ -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);
|
||||||
|
@ -166,6 +166,7 @@ namespace solidity::langutil
|
|||||||
K(Indexed, "indexed", 0) \
|
K(Indexed, "indexed", 0) \
|
||||||
K(Interface, "interface", 0) \
|
K(Interface, "interface", 0) \
|
||||||
K(Internal, "internal", 0) \
|
K(Internal, "internal", 0) \
|
||||||
|
K(Immutable, "immutable", 0) \
|
||||||
K(Import, "import", 0) \
|
K(Import, "import", 0) \
|
||||||
K(Is, "is", 0) \
|
K(Is, "is", 0) \
|
||||||
K(Library, "library", 0) \
|
K(Library, "library", 0) \
|
||||||
@ -243,7 +244,6 @@ namespace solidity::langutil
|
|||||||
K(Default, "default", 0) \
|
K(Default, "default", 0) \
|
||||||
K(Define, "define", 0) \
|
K(Define, "define", 0) \
|
||||||
K(Final, "final", 0) \
|
K(Final, "final", 0) \
|
||||||
K(Immutable, "immutable", 0) \
|
|
||||||
K(Implements, "implements", 0) \
|
K(Implements, "implements", 0) \
|
||||||
K(In, "in", 0) \
|
K(In, "in", 0) \
|
||||||
K(Inline, "inline", 0) \
|
K(Inline, "inline", 0) \
|
||||||
|
@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
|
|||||||
{
|
{
|
||||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
auto const& functionFlow = m_cfg.functionFlow(_function);
|
||||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
|
||||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
|
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
|
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
variableOccurrence->occurrence() ?
|
variableOccurrence->occurrence() ?
|
||||||
variableOccurrence->occurrence()->location() :
|
*variableOccurrence->occurrence() :
|
||||||
variableOccurrence->declaration().location(),
|
variableOccurrence->declaration().location(),
|
||||||
ssl,
|
ssl,
|
||||||
string("This variable is of storage pointer type and can be ") +
|
string("This variable is of storage pointer type and can be ") +
|
||||||
@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
|
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
|
||||||
{
|
{
|
||||||
// collect all nodes reachable from the entry point
|
// collect all nodes reachable from the entry point
|
||||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||||
@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
|||||||
}
|
}
|
||||||
).visited;
|
).visited;
|
||||||
|
|
||||||
// traverse all paths backwards from exit and revert
|
// traverse all paths backwards from exit, revert and transaction return
|
||||||
// and extract (valid) source locations of unreachable nodes into sorted set
|
// and extract (valid) source locations of unreachable nodes into sorted set
|
||||||
std::set<SourceLocation> unreachable;
|
std::set<SourceLocation> unreachable;
|
||||||
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
|
||||||
[&](CFGNode const* _node, auto&& _addChild) {
|
[&](CFGNode const* _node, auto&& _addChild) {
|
||||||
if (!reachable.count(_node) && _node->location.isValid())
|
if (!reachable.count(_node) && _node->location.isValid())
|
||||||
unreachable.insert(_node->location);
|
unreachable.insert(_node->location);
|
||||||
|
@ -36,9 +36,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
||||||
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
|
||||||
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
|
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
|
||||||
/// that can not be reached from @param _entry.
|
/// that can not be reached from @param _entry.
|
||||||
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const;
|
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
|
||||||
|
|
||||||
CFG const& m_cfg;
|
CFG const& m_cfg;
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
#include <libsolidity/analysis/ControlFlowBuilder.h>
|
||||||
|
#include <libyul/AsmData.h>
|
||||||
|
#include <libyul/backends/evm/EVMDialect.h>
|
||||||
|
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
|
|||||||
m_nodeContainer(_nodeContainer),
|
m_nodeContainer(_nodeContainer),
|
||||||
m_currentNode(_functionFlow.entry),
|
m_currentNode(_functionFlow.entry),
|
||||||
m_returnNode(_functionFlow.exit),
|
m_returnNode(_functionFlow.exit),
|
||||||
m_revertNode(_functionFlow.revert)
|
m_revertNode(_functionFlow.revert),
|
||||||
|
m_transactionReturnNode(_functionFlow.transactionReturn)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
||||||
CFG::NodeContainer& _nodeContainer,
|
CFG::NodeContainer& _nodeContainer,
|
||||||
FunctionDefinition const& _function
|
FunctionDefinition const& _function
|
||||||
@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
|
|||||||
functionFlow->entry = _nodeContainer.newNode();
|
functionFlow->entry = _nodeContainer.newNode();
|
||||||
functionFlow->exit = _nodeContainer.newNode();
|
functionFlow->exit = _nodeContainer.newNode();
|
||||||
functionFlow->revert = _nodeContainer.newNode();
|
functionFlow->revert = _nodeContainer.newNode();
|
||||||
|
functionFlow->transactionReturn = _nodeContainer.newNode();
|
||||||
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
|
||||||
builder.appendControlFlow(_function);
|
builder.appendControlFlow(_function);
|
||||||
|
|
||||||
@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
|
|||||||
if (_forStatement.condition())
|
if (_forStatement.condition())
|
||||||
appendControlFlow(*_forStatement.condition());
|
appendControlFlow(*_forStatement.condition());
|
||||||
|
|
||||||
auto loopExpression = newLabel();
|
auto postPart = newLabel();
|
||||||
auto nodes = splitFlow<2>();
|
auto nodes = splitFlow<2>();
|
||||||
auto afterFor = nodes[1];
|
auto afterFor = nodes[1];
|
||||||
m_currentNode = nodes[0];
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
{
|
{
|
||||||
BreakContinueScope scope(*this, afterFor, loopExpression);
|
BreakContinueScope scope(*this, afterFor, postPart);
|
||||||
appendControlFlow(_forStatement.body());
|
appendControlFlow(_forStatement.body());
|
||||||
}
|
}
|
||||||
|
|
||||||
placeAndConnectLabel(loopExpression);
|
placeAndConnectLabel(postPart);
|
||||||
|
|
||||||
if (auto expression = _forStatement.loopExpression())
|
if (auto expression = _forStatement.loopExpression())
|
||||||
appendControlFlow(*expression);
|
appendControlFlow(*expression);
|
||||||
@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
|
|||||||
appendControlFlow(*returnParameter);
|
appendControlFlow(*returnParameter);
|
||||||
m_returnNode->variableOccurrences.emplace_back(
|
m_returnNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Return,
|
VariableOccurrence::Kind::Return
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*returnParameter,
|
*returnParameter,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
&_return
|
_return.location()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
connect(m_currentNode, m_returnNode);
|
connect(m_currentNode, m_returnNode);
|
||||||
@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
|
|||||||
|
|
||||||
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
|
||||||
{
|
{
|
||||||
solAssert(!!m_currentNode, "");
|
solAssert(!!m_currentNode && !m_inlineAssembly, "");
|
||||||
visitNode(_inlineAssembly);
|
|
||||||
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
|
m_inlineAssembly = &_inlineAssembly;
|
||||||
|
(*this)(_inlineAssembly.operations());
|
||||||
|
m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::visit(yul::Statement const& _statement)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_statement));
|
||||||
|
ASTWalker::visit(_statement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::If const& _if)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_if.condition);
|
||||||
|
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
(*this)(_if.body);
|
||||||
|
nodes[0] = m_currentNode;
|
||||||
|
mergeFlow(nodes, nodes[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_switch.expression);
|
||||||
|
|
||||||
|
auto beforeSwitch = m_currentNode;
|
||||||
|
|
||||||
|
auto nodes = splitFlow(_switch.cases.size());
|
||||||
|
for (size_t i = 0u; i < _switch.cases.size(); ++i)
|
||||||
{
|
{
|
||||||
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
|
m_currentNode = nodes[i];
|
||||||
|
(*this)(_switch.cases[i].body);
|
||||||
|
nodes[i] = m_currentNode;
|
||||||
|
}
|
||||||
|
mergeFlow(nodes);
|
||||||
|
|
||||||
|
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
|
||||||
|
if (!hasDefault)
|
||||||
|
connect(beforeSwitch, m_currentNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
|
||||||
|
(*this)(_forLoop.pre);
|
||||||
|
|
||||||
|
auto condition = createLabelHere();
|
||||||
|
|
||||||
|
if (_forLoop.condition)
|
||||||
|
visit(*_forLoop.condition);
|
||||||
|
|
||||||
|
auto loopExpression = newLabel();
|
||||||
|
auto nodes = splitFlow<2>();
|
||||||
|
auto afterFor = nodes[1];
|
||||||
|
m_currentNode = nodes[0];
|
||||||
|
|
||||||
|
{
|
||||||
|
BreakContinueScope scope(*this, afterFor, loopExpression);
|
||||||
|
(*this)(_forLoop.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
placeAndConnectLabel(loopExpression);
|
||||||
|
|
||||||
|
(*this)(_forLoop.post);
|
||||||
|
|
||||||
|
connect(m_currentNode, condition);
|
||||||
|
m_currentNode = afterFor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Break const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_breakJump, "");
|
||||||
|
connect(m_currentNode, m_breakJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Continue const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
solAssert(m_continueJump, "");
|
||||||
|
connect(m_currentNode, m_continueJump);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
if (externalReferences.count(&_identifier))
|
||||||
|
{
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*variableDeclaration,
|
*declaration,
|
||||||
VariableOccurrence::Kind::InlineAssembly,
|
VariableOccurrence::Kind::Access,
|
||||||
&_inlineAssembly
|
_identifier.location
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
visit(*_assignment.value);
|
||||||
|
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
|
||||||
|
for (auto const& variable: _assignment.variableNames)
|
||||||
|
if (externalReferences.count(&variable))
|
||||||
|
if (auto const* declaration = dynamic_cast<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
|
||||||
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
|
*declaration,
|
||||||
|
VariableOccurrence::Kind::Assignment,
|
||||||
|
variable.location
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
using namespace yul;
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
yul::ASTWalker::operator()(_functionCall);
|
||||||
|
|
||||||
|
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
|
||||||
|
if (builtinFunction->controlFlowSideEffects.terminates)
|
||||||
|
{
|
||||||
|
if (builtinFunction->controlFlowSideEffects.reverts)
|
||||||
|
connect(m_currentNode, m_revertNode);
|
||||||
|
else
|
||||||
|
connect(m_currentNode, m_transactionReturnNode);
|
||||||
|
m_currentNode = newLabel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentNode && m_inlineAssembly, "");
|
||||||
|
// External references cannot be accessed from within functions, so we can ignore their control flow.
|
||||||
|
// TODO: we might still want to track if they always revert or return, though.
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlFlowBuilder::operator()(yul::Leave const&)
|
||||||
|
{
|
||||||
|
// This has to be implemented, if we ever decide to visit functions.
|
||||||
|
solUnimplementedAssert(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
||||||
@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
|
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Declaration,
|
VariableOccurrence::Kind::Declaration
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle declaration with immediate assignment.
|
// Handle declaration with immediate assignment.
|
||||||
@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
_variableDeclaration.value().get()
|
_variableDeclaration.value()->location()
|
||||||
);
|
);
|
||||||
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
|
||||||
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
|
||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
_variableDeclaration,
|
_variableDeclaration,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment
|
||||||
nullptr
|
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
|
|||||||
m_currentNode->variableOccurrences.emplace_back(
|
m_currentNode->variableOccurrences.emplace_back(
|
||||||
*var,
|
*var,
|
||||||
VariableOccurrence::Kind::Assignment,
|
VariableOccurrence::Kind::Assignment,
|
||||||
expression
|
expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
|
|||||||
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
|
||||||
VariableOccurrence::Kind::Assignment :
|
VariableOccurrence::Kind::Assignment :
|
||||||
VariableOccurrence::Kind::Access,
|
VariableOccurrence::Kind::Access,
|
||||||
&_identifier
|
_identifier.location()
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -30,7 +31,7 @@ namespace solidity::frontend {
|
|||||||
* Modifiers are not yet applied to the functions. This is done in a second
|
* Modifiers are not yet applied to the functions. This is done in a second
|
||||||
* step in the CFG class.
|
* step in the CFG class.
|
||||||
*/
|
*/
|
||||||
class ControlFlowBuilder: private ASTConstVisitor
|
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
static std::unique_ptr<FunctionFlow> createFunctionFlow(
|
||||||
@ -39,7 +40,10 @@ public:
|
|||||||
);
|
);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
|
explicit ControlFlowBuilder(
|
||||||
|
CFG::NodeContainer& _nodeContainer,
|
||||||
|
FunctionFlow const& _functionFlow
|
||||||
|
);
|
||||||
|
|
||||||
// Visits for constructing the control flow.
|
// Visits for constructing the control flow.
|
||||||
bool visit(BinaryOperation const& _operation) override;
|
bool visit(BinaryOperation const& _operation) override;
|
||||||
@ -62,6 +66,17 @@ private:
|
|||||||
// Visits for filling variable occurrences.
|
// Visits for filling variable occurrences.
|
||||||
bool visit(FunctionTypeName const& _functionTypeName) override;
|
bool visit(FunctionTypeName const& _functionTypeName) override;
|
||||||
bool visit(InlineAssembly const& _inlineAssembly) override;
|
bool visit(InlineAssembly const& _inlineAssembly) override;
|
||||||
|
void visit(yul::Statement const& _statement) override;
|
||||||
|
void operator()(yul::If const& _if) override;
|
||||||
|
void operator()(yul::Switch const& _switch) override;
|
||||||
|
void operator()(yul::ForLoop const& _for) override;
|
||||||
|
void operator()(yul::Break const&) override;
|
||||||
|
void operator()(yul::Continue const&) override;
|
||||||
|
void operator()(yul::Identifier const& _identifier) override;
|
||||||
|
void operator()(yul::Assignment const& _assignment) override;
|
||||||
|
void operator()(yul::FunctionCall const& _functionCall) override;
|
||||||
|
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
|
||||||
|
void operator()(yul::Leave const& _leaveStatement) override;
|
||||||
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
bool visit(VariableDeclaration const& _variableDeclaration) override;
|
||||||
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
@ -70,6 +85,9 @@ protected:
|
|||||||
bool visitNode(ASTNode const&) override;
|
bool visitNode(ASTNode const&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using ASTConstVisitor::visit;
|
||||||
|
using yul::ASTWalker::visit;
|
||||||
|
using yul::ASTWalker::operator();
|
||||||
|
|
||||||
/// Appends the control flow of @a _node to the current control flow.
|
/// Appends the control flow of @a _node to the current control flow.
|
||||||
void appendControlFlow(ASTNode const& _node);
|
void appendControlFlow(ASTNode const& _node);
|
||||||
@ -136,6 +154,7 @@ private:
|
|||||||
CFGNode* m_currentNode = nullptr;
|
CFGNode* m_currentNode = nullptr;
|
||||||
CFGNode* m_returnNode = nullptr;
|
CFGNode* m_returnNode = nullptr;
|
||||||
CFGNode* m_revertNode = nullptr;
|
CFGNode* m_revertNode = nullptr;
|
||||||
|
CFGNode* m_transactionReturnNode = nullptr;
|
||||||
|
|
||||||
/// The current jump destination of break Statements.
|
/// The current jump destination of break Statements.
|
||||||
CFGNode* m_breakJump = nullptr;
|
CFGNode* m_breakJump = nullptr;
|
||||||
@ -145,6 +164,8 @@ private:
|
|||||||
CFGNode* m_placeholderEntry = nullptr;
|
CFGNode* m_placeholderEntry = nullptr;
|
||||||
CFGNode* m_placeholderExit = nullptr;
|
CFGNode* m_placeholderExit = nullptr;
|
||||||
|
|
||||||
|
InlineAssembly const* m_inlineAssembly = nullptr;
|
||||||
|
|
||||||
/// Helper class that replaces the break and continue jump destinations for the
|
/// Helper class that replaces the break and continue jump destinations for the
|
||||||
/// current scope and restores the originals at the end of the scope.
|
/// current scope and restores the originals at the end of the scope.
|
||||||
class BreakContinueScope
|
class BreakContinueScope
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
#include <libsolidity/ast/ASTVisitor.h>
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
#include <liblangutil/ErrorReporter.h>
|
#include <liblangutil/ErrorReporter.h>
|
||||||
|
#include <liblangutil/EVMVersion.h>
|
||||||
#include <liblangutil/SourceLocation.h>
|
#include <liblangutil/SourceLocation.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -33,8 +34,8 @@ namespace solidity::frontend
|
|||||||
/**
|
/**
|
||||||
* Occurrence of a variable in a block of control flow.
|
* Occurrence of a variable in a block of control flow.
|
||||||
* Stores the declaration of the referenced variable, the
|
* Stores the declaration of the referenced variable, the
|
||||||
* kind of the occurrence and possibly the node at which
|
* kind of the occurrence and possibly the source location
|
||||||
* it occurred.
|
* at which it occurred.
|
||||||
*/
|
*/
|
||||||
class VariableOccurrence
|
class VariableOccurrence
|
||||||
{
|
{
|
||||||
@ -47,7 +48,7 @@ public:
|
|||||||
Assignment,
|
Assignment,
|
||||||
InlineAssembly
|
InlineAssembly
|
||||||
};
|
};
|
||||||
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence):
|
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
|
||||||
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -57,8 +58,8 @@ public:
|
|||||||
{
|
{
|
||||||
if (m_occurrence && _rhs.m_occurrence)
|
if (m_occurrence && _rhs.m_occurrence)
|
||||||
{
|
{
|
||||||
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
|
if (*m_occurrence < *_rhs.m_occurrence) return true;
|
||||||
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
|
if (*_rhs.m_occurrence < *m_occurrence) return false;
|
||||||
}
|
}
|
||||||
else if (_rhs.m_occurrence)
|
else if (_rhs.m_occurrence)
|
||||||
return true;
|
return true;
|
||||||
@ -74,14 +75,14 @@ public:
|
|||||||
|
|
||||||
VariableDeclaration const& declaration() const { return m_declaration; }
|
VariableDeclaration const& declaration() const { return m_declaration; }
|
||||||
Kind kind() const { return m_occurrenceKind; };
|
Kind kind() const { return m_occurrenceKind; };
|
||||||
ASTNode const* occurrence() const { return m_occurrence; }
|
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
|
||||||
private:
|
private:
|
||||||
/// Declaration of the occurring variable.
|
/// Declaration of the occurring variable.
|
||||||
VariableDeclaration const& m_declaration;
|
VariableDeclaration const& m_declaration;
|
||||||
/// Kind of occurrence.
|
/// Kind of occurrence.
|
||||||
Kind m_occurrenceKind = Kind::Access;
|
Kind m_occurrenceKind = Kind::Access;
|
||||||
/// AST node at which the variable occurred, if available (may be nullptr).
|
/// Source location at which the variable occurred, if available (may be nullptr).
|
||||||
ASTNode const* m_occurrence = nullptr;
|
std::optional<langutil::SourceLocation> m_occurrence;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,6 +120,10 @@ struct FunctionFlow
|
|||||||
/// This node is empty does not have any exits, but may have multiple entries
|
/// This node is empty does not have any exits, but may have multiple entries
|
||||||
/// (e.g. all assert, require, revert and throw statements).
|
/// (e.g. all assert, require, revert and throw statements).
|
||||||
CFGNode* revert = nullptr;
|
CFGNode* revert = nullptr;
|
||||||
|
/// Transaction return node. Destination node for inline assembly "return" calls.
|
||||||
|
/// This node is empty and does not have any exits, but may have multiple entries
|
||||||
|
/// (e.g. all inline assembly return calls).
|
||||||
|
CFGNode* transactionReturn = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CFG: private ASTConstVisitor
|
class CFG: private ASTConstVisitor
|
||||||
@ -140,7 +145,6 @@ public:
|
|||||||
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
std::vector<std::unique_ptr<CFGNode>> m_nodes;
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
/// Node container.
|
/// Node container.
|
||||||
|
@ -67,6 +67,22 @@ void ReferencesResolver::endVisit(Block const& _block)
|
|||||||
m_resolver.setScope(_block.scope());
|
m_resolver.setScope(_block.scope());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ReferencesResolver::visit(TryCatchClause const& _tryCatchClause)
|
||||||
|
{
|
||||||
|
if (!m_resolveInsideCode)
|
||||||
|
return false;
|
||||||
|
m_resolver.setScope(&_tryCatchClause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReferencesResolver::endVisit(TryCatchClause const& _tryCatchClause)
|
||||||
|
{
|
||||||
|
if (!m_resolveInsideCode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_resolver.setScope(_tryCatchClause.scope());
|
||||||
|
}
|
||||||
|
|
||||||
bool ReferencesResolver::visit(ForStatement const& _for)
|
bool ReferencesResolver::visit(ForStatement const& _for)
|
||||||
{
|
{
|
||||||
if (!m_resolveInsideCode)
|
if (!m_resolveInsideCode)
|
||||||
|
@ -70,6 +70,8 @@ private:
|
|||||||
|
|
||||||
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(TryCatchClause const& _tryCatchClause) override;
|
||||||
|
void endVisit(TryCatchClause const& _tryCatchClause) override;
|
||||||
bool visit(ForStatement const& _for) override;
|
bool visit(ForStatement const& _for) override;
|
||||||
void endVisit(ForStatement const& _for) override;
|
void endVisit(ForStatement const& _for) override;
|
||||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||||
|
@ -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())
|
||||||
@ -1693,22 +1703,21 @@ void TypeChecker::typeCheckFunctionCall(
|
|||||||
|
|
||||||
if (_functionType->kind() == FunctionType::Kind::Declaration)
|
if (_functionType->kind() == FunctionType::Kind::Declaration)
|
||||||
{
|
{
|
||||||
m_errorReporter.typeError(
|
if (
|
||||||
_functionCall.location(),
|
m_scope->derivesFrom(*_functionType->declaration().annotation().contract) &&
|
||||||
"Cannot call function via contract type name."
|
!dynamic_cast<FunctionDefinition const&>(_functionType->declaration()).isImplemented()
|
||||||
);
|
)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCall.location(),
|
||||||
|
"Cannot call unimplemented base function."
|
||||||
|
);
|
||||||
|
else
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCall.location(),
|
||||||
|
"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 (
|
||||||
@ -2302,7 +2311,11 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
|||||||
else if (!expressionFunctionType->isPayable())
|
else if (!expressionFunctionType->isPayable())
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
_functionCallOptions.location(),
|
_functionCallOptions.location(),
|
||||||
"Cannot set option \"value\" on a non-payable function type."
|
kind == FunctionType::Kind::Creation ?
|
||||||
|
"Cannot set option \"value\", since the constructor of " +
|
||||||
|
expressionFunctionType->returnParameterTypes().front()->toString() +
|
||||||
|
" is not payable." :
|
||||||
|
"Cannot set option \"value\" on a non-payable function type."
|
||||||
);
|
);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2512,12 +2525,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
annotation.type = possibleMembers.front().type;
|
annotation.type = possibleMembers.front().type;
|
||||||
|
|
||||||
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
if (auto funType = dynamic_cast<FunctionType const*>(annotation.type))
|
||||||
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
!funType->bound() || exprType->isImplicitlyConvertibleTo(*funType->selfType()),
|
||||||
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
"Function \"" + memberName + "\" cannot be called on an object of type " +
|
||||||
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
exprType->toString() + " (expected " + funType->selfType()->toString() + ")."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
dynamic_cast<FunctionType const*>(exprType) &&
|
||||||
|
!annotation.referencedDeclaration &&
|
||||||
|
(memberName == "value" || memberName == "gas")
|
||||||
|
)
|
||||||
|
m_errorReporter.warning(
|
||||||
|
_memberAccess.location(),
|
||||||
|
"Using \"." + memberName + "(...)\" is deprecated. Use \"{" + memberName + ": ...}\" instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
if (auto const* structType = dynamic_cast<StructType const*>(exprType))
|
||||||
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
|
||||||
else if (exprType->category() == Type::Category::Array)
|
else if (exprType->category() == Type::Category::Array)
|
||||||
|
@ -390,7 +390,7 @@ DeclarationAnnotation& Declaration::annotation() const
|
|||||||
bool VariableDeclaration::isLValue() const
|
bool VariableDeclaration::isLValue() const
|
||||||
{
|
{
|
||||||
// Constant declared variables are Read-Only
|
// Constant declared variables are Read-Only
|
||||||
if (m_isConstant)
|
if (isConstant())
|
||||||
return false;
|
return false;
|
||||||
// External function arguments of reference type are Read-Only
|
// External function arguments of reference type are Read-Only
|
||||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
||||||
|
@ -814,6 +814,7 @@ class VariableDeclaration: public Declaration
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Location { Unspecified, Storage, Memory, CallData };
|
enum Location { Unspecified, Storage, Memory, CallData };
|
||||||
|
enum class Constantness { Mutable, Immutable, Constant };
|
||||||
|
|
||||||
VariableDeclaration(
|
VariableDeclaration(
|
||||||
int64_t _id,
|
int64_t _id,
|
||||||
@ -824,7 +825,7 @@ public:
|
|||||||
Visibility _visibility,
|
Visibility _visibility,
|
||||||
bool _isStateVar = false,
|
bool _isStateVar = false,
|
||||||
bool _isIndexed = false,
|
bool _isIndexed = false,
|
||||||
bool _isConstant = false,
|
Constantness _constantness = Constantness::Mutable,
|
||||||
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
ASTPointer<OverrideSpecifier> const& _overrides = nullptr,
|
||||||
Location _referenceLocation = Location::Unspecified
|
Location _referenceLocation = Location::Unspecified
|
||||||
):
|
):
|
||||||
@ -833,7 +834,7 @@ public:
|
|||||||
m_value(_value),
|
m_value(_value),
|
||||||
m_isStateVariable(_isStateVar),
|
m_isStateVariable(_isStateVar),
|
||||||
m_isIndexed(_isIndexed),
|
m_isIndexed(_isIndexed),
|
||||||
m_isConstant(_isConstant),
|
m_constantness(_constantness),
|
||||||
m_overrides(_overrides),
|
m_overrides(_overrides),
|
||||||
m_location(_referenceLocation) {}
|
m_location(_referenceLocation) {}
|
||||||
|
|
||||||
@ -877,7 +878,7 @@ public:
|
|||||||
bool hasReferenceOrMappingType() const;
|
bool hasReferenceOrMappingType() const;
|
||||||
bool isStateVariable() const { return m_isStateVariable; }
|
bool isStateVariable() const { return m_isStateVariable; }
|
||||||
bool isIndexed() const { return m_isIndexed; }
|
bool isIndexed() const { return m_isIndexed; }
|
||||||
bool isConstant() const { return m_isConstant; }
|
bool isConstant() const { return m_constantness == Constantness::Constant; }
|
||||||
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
|
ASTPointer<OverrideSpecifier> const& overrides() const { return m_overrides; }
|
||||||
Location referenceLocation() const { return m_location; }
|
Location referenceLocation() const { return m_location; }
|
||||||
/// @returns a set of allowed storage locations for the variable.
|
/// @returns a set of allowed storage locations for the variable.
|
||||||
@ -904,7 +905,8 @@ private:
|
|||||||
ASTPointer<Expression> m_value;
|
ASTPointer<Expression> m_value;
|
||||||
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
|
bool m_isStateVariable = false; ///< Whether or not this is a contract state variable
|
||||||
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
|
bool m_isIndexed = false; ///< Whether this is an indexed variable (used by events).
|
||||||
bool m_isConstant = false; ///< Whether the variable is a compile-time constant.
|
/// Whether the variable is "constant", "immutable" or non-marked (mutable).
|
||||||
|
Constantness m_constantness = Constantness::Mutable;
|
||||||
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
|
ASTPointer<OverrideSpecifier> m_overrides; ///< Contains the override specifier node
|
||||||
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
|
Location m_location = Location::Unspecified; ///< Location of the variable if it is of reference type.
|
||||||
};
|
};
|
||||||
|
@ -411,6 +411,12 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
|||||||
{
|
{
|
||||||
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
|
astAssert(_node["name"].isString(), "Expected 'name' to be a string!");
|
||||||
|
|
||||||
|
VariableDeclaration::Constantness constantness{};
|
||||||
|
if (memberAsBool(_node, "constant"))
|
||||||
|
constantness = VariableDeclaration::Constantness::Constant;
|
||||||
|
else
|
||||||
|
constantness = VariableDeclaration::Constantness::Mutable;
|
||||||
|
|
||||||
return createASTNode<VariableDeclaration>(
|
return createASTNode<VariableDeclaration>(
|
||||||
_node,
|
_node,
|
||||||
nullOrCast<TypeName>(member(_node, "typeName")),
|
nullOrCast<TypeName>(member(_node, "typeName")),
|
||||||
@ -419,7 +425,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
|
|||||||
visibility(_node),
|
visibility(_node),
|
||||||
memberAsBool(_node, "stateVariable"),
|
memberAsBool(_node, "stateVariable"),
|
||||||
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
|
||||||
memberAsBool(_node, "constant"),
|
constantness,
|
||||||
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
_node["overrides"].isNull() ? nullptr : createOverrideSpecifier(member(_node, "overrides")),
|
||||||
location(_node)
|
location(_node)
|
||||||
);
|
);
|
||||||
|
@ -104,6 +104,8 @@ yul::Statement AsmJsonImporter::createStatement(Json::Value const& _node)
|
|||||||
return createContinue(_node);
|
return createContinue(_node);
|
||||||
else if (nodeType == "Leave")
|
else if (nodeType == "Leave")
|
||||||
return createLeave(_node);
|
return createLeave(_node);
|
||||||
|
else if (nodeType == "Block")
|
||||||
|
return createBlock(_node);
|
||||||
else
|
else
|
||||||
astAssert(false, "Invalid nodeType as statement");
|
astAssert(false, "Invalid nodeType as statement");
|
||||||
}
|
}
|
||||||
@ -158,10 +160,9 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
lit.value = YulString{member(_node, "value").asString()};
|
lit.value = YulString{member(_node, "value").asString()};
|
||||||
lit.type= YulString{member(_node, "type").asString()};
|
lit.type= YulString{member(_node, "type").asString()};
|
||||||
|
|
||||||
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
|
||||||
|
|
||||||
if (kind == "number")
|
if (kind == "number")
|
||||||
{
|
{
|
||||||
|
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||||
lit.kind = yul::LiteralKind::Number;
|
lit.kind = yul::LiteralKind::Number;
|
||||||
astAssert(
|
astAssert(
|
||||||
scanner.currentToken() == Token::Number,
|
scanner.currentToken() == Token::Number,
|
||||||
@ -170,6 +171,7 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
}
|
}
|
||||||
else if (kind == "bool")
|
else if (kind == "bool")
|
||||||
{
|
{
|
||||||
|
langutil::Scanner scanner{langutil::CharStream(lit.value.str(), "")};
|
||||||
lit.kind = yul::LiteralKind::Boolean;
|
lit.kind = yul::LiteralKind::Boolean;
|
||||||
astAssert(
|
astAssert(
|
||||||
scanner.currentToken() == Token::TrueLiteral ||
|
scanner.currentToken() == Token::TrueLiteral ||
|
||||||
@ -180,7 +182,10 @@ yul::Literal AsmJsonImporter::createLiteral(Json::Value const& _node)
|
|||||||
else if (kind == "string")
|
else if (kind == "string")
|
||||||
{
|
{
|
||||||
lit.kind = yul::LiteralKind::String;
|
lit.kind = yul::LiteralKind::String;
|
||||||
astAssert(scanner.currentToken() == Token::StringLiteral, "Expected string literal!");
|
astAssert(
|
||||||
|
lit.value.str().size() <= 32,
|
||||||
|
"String literal too long (" + to_string(lit.value.str().size()) + " > 32)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(false, "unknown type of literal");
|
solAssert(false, "unknown type of literal");
|
||||||
@ -268,7 +273,11 @@ yul::If AsmJsonImporter::createIf(Json::Value const& _node)
|
|||||||
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
||||||
{
|
{
|
||||||
auto caseStatement = createAsmNode<yul::Case>(_node);
|
auto caseStatement = createAsmNode<yul::Case>(_node);
|
||||||
caseStatement.value = member(_node, "value").asString() == "default" ? nullptr : make_unique<yul::Literal>(createLiteral(member(_node, "value")));
|
auto const& value = member(_node, "value");
|
||||||
|
if (value.isString())
|
||||||
|
astAssert(value.asString() == "default", "Expected default case");
|
||||||
|
else
|
||||||
|
caseStatement.value = make_unique<yul::Literal>(createLiteral(value));
|
||||||
caseStatement.body = createBlock(member(_node, "body"));
|
caseStatement.body = createBlock(member(_node, "body"));
|
||||||
return caseStatement;
|
return caseStatement;
|
||||||
}
|
}
|
||||||
@ -276,7 +285,7 @@ yul::Case AsmJsonImporter::createCase(Json::Value const& _node)
|
|||||||
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
|
yul::Switch AsmJsonImporter::createSwitch(Json::Value const& _node)
|
||||||
{
|
{
|
||||||
auto switchStatement = createAsmNode<yul::Switch>(_node);
|
auto switchStatement = createAsmNode<yul::Switch>(_node);
|
||||||
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "value")));
|
switchStatement.expression = make_unique<yul::Expression>(createExpression(member(_node, "expression")));
|
||||||
for (auto const& var: member(_node, "cases"))
|
for (auto const& var: member(_node, "cases"))
|
||||||
switchStatement.cases.emplace_back(createCase(var));
|
switchStatement.cases.emplace_back(createCase(var));
|
||||||
return switchStatement;
|
return switchStatement;
|
||||||
|
@ -2924,7 +2924,10 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
|
|||||||
{
|
{
|
||||||
case Kind::External:
|
case Kind::External:
|
||||||
case Kind::DelegateCall:
|
case Kind::DelegateCall:
|
||||||
slots = {make_tuple("address", TypeProvider::address()), make_tuple("functionIdentifier", TypeProvider::fixedBytes(4))};
|
slots = {
|
||||||
|
make_tuple("address", TypeProvider::address()),
|
||||||
|
make_tuple("functionIdentifier", TypeProvider::uint(32))
|
||||||
|
};
|
||||||
break;
|
break;
|
||||||
case Kind::BareCall:
|
case Kind::BareCall:
|
||||||
case Kind::BareCallCode:
|
case Kind::BareCallCode:
|
||||||
@ -3471,7 +3474,15 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
if (!contract.isLibrary() && inDerivingScope && declaration->isVisibleInDerivedContracts())
|
||||||
members.emplace_back(declaration->name(), declaration->type(), declaration);
|
{
|
||||||
|
if (
|
||||||
|
auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(declaration);
|
||||||
|
functionDefinition && !functionDefinition->isImplemented()
|
||||||
|
)
|
||||||
|
members.emplace_back(declaration->name(), declaration->typeViaContractName(), declaration);
|
||||||
|
else
|
||||||
|
members.emplace_back(declaration->name(), declaration->type(), declaration);
|
||||||
|
}
|
||||||
else if (
|
else if (
|
||||||
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
(contract.isLibrary() && declaration->isVisibleAsLibraryMember()) ||
|
||||||
declaration->isVisibleViaContractTypeAccess()
|
declaration->isVisibleViaContractTypeAccess()
|
||||||
|
@ -55,7 +55,7 @@ string ABIFunctions::tupleEncoder(
|
|||||||
functionName += t->identifier() + "_";
|
functionName += t->identifier() + "_";
|
||||||
functionName += options.toFunctionNameSuffix();
|
functionName += options.toFunctionNameSuffix();
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
// Note that the values are in reverse due to the difference in calling semantics.
|
// Note that the values are in reverse due to the difference in calling semantics.
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(headStart <valueParams>) -> tail {
|
function <functionName>(headStart <valueParams>) -> tail {
|
||||||
@ -121,7 +121,7 @@ string ABIFunctions::tupleEncoderPacked(
|
|||||||
functionName += t->identifier() + "_";
|
functionName += t->identifier() + "_";
|
||||||
functionName += options.toFunctionNameSuffix();
|
functionName += options.toFunctionNameSuffix();
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
solAssert(!_givenTypes.empty(), "");
|
solAssert(!_givenTypes.empty(), "");
|
||||||
|
|
||||||
// Note that the values are in reverse due to the difference in calling semantics.
|
// Note that the values are in reverse due to the difference in calling semantics.
|
||||||
@ -173,7 +173,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
if (_fromMemory)
|
if (_fromMemory)
|
||||||
functionName += "_fromMemory";
|
functionName += "_fromMemory";
|
||||||
|
|
||||||
return createExternallyUsedFunction(functionName, [&]() {
|
return createFunction(functionName, [&]() {
|
||||||
TypePointers decodingTypes;
|
TypePointers decodingTypes;
|
||||||
for (auto const& t: _types)
|
for (auto const& t: _types)
|
||||||
decodingTypes.emplace_back(t->decodingType());
|
decodingTypes.emplace_back(t->decodingType());
|
||||||
@ -240,13 +240,6 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<string, set<string>> ABIFunctions::requestedFunctions()
|
|
||||||
{
|
|
||||||
std::set<string> empty;
|
|
||||||
swap(empty, m_externallyUsedFunctions);
|
|
||||||
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
|
|
||||||
}
|
|
||||||
|
|
||||||
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||||
{
|
{
|
||||||
string suffix;
|
string suffix;
|
||||||
@ -1499,14 +1492,7 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
|
|||||||
|
|
||||||
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
||||||
{
|
{
|
||||||
return m_functionCollector->createFunction(_name, _creator);
|
return m_functionCollector.createFunction(_name, _creator);
|
||||||
}
|
|
||||||
|
|
||||||
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
|
|
||||||
{
|
|
||||||
string name = createFunction(_name, _creator);
|
|
||||||
m_externallyUsedFunctions.insert(name);
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||||
|
@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
@ -58,11 +57,11 @@ public:
|
|||||||
explicit ABIFunctions(
|
explicit ABIFunctions(
|
||||||
langutil::EVMVersion _evmVersion,
|
langutil::EVMVersion _evmVersion,
|
||||||
RevertStrings _revertStrings,
|
RevertStrings _revertStrings,
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
MultiUseYulFunctionCollector& _functionCollector
|
||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_functionCollector(std::move(_functionCollector)),
|
m_functionCollector(_functionCollector),
|
||||||
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
|
m_utils(_evmVersion, m_revertStrings, m_functionCollector)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -104,12 +103,6 @@ public:
|
|||||||
/// stack slot, it takes exactly that number of values.
|
/// stack slot, it takes exactly that number of values.
|
||||||
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
|
std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
|
||||||
|
|
||||||
/// @returns concatenation of all generated functions and a set of the
|
|
||||||
/// externally used functions.
|
|
||||||
/// Clears the internal list, i.e. calling it again will result in an
|
|
||||||
/// empty return value.
|
|
||||||
std::pair<std::string, std::set<std::string>> requestedFunctions();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct EncodingOptions
|
struct EncodingOptions
|
||||||
{
|
{
|
||||||
@ -239,11 +232,6 @@ private:
|
|||||||
/// cases.
|
/// cases.
|
||||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||||
|
|
||||||
/// Helper function that uses @a _creator to create a function and add it to
|
|
||||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
|
||||||
/// cases. Also adds it to the list of externally used functions.
|
|
||||||
std::string createExternallyUsedFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
|
||||||
|
|
||||||
/// @returns the size of the static part of the encoding of the given types.
|
/// @returns the size of the static part of the encoding of the given types.
|
||||||
static size_t headSize(TypePointers const& _targetTypes);
|
static size_t headSize(TypePointers const& _targetTypes);
|
||||||
|
|
||||||
@ -259,8 +247,7 @@ private:
|
|||||||
|
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings const m_revertStrings;
|
RevertStrings const m_revertStrings;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
MultiUseYulFunctionCollector& m_functionCollector;
|
||||||
std::set<std::string> m_externallyUsedFunctions;
|
|
||||||
YulUtilFunctions m_utils;
|
YulUtilFunctions m_utils;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -94,6 +94,20 @@ void CompilerContext::callLowLevelFunction(
|
|||||||
*this << retTag.tag();
|
*this << retTag.tag();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerContext::callYulFunction(
|
||||||
|
string const& _name,
|
||||||
|
unsigned _inArgs,
|
||||||
|
unsigned _outArgs
|
||||||
|
)
|
||||||
|
{
|
||||||
|
m_externallyUsedYulFunctions.insert(_name);
|
||||||
|
auto const retTag = pushNewTag();
|
||||||
|
CompilerUtils(*this).moveIntoStack(_inArgs);
|
||||||
|
appendJumpTo(namedTag(_name));
|
||||||
|
adjustStackOffset(int(_outArgs) - 1 - _inArgs);
|
||||||
|
*this << retTag.tag();
|
||||||
|
}
|
||||||
|
|
||||||
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
|
evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag(
|
||||||
string const& _name,
|
string const& _name,
|
||||||
unsigned _inArgs,
|
unsigned _inArgs,
|
||||||
@ -133,6 +147,13 @@ void CompilerContext::appendMissingLowLevelFunctions()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pair<string, set<string>> CompilerContext::requestedYulFunctions()
|
||||||
|
{
|
||||||
|
set<string> empty;
|
||||||
|
swap(empty, m_externallyUsedYulFunctions);
|
||||||
|
return {m_yulFunctionCollector.requestedFunctions(), std::move(empty)};
|
||||||
|
}
|
||||||
|
|
||||||
void CompilerContext::addVariable(
|
void CompilerContext::addVariable(
|
||||||
VariableDeclaration const& _declaration,
|
VariableDeclaration const& _declaration,
|
||||||
unsigned _offsetToCurrent
|
unsigned _offsetToCurrent
|
||||||
|
@ -65,7 +65,8 @@ public:
|
|||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_runtimeContext(_runtimeContext),
|
m_runtimeContext(_runtimeContext),
|
||||||
m_abiFunctions(m_evmVersion, m_revertStrings)
|
m_abiFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector),
|
||||||
|
m_yulUtilFunctions(m_evmVersion, m_revertStrings, m_yulFunctionCollector)
|
||||||
{
|
{
|
||||||
if (m_runtimeContext)
|
if (m_runtimeContext)
|
||||||
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
||||||
@ -131,6 +132,14 @@ public:
|
|||||||
unsigned _outArgs,
|
unsigned _outArgs,
|
||||||
std::function<void(CompilerContext&)> const& _generator
|
std::function<void(CompilerContext&)> const& _generator
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Appends a call to a yul function and registers the function as externally used.
|
||||||
|
void callYulFunction(
|
||||||
|
std::string const& _name,
|
||||||
|
unsigned _inArgs,
|
||||||
|
unsigned _outArgs
|
||||||
|
);
|
||||||
|
|
||||||
/// Returns the tag of the named low-level function and inserts the generator into the
|
/// Returns the tag of the named low-level function and inserts the generator into the
|
||||||
/// list of low-level-functions to be generated, unless it already exists.
|
/// list of low-level-functions to be generated, unless it already exists.
|
||||||
/// Note that the generator should not assume that objects are still alive when it is called,
|
/// Note that the generator should not assume that objects are still alive when it is called,
|
||||||
@ -144,6 +153,12 @@ public:
|
|||||||
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
||||||
void appendMissingLowLevelFunctions();
|
void appendMissingLowLevelFunctions();
|
||||||
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
||||||
|
YulUtilFunctions& utilFunctions() { return m_yulUtilFunctions; }
|
||||||
|
/// @returns concatenation of all generated functions and a set of the
|
||||||
|
/// externally used functions.
|
||||||
|
/// Clears the internal list, i.e. calling it again will result in an
|
||||||
|
/// empty return value.
|
||||||
|
std::pair<std::string, std::set<std::string>> requestedYulFunctions();
|
||||||
|
|
||||||
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
|
ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
|
||||||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
||||||
@ -355,8 +370,14 @@ private:
|
|||||||
size_t m_runtimeSub = -1;
|
size_t m_runtimeSub = -1;
|
||||||
/// An index of low-level function labels by name.
|
/// An index of low-level function labels by name.
|
||||||
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
|
std::map<std::string, evmasm::AssemblyItem> m_lowLevelFunctions;
|
||||||
|
/// Collector for yul functions.
|
||||||
|
MultiUseYulFunctionCollector m_yulFunctionCollector;
|
||||||
|
/// Set of externally used yul functions.
|
||||||
|
std::set<std::string> m_externallyUsedYulFunctions;
|
||||||
/// Container for ABI functions to be generated.
|
/// Container for ABI functions to be generated.
|
||||||
ABIFunctions m_abiFunctions;
|
ABIFunctions m_abiFunctions;
|
||||||
|
/// Container for Yul Util functions to be generated.
|
||||||
|
YulUtilFunctions m_yulUtilFunctions;
|
||||||
/// The queue of low-level functions to generate.
|
/// The queue of low-level functions to generate.
|
||||||
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
||||||
};
|
};
|
||||||
|
@ -121,56 +121,12 @@ void CompilerUtils::returnDataToArray()
|
|||||||
|
|
||||||
void CompilerUtils::accessCalldataTail(Type const& _type)
|
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
m_context << Instruction::SWAP1;
|
||||||
solAssert(_type.isDynamicallyEncoded(), "");
|
m_context.callYulFunction(
|
||||||
|
m_context.utilFunctions().accessCalldataTailFunction(_type),
|
||||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
2,
|
||||||
solAssert(tailSize > 1, "");
|
_type.isDynamicallySized() ? 2 : 1
|
||||||
|
);
|
||||||
// returns the absolute offset of the tail in "base_ref"
|
|
||||||
m_context.appendInlineAssembly(Whiskers(R"({
|
|
||||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
|
||||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { <revertString> }
|
|
||||||
base_ref := add(base_ref, rel_offset_of_tail)
|
|
||||||
})")
|
|
||||||
("neededLength", toCompactHexWithPrefix(tailSize))
|
|
||||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset"))
|
|
||||||
.render(), {"base_ref", "ptr_to_tail"});
|
|
||||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
|
||||||
|
|
||||||
if (!_type.isDynamicallySized())
|
|
||||||
{
|
|
||||||
m_context << Instruction::POP;
|
|
||||||
// stack layout: <absolute_offset_of_tail>
|
|
||||||
solAssert(
|
|
||||||
_type.category() == Type::Category::Struct ||
|
|
||||||
_type.category() == Type::Category::Array,
|
|
||||||
"Invalid dynamically encoded base type on tail access."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
|
||||||
solAssert(!!arrayType, "Invalid dynamically sized type.");
|
|
||||||
unsigned int calldataStride = arrayType->calldataStride();
|
|
||||||
solAssert(calldataStride > 0, "");
|
|
||||||
|
|
||||||
// returns the absolute offset of the tail in "base_ref"
|
|
||||||
// and the length of the tail in "length"
|
|
||||||
m_context.appendInlineAssembly(
|
|
||||||
Whiskers(R"({
|
|
||||||
length := calldataload(base_ref)
|
|
||||||
base_ref := add(base_ref, 0x20)
|
|
||||||
if gt(length, 0xffffffffffffffff) { <revertString> }
|
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
|
||||||
})")
|
|
||||||
("calldataStride", toCompactHexWithPrefix(calldataStride))
|
|
||||||
("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length"))
|
|
||||||
.render(),
|
|
||||||
{"base_ref", "length"}
|
|
||||||
);
|
|
||||||
// stack layout: <absolute_offset_of_tail> <length>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned CompilerUtils::loadFromMemory(
|
unsigned CompilerUtils::loadFromMemory(
|
||||||
@ -539,6 +495,10 @@ void CompilerUtils::encodeToMemory(
|
|||||||
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
|
||||||
{
|
{
|
||||||
// copy tail pointer (=mem_end - mem_start) to memory
|
// copy tail pointer (=mem_end - mem_start) to memory
|
||||||
|
solAssert(
|
||||||
|
(2 + dynPointers) <= 16,
|
||||||
|
"Stack too deep(" + to_string(2 + dynPointers) + "), try using fewer variables."
|
||||||
|
);
|
||||||
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
|
||||||
m_context << Instruction::SUB;
|
m_context << Instruction::SUB;
|
||||||
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
||||||
@ -595,31 +555,21 @@ void CompilerUtils::abiEncodeV2(
|
|||||||
|
|
||||||
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
||||||
|
|
||||||
auto ret = m_context.pushNewTag();
|
|
||||||
moveIntoStack(sizeOnStack(_givenTypes) + 1);
|
|
||||||
|
|
||||||
string encoderName =
|
string encoderName =
|
||||||
_padToWordBoundaries ?
|
_padToWordBoundaries ?
|
||||||
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
|
m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) :
|
||||||
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
|
m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes);
|
||||||
m_context.appendJumpTo(m_context.namedTag(encoderName));
|
m_context.callYulFunction(encoderName, sizeOnStack(_givenTypes) + 1, 1);
|
||||||
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
|
||||||
m_context << ret.tag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
|
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
|
||||||
{
|
{
|
||||||
// stack: <source_offset> <length> [stack top]
|
// stack: <source_offset> <length> [stack top]
|
||||||
auto ret = m_context.pushNewTag();
|
|
||||||
moveIntoStack(2);
|
|
||||||
// stack: <return tag> <source_offset> <length> [stack top]
|
|
||||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||||
m_context << Instruction::SWAP1;
|
m_context << Instruction::SWAP1;
|
||||||
// stack: <return tag> <end> <start>
|
// stack: <end> <start>
|
||||||
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
|
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
|
||||||
m_context.appendJumpTo(m_context.namedTag(decoderName));
|
m_context.callYulFunction(decoderName, 2, sizeOnStack(_parameterTypes));
|
||||||
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
|
|
||||||
m_context << ret.tag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||||
|
@ -781,7 +781,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// lvalue context
|
// lvalue context
|
||||||
solAssert(!ref->second.isOffset && !ref->second.isSlot, "");
|
solAssert(!ref->second.isOffset, "");
|
||||||
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
|
auto variable = dynamic_cast<VariableDeclaration const*>(decl);
|
||||||
solAssert(
|
solAssert(
|
||||||
!!variable && m_context.isLocalVariable(variable),
|
!!variable && m_context.isLocalVariable(variable),
|
||||||
@ -1267,12 +1267,12 @@ void ContractCompiler::appendMissingFunctions()
|
|||||||
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
||||||
}
|
}
|
||||||
m_context.appendMissingLowLevelFunctions();
|
m_context.appendMissingLowLevelFunctions();
|
||||||
auto abiFunctions = m_context.abiFunctions().requestedFunctions();
|
auto [yulFunctions, externallyUsedYulFunctions] = m_context.requestedYulFunctions();
|
||||||
if (!abiFunctions.first.empty())
|
if (!yulFunctions.empty())
|
||||||
m_context.appendInlineAssembly(
|
m_context.appendInlineAssembly(
|
||||||
"{" + move(abiFunctions.first) + "}",
|
"{" + move(yulFunctions) + "}",
|
||||||
{},
|
{},
|
||||||
abiFunctions.second,
|
externallyUsedYulFunctions,
|
||||||
true,
|
true,
|
||||||
m_optimiserSettings
|
m_optimiserSettings
|
||||||
);
|
);
|
||||||
|
@ -39,7 +39,7 @@ using namespace solidity::frontend;
|
|||||||
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||||
{
|
{
|
||||||
string functionName = "combine_external_function_id";
|
string functionName = "combine_external_function_id";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(addr, selector) -> combined {
|
function <functionName>(addr, selector) -> combined {
|
||||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||||
@ -55,7 +55,7 @@ string YulUtilFunctions::combineExternalFunctionIdFunction()
|
|||||||
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||||
{
|
{
|
||||||
string functionName = "split_external_function_id";
|
string functionName = "split_external_function_id";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(combined) -> addr, selector {
|
function <functionName>(combined) -> addr, selector {
|
||||||
combined := <shr64>(combined)
|
combined := <shr64>(combined)
|
||||||
@ -73,7 +73,7 @@ string YulUtilFunctions::splitExternalFunctionIdFunction()
|
|||||||
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||||
{
|
{
|
||||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_fromCalldata)
|
if (_fromCalldata)
|
||||||
{
|
{
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
@ -116,7 +116,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
|||||||
|
|
||||||
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (!_messageType)
|
if (!_messageType)
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(condition) {
|
function <functionName>(condition) {
|
||||||
@ -166,7 +166,7 @@ string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _mess
|
|||||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||||
{
|
{
|
||||||
string functionName = string("leftAlign_") + _type.identifier();
|
string functionName = string("leftAlign_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> aligned {
|
function <functionName>(value) -> aligned {
|
||||||
<body>
|
<body>
|
||||||
@ -228,7 +228,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
|||||||
solAssert(_numBits < 256, "");
|
solAssert(_numBits < 256, "");
|
||||||
|
|
||||||
string functionName = "shift_left_" + to_string(_numBits);
|
string functionName = "shift_left_" + to_string(_numBits);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> newValue {
|
function <functionName>(value) -> newValue {
|
||||||
@ -251,7 +251,7 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
|||||||
string YulUtilFunctions::shiftLeftFunctionDynamic()
|
string YulUtilFunctions::shiftLeftFunctionDynamic()
|
||||||
{
|
{
|
||||||
string functionName = "shift_left_dynamic";
|
string functionName = "shift_left_dynamic";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(bits, value) -> newValue {
|
function <functionName>(bits, value) -> newValue {
|
||||||
@ -277,7 +277,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
|
|||||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||||
|
|
||||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> newValue {
|
function <functionName>(value) -> newValue {
|
||||||
@ -303,7 +303,7 @@ string YulUtilFunctions::shiftRightFunctionDynamic()
|
|||||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||||
|
|
||||||
string const functionName = "shift_right_unsigned_dynamic";
|
string const functionName = "shift_right_unsigned_dynamic";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(bits, value) -> newValue {
|
function <functionName>(bits, value) -> newValue {
|
||||||
@ -328,7 +328,7 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
|
|||||||
size_t numBits = _numBytes * 8;
|
size_t numBits = _numBytes * 8;
|
||||||
size_t shiftBits = _shiftBytes * 8;
|
size_t shiftBits = _shiftBytes * 8;
|
||||||
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value, toInsert) -> result {
|
function <functionName>(value, toInsert) -> result {
|
||||||
@ -350,7 +350,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
|||||||
solAssert(_numBytes <= 32, "");
|
solAssert(_numBytes <= 32, "");
|
||||||
size_t numBits = _numBytes * 8;
|
size_t numBits = _numBytes * 8;
|
||||||
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
|
string functionName = "update_byte_slice_dynamic" + to_string(_numBytes);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value, shiftBytes, toInsert) -> result {
|
function <functionName>(value, shiftBytes, toInsert) -> result {
|
||||||
@ -371,7 +371,7 @@ string YulUtilFunctions::updateByteSliceFunctionDynamic(size_t _numBytes)
|
|||||||
string YulUtilFunctions::roundUpFunction()
|
string YulUtilFunctions::roundUpFunction()
|
||||||
{
|
{
|
||||||
string functionName = "round_up_to_mul_of_32";
|
string functionName = "round_up_to_mul_of_32";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(value) -> result {
|
function <functionName>(value) -> result {
|
||||||
@ -389,7 +389,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
|||||||
// TODO: Consider to add a special case for unsigned 256-bit integers
|
// TODO: Consider to add a special case for unsigned 256-bit integers
|
||||||
// and use the following instead:
|
// and use the following instead:
|
||||||
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
// sum := add(x, y) if lt(sum, x) { revert(0, 0) }
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> sum {
|
function <functionName>(x, y) -> sum {
|
||||||
@ -416,7 +416,7 @@ string YulUtilFunctions::overflowCheckedIntAddFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_mul_" + _type.identifier();
|
string functionName = "checked_mul_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
// Multiplication by zero could be treated separately and directly return zero.
|
// Multiplication by zero could be treated separately and directly return zero.
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
@ -448,7 +448,7 @@ string YulUtilFunctions::overflowCheckedIntMulFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_div_" + _type.identifier();
|
string functionName = "checked_div_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> r {
|
function <functionName>(x, y) -> r {
|
||||||
@ -473,7 +473,7 @@ string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_mod_" + _type.identifier();
|
string functionName = "checked_mod_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> r {
|
function <functionName>(x, y) -> r {
|
||||||
@ -490,7 +490,7 @@ string YulUtilFunctions::checkedIntModFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "checked_sub_" + _type.identifier();
|
string functionName = "checked_sub_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return
|
return
|
||||||
Whiskers(R"(
|
Whiskers(R"(
|
||||||
function <functionName>(x, y) -> diff {
|
function <functionName>(x, y) -> diff {
|
||||||
@ -516,7 +516,7 @@ string YulUtilFunctions::overflowCheckedIntSubFunction(IntegerType const& _type)
|
|||||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_length_" + _type.identifier();
|
string functionName = "array_length_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers w(R"(
|
Whiskers w(R"(
|
||||||
function <functionName>(value) -> length {
|
function <functionName>(value) -> length {
|
||||||
<?dynamic>
|
<?dynamic>
|
||||||
@ -564,7 +564,7 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
||||||
|
|
||||||
string functionName = "resize_array_" + _type.identifier();
|
string functionName = "resize_array_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, newLen) {
|
function <functionName>(array, newLen) {
|
||||||
if gt(newLen, <maxArrayLength>) {
|
if gt(newLen, <maxArrayLength>) {
|
||||||
@ -604,7 +604,7 @@ string YulUtilFunctions::storageArrayPopFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_pop_" + _type.identifier();
|
string functionName = "array_pop_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array) {
|
function <functionName>(array) {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -632,7 +632,7 @@ string YulUtilFunctions::storageArrayPushFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_push_" + _type.identifier();
|
string functionName = "array_push_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, value) {
|
function <functionName>(array, value) {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -659,7 +659,7 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
|
||||||
|
|
||||||
string functionName = "array_push_zero_" + _type.identifier();
|
string functionName = "array_push_zero_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array) -> slot, offset {
|
function <functionName>(array) -> slot, offset {
|
||||||
let oldLen := <fetchLength>(array)
|
let oldLen := <fetchLength>(array)
|
||||||
@ -684,7 +684,7 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
|
|||||||
|
|
||||||
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(start, end) {
|
function <functionName>(start, end) {
|
||||||
for {} lt(start, end) { start := add(start, <increment>) }
|
for {} lt(start, end) { start := add(start, <increment>) }
|
||||||
@ -715,7 +715,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
|||||||
|
|
||||||
string functionName = "clear_storage_array_" + _type.identifier();
|
string functionName = "clear_storage_array_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot) {
|
function <functionName>(slot) {
|
||||||
<?dynamic>
|
<?dynamic>
|
||||||
@ -745,7 +745,7 @@ string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
string functionName = "array_convert_length_to_size_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Type const& baseType = *_type.baseType();
|
Type const& baseType = *_type.baseType();
|
||||||
|
|
||||||
switch (_type.location())
|
switch (_type.location())
|
||||||
@ -798,7 +798,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
|||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||||
string functionName = "array_allocation_size_" + _type.identifier();
|
string functionName = "array_allocation_size_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers w(R"(
|
Whiskers w(R"(
|
||||||
function <functionName>(length) -> size {
|
function <functionName>(length) -> size {
|
||||||
// Make sure we can allocate memory without overflow
|
// Make sure we can allocate memory without overflow
|
||||||
@ -825,7 +825,7 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "array_dataslot_" + _type.identifier();
|
string functionName = "array_dataslot_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
// No special processing for calldata arrays, because they are stored as
|
// No special processing for calldata arrays, because they are stored as
|
||||||
// offset of the data area and length on the stack, so the offset already
|
// offset of the data area and length on the stack, so the offset already
|
||||||
// points to the data area.
|
// points to the data area.
|
||||||
@ -858,7 +858,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
solUnimplementedAssert(_type.baseType()->storageBytes() > 16, "");
|
||||||
|
|
||||||
string functionName = "storage_array_index_access_" + _type.identifier();
|
string functionName = "storage_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(array, index) -> slot, offset {
|
function <functionName>(array, index) -> slot, offset {
|
||||||
if iszero(lt(index, <arrayLen>(array))) {
|
if iszero(lt(index, <arrayLen>(array))) {
|
||||||
@ -886,7 +886,7 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
|||||||
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||||
{
|
{
|
||||||
string functionName = "memory_array_index_access_" + _type.identifier();
|
string functionName = "memory_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(baseRef, index) -> addr {
|
function <functionName>(baseRef, index) -> addr {
|
||||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||||
@ -912,7 +912,7 @@ string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& _type
|
|||||||
{
|
{
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
string functionName = "calldata_array_index_access_" + _type.identifier();
|
string functionName = "calldata_array_index_access_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
function <functionName>(base_ref<?dynamicallySized>, length</dynamicallySized>, index) -> addr<?dynamicallySizedBase>, len</dynamicallySizedBase> {
|
||||||
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
|
if iszero(lt(index, <?dynamicallySized>length<!dynamicallySized><arrayLen></dynamicallySized>)) { invalid() }
|
||||||
@ -938,17 +938,17 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
|
|||||||
solAssert(_type.isDynamicallyEncoded(), "");
|
solAssert(_type.isDynamicallyEncoded(), "");
|
||||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||||
string functionName = "access_calldata_tail_" + _type.identifier();
|
string functionName = "access_calldata_tail_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
|
function <functionName>(base_ref, ptr_to_tail) -> addr<?dynamicallySized>, length</dynamicallySized> {
|
||||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||||
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)))) { <invalidCalldataTailOffset> }
|
||||||
addr := add(base_ref, rel_offset_of_tail)
|
addr := add(base_ref, rel_offset_of_tail)
|
||||||
<?dynamicallySized>
|
<?dynamicallySized>
|
||||||
length := calldataload(addr)
|
length := calldataload(addr)
|
||||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
if gt(length, 0xffffffffffffffff) { <invalidCalldataTailLength> }
|
||||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
|
||||||
addr := add(addr, 32)
|
addr := add(addr, 32)
|
||||||
|
if sgt(addr, sub(calldatasize(), mul(length, <calldataStride>))) { <shortCalldataTail> }
|
||||||
</dynamicallySized>
|
</dynamicallySized>
|
||||||
}
|
}
|
||||||
)")
|
)")
|
||||||
@ -956,6 +956,9 @@ string YulUtilFunctions::accessCalldataTailFunction(Type const& _type)
|
|||||||
("dynamicallySized", _type.isDynamicallySized())
|
("dynamicallySized", _type.isDynamicallySized())
|
||||||
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
|
("neededLength", toCompactHexWithPrefix(_type.calldataEncodedTailSize()))
|
||||||
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
|
("calldataStride", toCompactHexWithPrefix(_type.isDynamicallySized() ? dynamic_cast<ArrayType const&>(_type).calldataStride() : 0))
|
||||||
|
("invalidCalldataTailOffset", revertReasonIfDebug("Invalid calldata tail offset"))
|
||||||
|
("invalidCalldataTailLength", revertReasonIfDebug("Invalid calldata tail length"))
|
||||||
|
("shortCalldataTail", revertReasonIfDebug("Calldata tail too short"))
|
||||||
.render();
|
.render();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -966,7 +969,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
|||||||
if (_type.dataStoredIn(DataLocation::Storage))
|
if (_type.dataStoredIn(DataLocation::Storage))
|
||||||
solAssert(_type.baseType()->storageBytes() > 16, "");
|
solAssert(_type.baseType()->storageBytes() > 16, "");
|
||||||
string functionName = "array_nextElement_" + _type.identifier();
|
string functionName = "array_nextElement_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(ptr) -> next {
|
function <functionName>(ptr) -> next {
|
||||||
next := add(ptr, <advance>)
|
next := add(ptr, <advance>)
|
||||||
@ -1002,7 +1005,7 @@ string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingT
|
|||||||
solAssert(_keyType.sizeOnStack() <= 1, "");
|
solAssert(_keyType.sizeOnStack() <= 1, "");
|
||||||
|
|
||||||
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
|
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_mappingType.keyType()->isDynamicallySized())
|
if (_mappingType.keyType()->isDynamicallySized())
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot <comma> <key>) -> dataSlot {
|
function <functionName>(slot <comma> <key>) -> dataSlot {
|
||||||
@ -1050,7 +1053,7 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
|
|||||||
to_string(_offset) +
|
to_string(_offset) +
|
||||||
"_" +
|
"_" +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(_type.sizeOnStack() == 1, "");
|
solAssert(_type.sizeOnStack() == 1, "");
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot) -> value {
|
function <functionName>(slot) -> value {
|
||||||
@ -1071,7 +1074,7 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
|||||||
string(_splitFunctionTypes ? "split_" : "") +
|
string(_splitFunctionTypes ? "split_" : "") +
|
||||||
"_" +
|
"_" +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(_type.sizeOnStack() == 1, "");
|
solAssert(_type.sizeOnStack() == 1, "");
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot, offset) -> value {
|
function <functionName>(slot, offset) -> value {
|
||||||
@ -1101,7 +1104,7 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, std::opti
|
|||||||
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
(_offset.has_value() ? ("offset_" + to_string(*_offset)) : "") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
if (_type.isValueType())
|
if (_type.isValueType())
|
||||||
{
|
{
|
||||||
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
|
||||||
@ -1141,7 +1144,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
|
|||||||
string("write_to_memory_") +
|
string("write_to_memory_") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
||||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
{
|
{
|
||||||
@ -1201,7 +1204,7 @@ string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool
|
|||||||
"extract_from_storage_value_dynamic" +
|
"extract_from_storage_value_dynamic" +
|
||||||
string(_splitFunctionTypes ? "split_" : "") +
|
string(_splitFunctionTypes ? "split_" : "") +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot_value, offset) -> value {
|
function <functionName>(slot_value, offset) -> value {
|
||||||
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
|
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
|
||||||
@ -1224,7 +1227,7 @@ string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offs
|
|||||||
"offset_" +
|
"offset_" +
|
||||||
to_string(_offset) +
|
to_string(_offset) +
|
||||||
_type.identifier();
|
_type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot_value) -> value {
|
function <functionName>(slot_value) -> value {
|
||||||
value := <cleanupStorage>(<shr>(slot_value))
|
value := <cleanupStorage>(<shr>(slot_value))
|
||||||
@ -1243,7 +1246,7 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl
|
|||||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||||
|
|
||||||
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> cleaned {
|
function <functionName>(value) -> cleaned {
|
||||||
cleaned := <cleaned>
|
cleaned := <cleaned>
|
||||||
@ -1275,7 +1278,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
|||||||
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
|
||||||
|
|
||||||
string functionName = "prepare_store_" + _type.identifier();
|
string functionName = "prepare_store_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> ret {
|
function <functionName>(value) -> ret {
|
||||||
ret := <actualPrepare>
|
ret := <actualPrepare>
|
||||||
@ -1293,7 +1296,7 @@ string YulUtilFunctions::prepareStoreFunction(Type const& _type)
|
|||||||
string YulUtilFunctions::allocationFunction()
|
string YulUtilFunctions::allocationFunction()
|
||||||
{
|
{
|
||||||
string functionName = "allocateMemory";
|
string functionName = "allocateMemory";
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(size) -> memPtr {
|
function <functionName>(size) -> memPtr {
|
||||||
memPtr := mload(<freeMemoryPointer>)
|
memPtr := mload(<freeMemoryPointer>)
|
||||||
@ -1314,7 +1317,7 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
|||||||
solUnimplementedAssert(!_type.isByteArray(), "");
|
solUnimplementedAssert(!_type.isByteArray(), "");
|
||||||
|
|
||||||
string functionName = "allocate_memory_array_" + _type.identifier();
|
string functionName = "allocate_memory_array_" + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(length) -> memPtr {
|
function <functionName>(length) -> memPtr {
|
||||||
memPtr := <alloc>(<allocSize>(length))
|
memPtr := <alloc>(<allocSize>(length))
|
||||||
@ -1333,6 +1336,35 @@ string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
|||||||
|
|
||||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||||
{
|
{
|
||||||
|
if (_from.category() == Type::Category::Function)
|
||||||
|
{
|
||||||
|
solAssert(_to.category() == Type::Category::Function, "");
|
||||||
|
FunctionType const& fromType = dynamic_cast<FunctionType const&>(_from);
|
||||||
|
FunctionType const& targetType = dynamic_cast<FunctionType const&>(_to);
|
||||||
|
solAssert(
|
||||||
|
fromType.isImplicitlyConvertibleTo(targetType) &&
|
||||||
|
fromType.sizeOnStack() == targetType.sizeOnStack() &&
|
||||||
|
(fromType.kind() == FunctionType::Kind::Internal || fromType.kind() == FunctionType::Kind::External) &&
|
||||||
|
fromType.kind() == targetType.kind(),
|
||||||
|
"Invalid function type conversion requested."
|
||||||
|
);
|
||||||
|
string const functionName =
|
||||||
|
"convert_" +
|
||||||
|
_from.identifier() +
|
||||||
|
"_to_" +
|
||||||
|
_to.identifier();
|
||||||
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
|
return Whiskers(R"(
|
||||||
|
function <functionName>(addr, functionId) -> outAddr, outFunctionId {
|
||||||
|
outAddr := addr
|
||||||
|
outFunctionId := functionId
|
||||||
|
}
|
||||||
|
)")
|
||||||
|
("functionName", functionName)
|
||||||
|
.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||||
return conversionFunctionSpecial(_from, _to);
|
return conversionFunctionSpecial(_from, _to);
|
||||||
|
|
||||||
@ -1341,7 +1373,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
_from.identifier() +
|
_from.identifier() +
|
||||||
"_to_" +
|
"_to_" +
|
||||||
_to.identifier();
|
_to.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> converted {
|
function <functionName>(value) -> converted {
|
||||||
<body>
|
<body>
|
||||||
@ -1440,22 +1472,34 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
break;
|
break;
|
||||||
case Type::Category::Array:
|
case Type::Category::Array:
|
||||||
{
|
{
|
||||||
bool equal = _from == _to;
|
if (_from == _to)
|
||||||
|
body = "converted := value";
|
||||||
if (!equal)
|
else
|
||||||
{
|
{
|
||||||
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
||||||
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
||||||
|
|
||||||
if (*from.mobileType() == *to.mobileType())
|
switch (to.location())
|
||||||
equal = true;
|
{
|
||||||
|
case DataLocation::Storage:
|
||||||
|
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
|
||||||
|
solAssert(
|
||||||
|
(to.isPointer() || (from.isByteArray() && to.isByteArray())) &&
|
||||||
|
from.location() == DataLocation::Storage,
|
||||||
|
"Invalid conversion to storage type."
|
||||||
|
);
|
||||||
|
body = "converted := value";
|
||||||
|
break;
|
||||||
|
case DataLocation::Memory:
|
||||||
|
// Copy the array to a free position in memory, unless it is already in memory.
|
||||||
|
solUnimplementedAssert(from.location() == DataLocation::Memory, "Not implemented yet.");
|
||||||
|
body = "converted := value";
|
||||||
|
break;
|
||||||
|
case DataLocation::CallData:
|
||||||
|
solUnimplemented("Conversion of calldata types not yet implemented.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (equal)
|
|
||||||
body = "converted := value";
|
|
||||||
else
|
|
||||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Type::Category::Struct:
|
case Type::Category::Struct:
|
||||||
@ -1519,7 +1563,7 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
|||||||
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||||
{
|
{
|
||||||
string functionName = string("cleanup_") + _type.identifier();
|
string functionName = string("cleanup_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) -> cleaned {
|
function <functionName>(value) -> cleaned {
|
||||||
<body>
|
<body>
|
||||||
@ -1606,7 +1650,7 @@ string YulUtilFunctions::cleanupFunction(Type const& _type)
|
|||||||
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFailure)
|
||||||
{
|
{
|
||||||
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
string functionName = string("validator_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(value) {
|
function <functionName>(value) {
|
||||||
if iszero(<condition>) { <failure> }
|
if iszero(<condition>) { <failure> }
|
||||||
@ -1667,7 +1711,7 @@ string YulUtilFunctions::packedHashFunction(
|
|||||||
size_t sizeOnStack = 0;
|
size_t sizeOnStack = 0;
|
||||||
for (Type const* t: _givenTypes)
|
for (Type const* t: _givenTypes)
|
||||||
sizeOnStack += t->sizeOnStack();
|
sizeOnStack += t->sizeOnStack();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(<variables>) -> hash {
|
function <functionName>(<variables>) -> hash {
|
||||||
let pos := mload(<freeMemoryPointer>)
|
let pos := mload(<freeMemoryPointer>)
|
||||||
@ -1688,7 +1732,7 @@ string YulUtilFunctions::forwardingRevertFunction()
|
|||||||
{
|
{
|
||||||
bool forward = m_evmVersion.supportsReturndata();
|
bool forward = m_evmVersion.supportsReturndata();
|
||||||
string functionName = "revert_forward_" + to_string(forward);
|
string functionName = "revert_forward_" + to_string(forward);
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (forward)
|
if (forward)
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>() {
|
function <functionName>() {
|
||||||
@ -1715,7 +1759,7 @@ std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "decrement_" + _type.identifier();
|
string const functionName = "decrement_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
u256 minintval;
|
u256 minintval;
|
||||||
|
|
||||||
// Smallest admissible value to decrement
|
// Smallest admissible value to decrement
|
||||||
@ -1743,7 +1787,7 @@ std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "increment_" + _type.identifier();
|
string const functionName = "increment_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
u256 maxintval;
|
u256 maxintval;
|
||||||
|
|
||||||
// Biggest admissible value to increment
|
// Biggest admissible value to increment
|
||||||
@ -1774,7 +1818,7 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
|
|||||||
|
|
||||||
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(_value) -> ret {
|
function <functionName>(_value) -> ret {
|
||||||
if slt(_value, <minval>) { revert(0,0) }
|
if slt(_value, <minval>) { revert(0,0) }
|
||||||
@ -1794,7 +1838,7 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type)
|
|||||||
|
|
||||||
string const functionName = "zero_value_for_" + _type.identifier();
|
string const functionName = "zero_value_for_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>() -> ret {
|
function <functionName>() -> ret {
|
||||||
<body>
|
<body>
|
||||||
@ -1810,7 +1854,7 @@ string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
|
|||||||
{
|
{
|
||||||
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (_type.isValueType())
|
if (_type.isValueType())
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
function <functionName>(slot, offset) {
|
function <functionName>(slot, offset) {
|
||||||
@ -1842,7 +1886,7 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
|||||||
_from.identifier() +
|
_from.identifier() +
|
||||||
"_to_" +
|
"_to_" +
|
||||||
_to.identifier();
|
_to.identifier();
|
||||||
return m_functionCollector->createFunction(functionName, [&]() {
|
return m_functionCollector.createFunction(functionName, [&]() {
|
||||||
if (
|
if (
|
||||||
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
|
auto fromTuple = dynamic_cast<TupleType const*>(&_from), toTuple = dynamic_cast<TupleType const*>(&_to);
|
||||||
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
|
fromTuple && toTuple && fromTuple->components().size() == toTuple->components().size()
|
||||||
@ -1950,7 +1994,7 @@ string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromC
|
|||||||
if (_fromCalldata)
|
if (_fromCalldata)
|
||||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||||
|
|
||||||
return m_functionCollector->createFunction(functionName, [&] {
|
return m_functionCollector.createFunction(functionName, [&] {
|
||||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
{
|
{
|
||||||
solAssert(refType->sizeOnStack() == 1, "");
|
solAssert(refType->sizeOnStack() == 1, "");
|
||||||
|
@ -47,11 +47,11 @@ public:
|
|||||||
explicit YulUtilFunctions(
|
explicit YulUtilFunctions(
|
||||||
langutil::EVMVersion _evmVersion,
|
langutil::EVMVersion _evmVersion,
|
||||||
RevertStrings _revertStrings,
|
RevertStrings _revertStrings,
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
MultiUseYulFunctionCollector& _functionCollector
|
||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_functionCollector(std::move(_functionCollector))
|
m_functionCollector(_functionCollector)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// @returns a function that combines the address and selector to a single value
|
/// @returns a function that combines the address and selector to a single value
|
||||||
@ -306,7 +306,7 @@ private:
|
|||||||
|
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
RevertStrings m_revertStrings;
|
RevertStrings m_revertStrings;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
MultiUseYulFunctionCollector& m_functionCollector;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ string IRGenerationContext::newYulVariable()
|
|||||||
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||||
{
|
{
|
||||||
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
|
||||||
return m_functions->createFunction(funName, [&]() {
|
return m_functions.createFunction(funName, [&]() {
|
||||||
Whiskers templ(R"(
|
Whiskers templ(R"(
|
||||||
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
function <functionName>(fun <comma> <in>) <arrow> <out> {
|
||||||
switch fun
|
switch fun
|
||||||
|
@ -56,11 +56,10 @@ public:
|
|||||||
):
|
):
|
||||||
m_evmVersion(_evmVersion),
|
m_evmVersion(_evmVersion),
|
||||||
m_revertStrings(_revertStrings),
|
m_revertStrings(_revertStrings),
|
||||||
m_optimiserSettings(std::move(_optimiserSettings)),
|
m_optimiserSettings(std::move(_optimiserSettings))
|
||||||
m_functions(std::make_shared<MultiUseYulFunctionCollector>())
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
|
MultiUseYulFunctionCollector& functionCollector() { return m_functions; }
|
||||||
|
|
||||||
/// Sets the current inheritance hierarchy from derived to base.
|
/// Sets the current inheritance hierarchy from derived to base.
|
||||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
|
||||||
@ -108,7 +107,7 @@ private:
|
|||||||
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
|
std::map<VariableDeclaration const*, IRVariable> m_localVariables;
|
||||||
/// Storage offsets of state variables
|
/// Storage offsets of state variables
|
||||||
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
|
||||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
|
MultiUseYulFunctionCollector m_functions;
|
||||||
size_t m_varCounter = 0;
|
size_t m_varCounter = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
|||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
for (auto const* fun: contract->definedFunctions())
|
for (auto const* fun: contract->definedFunctions())
|
||||||
generateFunction(*fun);
|
generateFunction(*fun);
|
||||||
t("functions", m_context.functionCollector()->requestedFunctions());
|
t("functions", m_context.functionCollector().requestedFunctions());
|
||||||
|
|
||||||
resetContext(_contract);
|
resetContext(_contract);
|
||||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||||
@ -116,7 +116,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
|
|||||||
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
for (auto const* fun: contract->definedFunctions())
|
for (auto const* fun: contract->definedFunctions())
|
||||||
generateFunction(*fun);
|
generateFunction(*fun);
|
||||||
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
|
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
|
||||||
return t.render();
|
return t.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ string IRGenerator::generate(Block const& _block)
|
|||||||
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
string functionName = m_context.functionName(_function);
|
string functionName = m_context.functionName(_function);
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
Whiskers t(R"(
|
Whiskers t(R"(
|
||||||
function <functionName>(<params>) <returns> {
|
function <functionName>(<params>) <returns> {
|
||||||
<body>
|
<body>
|
||||||
@ -160,7 +160,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
|||||||
solAssert(_varDecl.isStateVariable(), "");
|
solAssert(_varDecl.isStateVariable(), "");
|
||||||
|
|
||||||
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
|
if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||||
solAssert(slot_offset.second == 0, "");
|
solAssert(slot_offset.second == 0, "");
|
||||||
FunctionType funType(_varDecl);
|
FunctionType funType(_varDecl);
|
||||||
@ -209,7 +209,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
|||||||
{
|
{
|
||||||
solUnimplementedAssert(type->isValueType(), "");
|
solUnimplementedAssert(type->isValueType(), "");
|
||||||
|
|
||||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
return m_context.functionCollector().createFunction(functionName, [&]() {
|
||||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||||
|
|
||||||
return Whiskers(R"(
|
return Whiskers(R"(
|
||||||
@ -383,11 +383,10 @@ string IRGenerator::memoryInit()
|
|||||||
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
void IRGenerator::resetContext(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
m_context.functionCollector()->requestedFunctions().empty(),
|
m_context.functionCollector().requestedFunctions().empty(),
|
||||||
"Reset context while it still had functions."
|
"Reset context while it still had functions."
|
||||||
);
|
);
|
||||||
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
|
m_context = IRGenerationContext(m_evmVersion, m_context.revertStrings(), m_optimiserSettings);
|
||||||
m_utils = YulUtilFunctions(m_evmVersion, m_context.revertStrings(), m_context.functionCollector());
|
|
||||||
|
|
||||||
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
|
||||||
for (auto const& var: ContractType(_contract).stateVariables())
|
for (auto const& var: ContractType(_contract).stateVariables())
|
||||||
|
@ -47,6 +47,7 @@ using namespace std;
|
|||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
using namespace solidity::frontend;
|
using namespace solidity::frontend;
|
||||||
|
using namespace std::string_literals;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -800,11 +801,19 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
|||||||
case Type::Category::Function:
|
case Type::Category::Function:
|
||||||
if (member == "selector")
|
if (member == "selector")
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(
|
||||||
|
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||||
|
FunctionType::Kind::External, ""
|
||||||
|
);
|
||||||
|
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
|
||||||
}
|
}
|
||||||
else if (member == "address")
|
else if (member == "address")
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "");
|
solUnimplementedAssert(
|
||||||
|
dynamic_cast<FunctionType const&>(*_memberAccess.expression().annotation().type).kind() ==
|
||||||
|
FunctionType::Kind::External, ""
|
||||||
|
);
|
||||||
|
define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
solAssert(
|
solAssert(
|
||||||
@ -1204,7 +1213,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
argumentTypes.emplace_back(&type(*arg));
|
argumentTypes.emplace_back(&type(*arg));
|
||||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||||
}
|
}
|
||||||
string argumentString = ", " + joinHumanReadable(argumentStrings);
|
string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
|
||||||
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||||
|
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#include <libsolidity/ast/TypeProvider.h>
|
#include <libsolidity/ast/TypeProvider.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Algorithms.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -75,16 +77,39 @@ void CHC::analyze(SourceUnit const& _source)
|
|||||||
m_context.setAssertionAccumulation(false);
|
m_context.setAssertionAccumulation(false);
|
||||||
m_variableUsage.setFunctionInlining(false);
|
m_variableUsage.setFunctionInlining(false);
|
||||||
|
|
||||||
|
resetSourceAnalysis();
|
||||||
|
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
auto genesisSort = make_shared<smt::FunctionSort>(
|
auto genesisSort = make_shared<smt::FunctionSort>(
|
||||||
vector<smt::SortPointer>(),
|
vector<smt::SortPointer>(),
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
m_genesisPredicate = createSymbolicBlock(genesisSort, "genesis");
|
||||||
auto genesis = (*m_genesisPredicate)({});
|
addRule(genesis(), "genesis");
|
||||||
addRule(genesis, genesis.name);
|
|
||||||
|
|
||||||
_source.accept(*this);
|
set<SourceUnit const*, IdCompare> sources;
|
||||||
|
sources.insert(&_source);
|
||||||
|
for (auto const& source: _source.referencedSourceUnits(true))
|
||||||
|
sources.insert(source);
|
||||||
|
for (auto const* source: sources)
|
||||||
|
defineInterfacesAndSummaries(*source);
|
||||||
|
for (auto const* source: sources)
|
||||||
|
source->accept(*this);
|
||||||
|
|
||||||
|
for (auto const& [scope, target]: m_verificationTargets)
|
||||||
|
{
|
||||||
|
auto assertions = transactionAssertions(scope);
|
||||||
|
for (auto const* assertion: assertions)
|
||||||
|
{
|
||||||
|
createErrorBlock();
|
||||||
|
connectBlocks(target.value, error(), target.constraints && (target.errorId == assertion->id()));
|
||||||
|
auto [result, model] = query(error(), assertion->location());
|
||||||
|
// This should be fine but it's a bug in the old compiler
|
||||||
|
(void)model;
|
||||||
|
if (result == smt::CheckResult::UNSATISFIABLE)
|
||||||
|
m_safeAssertions.insert(assertion);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<string> CHC::unhandledQueries() const
|
vector<string> CHC::unhandledQueries() const
|
||||||
@ -97,26 +122,15 @@ vector<string> CHC::unhandledQueries() const
|
|||||||
|
|
||||||
bool CHC::visit(ContractDefinition const& _contract)
|
bool CHC::visit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_contract))
|
resetContractAnalysis();
|
||||||
return false;
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
initContract(_contract);
|
initContract(_contract);
|
||||||
|
|
||||||
m_stateVariables = _contract.stateVariablesIncludingInherited();
|
m_stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||||
|
m_stateSorts = stateSorts(_contract);
|
||||||
for (auto const& var: m_stateVariables)
|
|
||||||
// SMT solvers do not support function types as arguments.
|
|
||||||
if (var->type()->category() == Type::Category::Function)
|
|
||||||
m_stateSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
|
||||||
else
|
|
||||||
m_stateSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
|
|
||||||
clearIndices(&_contract);
|
clearIndices(&_contract);
|
||||||
|
|
||||||
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
|
||||||
m_interfacePredicate = createSymbolicBlock(interfaceSort(), "interface_" + suffix);
|
|
||||||
|
|
||||||
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
@ -125,10 +139,12 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
string suffix = _contract.name() + "_" + to_string(_contract.id());
|
||||||
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
|
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error_" + suffix);
|
||||||
m_constructorPredicate = createSymbolicBlock(constructorSort(), "implicit_constructor_" + to_string(_contract.id()));
|
m_constructorSummaryPredicate = createSymbolicBlock(constructorSort(), "summary_constructor_" + suffix);
|
||||||
|
m_implicitConstructorPredicate = createSymbolicBlock(interfaceSort(), "implicit_constructor_" + suffix);
|
||||||
auto stateExprs = currentStateVariables();
|
auto stateExprs = currentStateVariables();
|
||||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||||
|
|
||||||
SMTEncoder::visit(_contract);
|
SMTEncoder::visit(_contract);
|
||||||
return false;
|
return false;
|
||||||
@ -136,33 +152,33 @@ bool CHC::visit(ContractDefinition const& _contract)
|
|||||||
|
|
||||||
void CHC::endVisit(ContractDefinition const& _contract)
|
void CHC::endVisit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
if (!shouldVisit(_contract))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto const& var: m_stateVariables)
|
for (auto const& var: m_stateVariables)
|
||||||
{
|
{
|
||||||
solAssert(m_context.knownVariable(*var), "");
|
solAssert(m_context.knownVariable(*var), "");
|
||||||
|
auto const& symbVar = m_context.variable(*var);
|
||||||
|
symbVar->resetIndex();
|
||||||
m_context.setZeroValue(*var);
|
m_context.setZeroValue(*var);
|
||||||
|
symbVar->increaseIndex();
|
||||||
}
|
}
|
||||||
auto genesisPred = (*m_genesisPredicate)({});
|
auto implicitConstructor = (*m_implicitConstructorPredicate)(initialStateVariables());
|
||||||
auto implicitConstructor = (*m_constructorPredicate)(currentStateVariables());
|
connectBlocks(genesis(), implicitConstructor);
|
||||||
connectBlocks(genesisPred, implicitConstructor);
|
|
||||||
m_currentBlock = implicitConstructor;
|
m_currentBlock = implicitConstructor;
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
|
||||||
if (auto constructor = _contract.constructor())
|
if (auto constructor = _contract.constructor())
|
||||||
constructor->accept(*this);
|
constructor->accept(*this);
|
||||||
else
|
else
|
||||||
inlineConstructorHierarchy(_contract);
|
inlineConstructorHierarchy(_contract);
|
||||||
|
|
||||||
connectBlocks(m_currentBlock, interface());
|
auto summary = predicate(*m_constructorSummaryPredicate, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables());
|
||||||
|
connectBlocks(m_currentBlock, summary);
|
||||||
|
|
||||||
for (unsigned i = 0; i < m_verificationTargets.size(); ++i)
|
clearIndices(m_currentContract, nullptr);
|
||||||
{
|
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||||
auto const& target = m_verificationTargets.at(i);
|
setCurrentBlock(*m_constructorSummaryPredicate, &stateExprs);
|
||||||
auto errorAppl = error(i + 1);
|
|
||||||
if (query(errorAppl, target->location()))
|
addVerificationTarget(m_currentContract, m_currentBlock, smt::Expression(true), m_error.currentValue());
|
||||||
m_safeAssertions.insert(target);
|
connectBlocks(m_currentBlock, interface(), m_error.currentValue() == 0);
|
||||||
}
|
|
||||||
|
|
||||||
SMTEncoder::endVisit(_contract);
|
SMTEncoder::endVisit(_contract);
|
||||||
}
|
}
|
||||||
@ -182,7 +198,7 @@ bool CHC::visit(FunctionDefinition const& _function)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented");
|
solAssert(!m_currentFunction, "Function inlining should not happen in CHC.");
|
||||||
m_currentFunction = &_function;
|
m_currentFunction = &_function;
|
||||||
|
|
||||||
initFunction(_function);
|
initFunction(_function);
|
||||||
@ -193,7 +209,17 @@ bool CHC::visit(FunctionDefinition const& _function)
|
|||||||
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
|
auto functionPred = predicate(*functionEntryBlock, currentFunctionVariables());
|
||||||
auto bodyPred = predicate(*bodyBlock);
|
auto bodyPred = predicate(*bodyBlock);
|
||||||
|
|
||||||
connectBlocks(m_currentBlock, functionPred);
|
if (_function.isConstructor())
|
||||||
|
connectBlocks(m_currentBlock, functionPred);
|
||||||
|
else
|
||||||
|
connectBlocks(genesis(), functionPred);
|
||||||
|
|
||||||
|
m_context.addAssertion(m_error.currentValue() == 0);
|
||||||
|
for (auto const* var: m_stateVariables)
|
||||||
|
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
m_context.addAssertion(m_context.variable(*var)->valueAtIndex(0) == currentValue(*var));
|
||||||
|
|
||||||
connectBlocks(functionPred, bodyPred);
|
connectBlocks(functionPred, bodyPred);
|
||||||
|
|
||||||
setCurrentBlock(*bodyBlock);
|
setCurrentBlock(*bodyBlock);
|
||||||
@ -225,18 +251,30 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
|||||||
// This is done in endVisit(ContractDefinition).
|
// This is done in endVisit(ContractDefinition).
|
||||||
if (_function.isConstructor())
|
if (_function.isConstructor())
|
||||||
{
|
{
|
||||||
auto constructorExit = createSymbolicBlock(interfaceSort(), "constructor_exit_" + to_string(_function.id()));
|
string suffix = m_currentContract->name() + "_" + to_string(m_currentContract->id());
|
||||||
connectBlocks(m_currentBlock, predicate(*constructorExit, currentStateVariables()));
|
auto constructorExit = createSymbolicBlock(constructorSort(), "constructor_exit_" + suffix);
|
||||||
|
connectBlocks(m_currentBlock, predicate(*constructorExit, vector<smt::Expression>{m_error.currentValue()} + currentStateVariables()));
|
||||||
|
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
clearIndices(m_currentContract, m_currentFunction);
|
||||||
auto stateExprs = currentStateVariables();
|
auto stateExprs = vector<smt::Expression>{m_error.currentValue()} + currentStateVariables();
|
||||||
setCurrentBlock(*constructorExit, &stateExprs);
|
setCurrentBlock(*constructorExit, &stateExprs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connectBlocks(m_currentBlock, interface());
|
auto assertionError = m_error.currentValue();
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
auto sum = summary(_function);
|
||||||
auto stateExprs = currentStateVariables();
|
connectBlocks(m_currentBlock, sum);
|
||||||
setCurrentBlock(*m_interfacePredicate, &stateExprs);
|
|
||||||
|
auto iface = interface();
|
||||||
|
|
||||||
|
auto stateExprs = initialStateVariables();
|
||||||
|
setCurrentBlock(*m_interfaces.at(m_currentContract), &stateExprs);
|
||||||
|
|
||||||
|
if (_function.isPublic())
|
||||||
|
{
|
||||||
|
addVerificationTarget(&_function, m_currentBlock, sum, assertionError);
|
||||||
|
connectBlocks(m_currentBlock, iface, sum && (assertionError == 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_currentFunction = nullptr;
|
m_currentFunction = nullptr;
|
||||||
}
|
}
|
||||||
@ -468,12 +506,23 @@ void CHC::visitAssert(FunctionCall const& _funCall)
|
|||||||
solAssert(args.size() == 1, "");
|
solAssert(args.size() == 1, "");
|
||||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||||
|
|
||||||
createErrorBlock();
|
solAssert(m_currentContract, "");
|
||||||
|
solAssert(m_currentFunction, "");
|
||||||
|
if (m_currentFunction->isConstructor())
|
||||||
|
m_functionAssertions[m_currentContract].insert(&_funCall);
|
||||||
|
else
|
||||||
|
m_functionAssertions[m_currentFunction].insert(&_funCall);
|
||||||
|
|
||||||
smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue());
|
auto previousError = m_error.currentValue();
|
||||||
connectBlocks(m_currentBlock, error(), currentPathConditions() && assertNeg);
|
m_error.increaseIndex();
|
||||||
|
|
||||||
m_verificationTargets.push_back(&_funCall);
|
connectBlocks(
|
||||||
|
m_currentBlock,
|
||||||
|
m_currentFunction->isConstructor() ? summary(*m_currentContract) : summary(*m_currentFunction),
|
||||||
|
currentPathConditions() && !m_context.expression(*args.front())->currentValue() && (m_error.currentValue() == _funCall.id())
|
||||||
|
);
|
||||||
|
|
||||||
|
m_context.addAssertion(m_error.currentValue() == previousError);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::unknownFunctionCall(FunctionCall const&)
|
void CHC::unknownFunctionCall(FunctionCall const&)
|
||||||
@ -488,15 +537,23 @@ void CHC::unknownFunctionCall(FunctionCall const&)
|
|||||||
m_unknownFunctionCallSeen = true;
|
m_unknownFunctionCallSeen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::reset()
|
void CHC::resetSourceAnalysis()
|
||||||
|
{
|
||||||
|
m_verificationTargets.clear();
|
||||||
|
m_safeAssertions.clear();
|
||||||
|
m_functionAssertions.clear();
|
||||||
|
m_callGraph.clear();
|
||||||
|
m_summaries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHC::resetContractAnalysis()
|
||||||
{
|
{
|
||||||
m_stateSorts.clear();
|
m_stateSorts.clear();
|
||||||
m_stateVariables.clear();
|
m_stateVariables.clear();
|
||||||
m_verificationTargets.clear();
|
|
||||||
m_safeAssertions.clear();
|
|
||||||
m_unknownFunctionCallSeen = false;
|
m_unknownFunctionCallSeen = false;
|
||||||
m_breakDest = nullptr;
|
m_breakDest = nullptr;
|
||||||
m_continueDest = nullptr;
|
m_continueDest = nullptr;
|
||||||
|
m_error.resetIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHC::eraseKnowledge()
|
void CHC::eraseKnowledge()
|
||||||
@ -521,17 +578,6 @@ void CHC::clearIndices(ContractDefinition const* _contract, FunctionDefinition c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool CHC::shouldVisit(ContractDefinition const& _contract) const
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
_contract.isLibrary() ||
|
|
||||||
_contract.isInterface()
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
@ -547,7 +593,8 @@ void CHC::setCurrentBlock(
|
|||||||
vector<smt::Expression> const* _arguments
|
vector<smt::Expression> const* _arguments
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
m_context.popSolver();
|
if (m_context.solverStackHeigh() > 0)
|
||||||
|
m_context.popSolver();
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
clearIndices(m_currentContract, m_currentFunction);
|
clearIndices(m_currentContract, m_currentFunction);
|
||||||
m_context.pushSolver();
|
m_context.pushSolver();
|
||||||
@ -557,10 +604,42 @@ void CHC::setCurrentBlock(
|
|||||||
m_currentBlock = predicate(_block);
|
m_currentBlock = predicate(_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set<Expression const*, CHC::IdCompare> CHC::transactionAssertions(ASTNode const* _txRoot)
|
||||||
|
{
|
||||||
|
set<Expression const*, IdCompare> assertions;
|
||||||
|
solidity::util::BreadthFirstSearch<ASTNode const*>{{_txRoot}}.run([&](auto const* function, auto&& _addChild) {
|
||||||
|
assertions.insert(m_functionAssertions[function].begin(), m_functionAssertions[function].end());
|
||||||
|
for (auto const* called: m_callGraph[function])
|
||||||
|
_addChild(called);
|
||||||
|
});
|
||||||
|
return assertions;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<VariableDeclaration const*> CHC::stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<VariableDeclaration const*> stateVars;
|
||||||
|
for (auto const& contract: _contract.annotation().linearizedBaseContracts)
|
||||||
|
for (auto var: contract->stateVariables())
|
||||||
|
stateVars.push_back(var);
|
||||||
|
return stateVars;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::SortPointer> CHC::stateSorts(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<smt::SortPointer> stateSorts;
|
||||||
|
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||||
|
stateSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
return stateSorts;
|
||||||
|
}
|
||||||
|
|
||||||
smt::SortPointer CHC::constructorSort()
|
smt::SortPointer CHC::constructorSort()
|
||||||
{
|
{
|
||||||
// TODO this will change once we support function calls.
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
return interfaceSort();
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
vector<smt::SortPointer>{intSort} + m_stateSorts,
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::SortPointer CHC::interfaceSort()
|
smt::SortPointer CHC::interfaceSort()
|
||||||
@ -572,20 +651,38 @@ smt::SortPointer CHC::interfaceSort()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::SortPointer CHC::interfaceSort(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
stateSorts(_contract),
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A function in the symbolic CFG requires:
|
||||||
|
/// - Index of failed assertion. 0 means no assertion failed.
|
||||||
|
/// - 2 sets of state variables:
|
||||||
|
/// - State variables at the beginning of the current function, immutable
|
||||||
|
/// - Current state variables
|
||||||
|
/// At the beginning of the function these must equal set 1
|
||||||
|
/// - 2 sets of input variables:
|
||||||
|
/// - Input variables at the beginning of the current function, immutable
|
||||||
|
/// - Current input variables
|
||||||
|
/// At the beginning of the function these must equal set 1
|
||||||
|
/// - 1 set of output variables
|
||||||
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
vector<smt::SortPointer> varSorts;
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
for (auto const& var: _function.parameters() + _function.returnParameters())
|
vector<smt::SortPointer> inputSorts;
|
||||||
{
|
for (auto const& var: _function.parameters())
|
||||||
// SMT solvers do not support function types as arguments.
|
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
if (var->type()->category() == Type::Category::Function)
|
vector<smt::SortPointer> outputSorts;
|
||||||
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
for (auto const& var: _function.returnParameters())
|
||||||
else
|
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
varSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
}
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
m_stateSorts + varSorts,
|
vector<smt::SortPointer>{intSort} + m_stateSorts + inputSorts + m_stateSorts + inputSorts + outputSorts,
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -601,19 +698,31 @@ smt::SortPointer CHC::sort(ASTNode const* _node)
|
|||||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
vector<smt::SortPointer> varSorts;
|
vector<smt::SortPointer> varSorts;
|
||||||
for (auto const& var: m_currentFunction->localVariables())
|
for (auto const& var: m_currentFunction->localVariables())
|
||||||
{
|
varSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
// SMT solvers do not support function types as arguments.
|
|
||||||
if (var->type()->category() == Type::Category::Function)
|
|
||||||
varSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
|
||||||
else
|
|
||||||
varSorts.push_back(smt::smtSort(*var->type()));
|
|
||||||
}
|
|
||||||
return make_shared<smt::FunctionSort>(
|
return make_shared<smt::FunctionSort>(
|
||||||
fSort->domain + varSorts,
|
fSort->domain + varSorts,
|
||||||
boolSort
|
boolSort
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::SortPointer CHC::summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
auto stateVariables = stateVariablesIncludingInheritedAndPrivate(_contract);
|
||||||
|
auto sorts = stateSorts(_contract);
|
||||||
|
|
||||||
|
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||||
|
auto intSort = make_shared<smt::Sort>(smt::Kind::Int);
|
||||||
|
vector<smt::SortPointer> inputSorts, outputSorts;
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
inputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
for (auto const& var: _function.returnParameters())
|
||||||
|
outputSorts.push_back(smt::smtSortAbstractFunction(*var->type()));
|
||||||
|
return make_shared<smt::FunctionSort>(
|
||||||
|
vector<smt::SortPointer>{intSort} + sorts + inputSorts + sorts + outputSorts,
|
||||||
|
boolSort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
|
||||||
{
|
{
|
||||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||||
@ -625,12 +734,33 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPoin
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CHC::defineInterfacesAndSummaries(SourceUnit const& _source)
|
||||||
|
{
|
||||||
|
for (auto const& node: _source.nodes())
|
||||||
|
if (auto const* contract = dynamic_cast<ContractDefinition const*>(node.get()))
|
||||||
|
for (auto const* base: contract->annotation().linearizedBaseContracts)
|
||||||
|
{
|
||||||
|
string suffix = base->name() + "_" + to_string(base->id());
|
||||||
|
m_interfaces[base] = createSymbolicBlock(interfaceSort(*base), "interface_" + suffix);
|
||||||
|
for (auto const* var: stateVariablesIncludingInheritedAndPrivate(*base))
|
||||||
|
if (!m_context.knownVariable(*var))
|
||||||
|
createVariable(*var);
|
||||||
|
for (auto const* function: base->definedFunctions())
|
||||||
|
m_summaries[contract].emplace(function, createSummaryBlock(*function, *contract));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
smt::Expression CHC::interface()
|
smt::Expression CHC::interface()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
vector<smt::Expression> paramExprs;
|
||||||
for (auto const& var: m_stateVariables)
|
for (auto const& var: m_stateVariables)
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
return (*m_interfacePredicate)(paramExprs);
|
return (*m_interfaces.at(m_currentContract))(paramExprs);
|
||||||
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::interface(ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
return (*m_interfaces.at(&_contract))(stateVariablesAtIndex(0, _contract));
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::Expression CHC::error()
|
smt::Expression CHC::error()
|
||||||
@ -643,6 +773,27 @@ smt::Expression CHC::error(unsigned _idx)
|
|||||||
return m_errorPredicate->functionValueAtIndex(_idx)({});
|
return m_errorPredicate->functionValueAtIndex(_idx)({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::summary(ContractDefinition const&)
|
||||||
|
{
|
||||||
|
return (*m_constructorSummaryPredicate)(
|
||||||
|
vector<smt::Expression>{m_error.currentValue()} +
|
||||||
|
currentStateVariables()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
smt::Expression CHC::summary(FunctionDefinition const& _function)
|
||||||
|
{
|
||||||
|
vector<smt::Expression> args{m_error.currentValue()};
|
||||||
|
auto contract = _function.annotation().contract;
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(0, *contract) : initialStateVariables();
|
||||||
|
for (auto const& var: _function.parameters())
|
||||||
|
args.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||||
|
args += contract->isLibrary() ? stateVariablesAtIndex(1, *contract) : currentStateVariables();
|
||||||
|
for (auto const& var: _function.returnParameters())
|
||||||
|
args.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
return (*m_summaries.at(m_currentContract).at(&_function))(args);
|
||||||
|
}
|
||||||
|
|
||||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||||
{
|
{
|
||||||
return createSymbolicBlock(sort(_node),
|
return createSymbolicBlock(sort(_node),
|
||||||
@ -653,6 +804,15 @@ unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(ASTNode const* _node,
|
|||||||
predicateName(_node));
|
predicateName(_node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
return createSymbolicBlock(summarySort(_function, _contract),
|
||||||
|
"summary_" +
|
||||||
|
uniquePrefix() +
|
||||||
|
"_" +
|
||||||
|
predicateName(&_function, &_contract));
|
||||||
|
}
|
||||||
|
|
||||||
void CHC::createErrorBlock()
|
void CHC::createErrorBlock()
|
||||||
{
|
{
|
||||||
solAssert(m_errorPredicate, "");
|
solAssert(m_errorPredicate, "");
|
||||||
@ -669,6 +829,28 @@ void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to
|
|||||||
addRule(edge, _from.name + "_to_" + _to.name);
|
addRule(edge, _from.name + "_to_" + _to.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::initialStateVariables()
|
||||||
|
{
|
||||||
|
return stateVariablesAtIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index)
|
||||||
|
{
|
||||||
|
solAssert(m_currentContract, "");
|
||||||
|
vector<smt::Expression> exprs;
|
||||||
|
for (auto const& var: m_stateVariables)
|
||||||
|
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||||
|
return exprs;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<smt::Expression> CHC::stateVariablesAtIndex(int _index, ContractDefinition const& _contract)
|
||||||
|
{
|
||||||
|
vector<smt::Expression> exprs;
|
||||||
|
for (auto const& var: stateVariablesIncludingInheritedAndPrivate(_contract))
|
||||||
|
exprs.push_back(m_context.variable(*var)->valueAtIndex(_index));
|
||||||
|
return exprs;
|
||||||
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentStateVariables()
|
vector<smt::Expression> CHC::currentStateVariables()
|
||||||
{
|
{
|
||||||
solAssert(m_currentContract, "");
|
solAssert(m_currentContract, "");
|
||||||
@ -680,11 +862,22 @@ vector<smt::Expression> CHC::currentStateVariables()
|
|||||||
|
|
||||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||||
{
|
{
|
||||||
vector<smt::Expression> paramExprs;
|
vector<smt::Expression> initInputExprs;
|
||||||
if (m_currentFunction)
|
vector<smt::Expression> mutableInputExprs;
|
||||||
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
|
for (auto const& var: m_currentFunction->parameters())
|
||||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
{
|
||||||
return currentStateVariables() + paramExprs;
|
initInputExprs.push_back(m_context.variable(*var)->valueAtIndex(0));
|
||||||
|
mutableInputExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
}
|
||||||
|
vector<smt::Expression> returnExprs;
|
||||||
|
for (auto const& var: m_currentFunction->returnParameters())
|
||||||
|
returnExprs.push_back(m_context.variable(*var)->currentValue());
|
||||||
|
return vector<smt::Expression>{m_error.currentValue()} +
|
||||||
|
initialStateVariables() +
|
||||||
|
initInputExprs +
|
||||||
|
currentStateVariables() +
|
||||||
|
mutableInputExprs +
|
||||||
|
returnExprs;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<smt::Expression> CHC::currentBlockVariables()
|
vector<smt::Expression> CHC::currentBlockVariables()
|
||||||
@ -696,7 +889,7 @@ vector<smt::Expression> CHC::currentBlockVariables()
|
|||||||
return currentFunctionVariables() + paramExprs;
|
return currentFunctionVariables() + paramExprs;
|
||||||
}
|
}
|
||||||
|
|
||||||
string CHC::predicateName(ASTNode const* _node)
|
string CHC::predicateName(ASTNode const* _node, ContractDefinition const* _contract)
|
||||||
{
|
{
|
||||||
string prefix;
|
string prefix;
|
||||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||||
@ -705,7 +898,12 @@ string CHC::predicateName(ASTNode const* _node)
|
|||||||
if (!funDef->name().empty())
|
if (!funDef->name().empty())
|
||||||
prefix += "_" + funDef->name() + "_";
|
prefix += "_" + funDef->name() + "_";
|
||||||
}
|
}
|
||||||
return prefix + to_string(_node->id());
|
else if (m_currentFunction && !m_currentFunction->name().empty())
|
||||||
|
prefix += m_currentFunction->name();
|
||||||
|
|
||||||
|
auto contract = _contract ? _contract : m_currentContract;
|
||||||
|
solAssert(contract, "");
|
||||||
|
return prefix + "_" + to_string(_node->id()) + "_" + to_string(contract->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
|
smt::Expression CHC::predicate(smt::SymbolicFunctionVariable const& _block)
|
||||||
@ -726,7 +924,7 @@ void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
|
|||||||
m_interface->addRule(_rule, _ruleName);
|
m_interface->addRule(_rule, _ruleName);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
pair<smt::CheckResult, vector<string>> CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
smt::CheckResult result;
|
smt::CheckResult result;
|
||||||
vector<string> values;
|
vector<string> values;
|
||||||
@ -736,7 +934,7 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
|
|||||||
case smt::CheckResult::SATISFIABLE:
|
case smt::CheckResult::SATISFIABLE:
|
||||||
break;
|
break;
|
||||||
case smt::CheckResult::UNSATISFIABLE:
|
case smt::CheckResult::UNSATISFIABLE:
|
||||||
return true;
|
break;
|
||||||
case smt::CheckResult::UNKNOWN:
|
case smt::CheckResult::UNKNOWN:
|
||||||
break;
|
break;
|
||||||
case smt::CheckResult::CONFLICTING:
|
case smt::CheckResult::CONFLICTING:
|
||||||
@ -746,7 +944,12 @@ bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _
|
|||||||
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return {result, values};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CHC::addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId)
|
||||||
|
{
|
||||||
|
m_verificationTargets.emplace(_scope, CHCVerificationTarget{{VerificationTarget::Type::Assert, _from, _constraints}, _errorId});
|
||||||
}
|
}
|
||||||
|
|
||||||
string CHC::uniquePrefix()
|
string CHC::uniquePrefix()
|
||||||
|
@ -79,22 +79,38 @@ private:
|
|||||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
|
struct IdCompare
|
||||||
|
{
|
||||||
|
bool operator()(ASTNode const* lhs, ASTNode const* rhs) const
|
||||||
|
{
|
||||||
|
return lhs->id() < rhs->id();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// Helpers.
|
/// Helpers.
|
||||||
//@{
|
//@{
|
||||||
void reset();
|
void resetSourceAnalysis();
|
||||||
|
void resetContractAnalysis();
|
||||||
void eraseKnowledge();
|
void eraseKnowledge();
|
||||||
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
void clearIndices(ContractDefinition const* _contract, FunctionDefinition const* _function = nullptr) override;
|
||||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
|
||||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||||
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
|
void setCurrentBlock(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const* _arguments = nullptr);
|
||||||
|
std::set<Expression const*, IdCompare> transactionAssertions(ASTNode const* _txRoot);
|
||||||
|
static std::vector<VariableDeclaration const*> stateVariablesIncludingInheritedAndPrivate(ContractDefinition const& _contract);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Sort helpers.
|
/// Sort helpers.
|
||||||
//@{
|
//@{
|
||||||
|
static std::vector<smt::SortPointer> stateSorts(ContractDefinition const& _contract);
|
||||||
smt::SortPointer constructorSort();
|
smt::SortPointer constructorSort();
|
||||||
smt::SortPointer interfaceSort();
|
smt::SortPointer interfaceSort();
|
||||||
|
static smt::SortPointer interfaceSort(ContractDefinition const& _const);
|
||||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||||
smt::SortPointer sort(ASTNode const* _block);
|
smt::SortPointer sort(ASTNode const* _block);
|
||||||
|
/// @returns the sort of a predicate that represents the summary of _function in the scope of _contract.
|
||||||
|
/// The _contract is also needed because the same function might be in many contracts due to inheritance,
|
||||||
|
/// where the sort changes because the set of state variables might change.
|
||||||
|
static smt::SortPointer summarySort(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Predicate helpers.
|
/// Predicate helpers.
|
||||||
@ -102,14 +118,24 @@ private:
|
|||||||
/// @returns a new block of given _sort and _name.
|
/// @returns a new block of given _sort and _name.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
|
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
|
||||||
|
|
||||||
|
/// Creates summary predicates for all functions of all contracts
|
||||||
|
/// in a given _source.
|
||||||
|
void defineInterfacesAndSummaries(SourceUnit const& _source);
|
||||||
|
|
||||||
|
/// Genesis predicate.
|
||||||
|
smt::Expression genesis() { return (*m_genesisPredicate)({}); }
|
||||||
/// Interface predicate over current variables.
|
/// Interface predicate over current variables.
|
||||||
smt::Expression interface();
|
smt::Expression interface();
|
||||||
|
smt::Expression interface(ContractDefinition const& _contract);
|
||||||
/// Error predicate over current variables.
|
/// Error predicate over current variables.
|
||||||
smt::Expression error();
|
smt::Expression error();
|
||||||
smt::Expression error(unsigned _idx);
|
smt::Expression error(unsigned _idx);
|
||||||
|
|
||||||
/// Creates a block for the given _node.
|
/// Creates a block for the given _node.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
||||||
|
/// Creates a call block for the given function _function from contract _contract.
|
||||||
|
/// The contract is needed here because of inheritance.
|
||||||
|
std::unique_ptr<smt::SymbolicFunctionVariable> createSummaryBlock(FunctionDefinition const& _function, ContractDefinition const& _contract);
|
||||||
|
|
||||||
/// Creates a new error block to be used by an assertion.
|
/// Creates a new error block to be used by an assertion.
|
||||||
/// Also registers the predicate.
|
/// Also registers the predicate.
|
||||||
@ -117,6 +143,11 @@ private:
|
|||||||
|
|
||||||
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
|
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
|
||||||
|
|
||||||
|
/// @returns the symbolic values of the state variables at the beginning
|
||||||
|
/// of the current transaction.
|
||||||
|
std::vector<smt::Expression> initialStateVariables();
|
||||||
|
std::vector<smt::Expression> stateVariablesAtIndex(int _index);
|
||||||
|
std::vector<smt::Expression> stateVariablesAtIndex(int _index, ContractDefinition const& _contract);
|
||||||
/// @returns the current symbolic values of the current state variables.
|
/// @returns the current symbolic values of the current state variables.
|
||||||
std::vector<smt::Expression> currentStateVariables();
|
std::vector<smt::Expression> currentStateVariables();
|
||||||
|
|
||||||
@ -128,19 +159,26 @@ private:
|
|||||||
std::vector<smt::Expression> currentBlockVariables();
|
std::vector<smt::Expression> currentBlockVariables();
|
||||||
|
|
||||||
/// @returns the predicate name for a given node.
|
/// @returns the predicate name for a given node.
|
||||||
std::string predicateName(ASTNode const* _node);
|
std::string predicateName(ASTNode const* _node, ContractDefinition const* _contract = nullptr);
|
||||||
/// @returns a predicate application over the current scoped variables.
|
/// @returns a predicate application over the current scoped variables.
|
||||||
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block);
|
||||||
/// @returns a predicate application over @param _arguments.
|
/// @returns a predicate application over @param _arguments.
|
||||||
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
|
smt::Expression predicate(smt::SymbolicFunctionVariable const& _block, std::vector<smt::Expression> const& _arguments);
|
||||||
|
/// @returns a predicate that defines a constructor summary.
|
||||||
|
smt::Expression summary(ContractDefinition const& _contract);
|
||||||
|
/// @returns a predicate that defines a function summary.
|
||||||
|
smt::Expression summary(FunctionDefinition const& _function);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Solver related.
|
/// Solver related.
|
||||||
//@{
|
//@{
|
||||||
/// Adds Horn rule to the solver.
|
/// Adds Horn rule to the solver.
|
||||||
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
||||||
/// @returns true if query is unsatisfiable (safe).
|
/// @returns <true, empty> if query is unsatisfiable (safe).
|
||||||
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
/// @returns <false, model> otherwise.
|
||||||
|
std::pair<smt::CheckResult, std::vector<std::string>> query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
||||||
|
|
||||||
|
void addVerificationTarget(ASTNode const* _scope, smt::Expression _from, smt::Expression _constraints, smt::Expression _errorId);
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Misc.
|
/// Misc.
|
||||||
@ -157,15 +195,29 @@ private:
|
|||||||
|
|
||||||
/// Implicit constructor predicate.
|
/// Implicit constructor predicate.
|
||||||
/// Explicit constructors are handled as functions.
|
/// Explicit constructors are handled as functions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorPredicate;
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_implicitConstructorPredicate;
|
||||||
|
|
||||||
|
/// Constructor summary predicate, exists after the constructor
|
||||||
|
/// (implicit or explicit) and before the interface.
|
||||||
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_constructorSummaryPredicate;
|
||||||
|
|
||||||
/// Artificial Interface predicate.
|
/// Artificial Interface predicate.
|
||||||
/// Single entry block for all functions.
|
/// Single entry block for all functions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_interfacePredicate;
|
std::map<ContractDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>> m_interfaces;
|
||||||
|
|
||||||
/// Artificial Error predicate.
|
/// Artificial Error predicate.
|
||||||
/// Single error block for all assertions.
|
/// Single error block for all assertions.
|
||||||
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
std::unique_ptr<smt::SymbolicFunctionVariable> m_errorPredicate;
|
||||||
|
|
||||||
|
/// Function predicates.
|
||||||
|
std::map<ContractDefinition const*, std::map<FunctionDefinition const*, std::unique_ptr<smt::SymbolicFunctionVariable>>> m_summaries;
|
||||||
|
|
||||||
|
smt::SymbolicIntVariable m_error{
|
||||||
|
TypeProvider::uint256(),
|
||||||
|
TypeProvider::uint256(),
|
||||||
|
"error",
|
||||||
|
m_context
|
||||||
|
};
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
/// Variables.
|
/// Variables.
|
||||||
@ -180,7 +232,12 @@ private:
|
|||||||
|
|
||||||
/// Verification targets.
|
/// Verification targets.
|
||||||
//@{
|
//@{
|
||||||
std::vector<Expression const*> m_verificationTargets;
|
struct CHCVerificationTarget: VerificationTarget
|
||||||
|
{
|
||||||
|
smt::Expression errorId;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<ASTNode const*, CHCVerificationTarget, IdCompare> m_verificationTargets;
|
||||||
|
|
||||||
/// Assertions proven safe.
|
/// Assertions proven safe.
|
||||||
std::set<Expression const*> m_safeAssertions;
|
std::set<Expression const*> m_safeAssertions;
|
||||||
@ -190,6 +247,10 @@ private:
|
|||||||
//@{
|
//@{
|
||||||
FunctionDefinition const* m_currentFunction = nullptr;
|
FunctionDefinition const* m_currentFunction = nullptr;
|
||||||
|
|
||||||
|
std::map<ASTNode const*, std::set<ASTNode const*, IdCompare>, IdCompare> m_callGraph;
|
||||||
|
|
||||||
|
std::map<ASTNode const*, std::set<Expression const*>, IdCompare> m_functionAssertions;
|
||||||
|
|
||||||
/// The current block.
|
/// The current block.
|
||||||
smt::Expression m_currentBlock = smt::Expression(true);
|
smt::Expression m_currentBlock = smt::Expression(true);
|
||||||
|
|
||||||
|
@ -140,6 +140,7 @@ public:
|
|||||||
void pushSolver();
|
void pushSolver();
|
||||||
void popSolver();
|
void popSolver();
|
||||||
void addAssertion(Expression const& _e);
|
void addAssertion(Expression const& _e);
|
||||||
|
unsigned solverStackHeigh() { return m_assertions.size(); } const
|
||||||
SolverInterface* solver()
|
SolverInterface* solver()
|
||||||
{
|
{
|
||||||
solAssert(m_solver, "");
|
solAssert(m_solver, "");
|
||||||
|
@ -29,9 +29,9 @@ ModelChecker::ModelChecker(
|
|||||||
ReadCallback::Callback const& _smtCallback,
|
ReadCallback::Callback const& _smtCallback,
|
||||||
smt::SMTSolverChoice _enabledSolvers
|
smt::SMTSolverChoice _enabledSolvers
|
||||||
):
|
):
|
||||||
|
m_context(),
|
||||||
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
m_bmc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
||||||
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers),
|
m_chc(m_context, _errorReporter, _smtlib2Responses, _smtCallback, _enabledSolvers)
|
||||||
m_context()
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,14 +62,14 @@ public:
|
|||||||
static smt::SMTSolverChoice availableSolvers();
|
static smt::SMTSolverChoice availableSolvers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// Stores the context of the encoding.
|
||||||
|
smt::EncodingContext m_context;
|
||||||
|
|
||||||
/// Bounded Model Checker engine.
|
/// Bounded Model Checker engine.
|
||||||
BMC m_bmc;
|
BMC m_bmc;
|
||||||
|
|
||||||
/// Constrained Horn Clauses engine.
|
/// Constrained Horn Clauses engine.
|
||||||
CHC m_chc;
|
CHC m_chc;
|
||||||
|
|
||||||
/// Stores the context of the encoding.
|
|
||||||
smt::EncodingContext m_context;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -407,19 +407,26 @@ void SMTEncoder::endVisit(TupleExpression const& _tuple)
|
|||||||
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
|
auto const& symbTuple = dynamic_pointer_cast<smt::SymbolicTupleVariable>(m_context.expression(_tuple));
|
||||||
solAssert(symbTuple, "");
|
solAssert(symbTuple, "");
|
||||||
auto const& symbComponents = symbTuple->components();
|
auto const& symbComponents = symbTuple->components();
|
||||||
auto const& tupleComponents = _tuple.components();
|
auto const* tupleComponents = &_tuple.components();
|
||||||
solAssert(symbComponents.size() == _tuple.components().size(), "");
|
while (tupleComponents->size() == 1)
|
||||||
|
{
|
||||||
|
auto innerTuple = dynamic_pointer_cast<TupleExpression>(tupleComponents->front());
|
||||||
|
solAssert(innerTuple, "");
|
||||||
|
tupleComponents = &innerTuple->components();
|
||||||
|
}
|
||||||
|
solAssert(symbComponents.size() == tupleComponents->size(), "");
|
||||||
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
for (unsigned i = 0; i < symbComponents.size(); ++i)
|
||||||
{
|
{
|
||||||
auto sComponent = symbComponents.at(i);
|
auto sComponent = symbComponents.at(i);
|
||||||
auto tComponent = tupleComponents.at(i);
|
auto tComponent = tupleComponents->at(i);
|
||||||
if (sComponent && tComponent)
|
if (sComponent && tComponent)
|
||||||
{
|
{
|
||||||
if (auto varDecl = identifierToVariable(*tComponent))
|
if (auto varDecl = identifierToVariable(*tComponent))
|
||||||
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
m_context.addAssertion(sComponent->currentValue() == currentValue(*varDecl));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
solAssert(m_context.knownExpression(*tComponent), "");
|
if (!m_context.knownExpression(*tComponent))
|
||||||
|
createExpr(*tComponent);
|
||||||
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
m_context.addAssertion(sComponent->currentValue() == expr(*tComponent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,11 +58,11 @@ private:
|
|||||||
z3::sort z3Sort(smt::Sort const& _sort);
|
z3::sort z3Sort(smt::Sort const& _sort);
|
||||||
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
|
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
|
||||||
|
|
||||||
std::map<std::string, z3::expr> m_constants;
|
|
||||||
std::map<std::string, z3::func_decl> m_functions;
|
|
||||||
|
|
||||||
z3::context m_context;
|
z3::context m_context;
|
||||||
z3::solver m_solver;
|
z3::solver m_solver;
|
||||||
|
|
||||||
|
std::map<std::string, z3::expr> m_constants;
|
||||||
|
std::map<std::string, z3::func_decl> m_functions;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -565,7 +565,7 @@ string const* CompilerStack::sourceMapping(string const& _contractName) const
|
|||||||
if (!c.sourceMapping)
|
if (!c.sourceMapping)
|
||||||
{
|
{
|
||||||
if (auto items = assemblyItems(_contractName))
|
if (auto items = assemblyItems(_contractName))
|
||||||
c.sourceMapping = make_unique<string>(computeSourceMapping(*items));
|
c.sourceMapping = make_unique<string>(evmasm::AssemblyItem::computeSourceMapping(*items, sourceIndices()));
|
||||||
}
|
}
|
||||||
return c.sourceMapping.get();
|
return c.sourceMapping.get();
|
||||||
}
|
}
|
||||||
@ -579,7 +579,9 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
|
|||||||
if (!c.runtimeSourceMapping)
|
if (!c.runtimeSourceMapping)
|
||||||
{
|
{
|
||||||
if (auto items = runtimeAssemblyItems(_contractName))
|
if (auto items = runtimeAssemblyItems(_contractName))
|
||||||
c.runtimeSourceMapping = make_unique<string>(computeSourceMapping(*items));
|
c.runtimeSourceMapping = make_unique<string>(
|
||||||
|
evmasm::AssemblyItem::computeSourceMapping(*items, sourceIndices())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return c.runtimeSourceMapping.get();
|
return c.runtimeSourceMapping.get();
|
||||||
}
|
}
|
||||||
@ -1005,7 +1007,6 @@ void CompilerStack::resolveImports()
|
|||||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||||
{
|
{
|
||||||
string const& path = import->annotation().absolutePath;
|
string const& path = import->annotation().absolutePath;
|
||||||
solAssert(!path.empty(), "");
|
|
||||||
solAssert(m_sources.count(path), "");
|
solAssert(m_sources.count(path), "");
|
||||||
import->annotation().sourceUnit = m_sources[path].ast.get();
|
import->annotation().sourceUnit = m_sources[path].ast.get();
|
||||||
toposort(&m_sources[path]);
|
toposort(&m_sources[path]);
|
||||||
@ -1390,95 +1391,6 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
|
|||||||
return encoder.serialise();
|
return encoder.serialise();
|
||||||
}
|
}
|
||||||
|
|
||||||
string CompilerStack::computeSourceMapping(evmasm::AssemblyItems const& _items) const
|
|
||||||
{
|
|
||||||
if (m_stackState != CompilationSuccessful)
|
|
||||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
|
||||||
|
|
||||||
string ret;
|
|
||||||
map<string, unsigned> sourceIndicesMap = sourceIndices();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -401,9 +401,6 @@ private:
|
|||||||
/// @returns the metadata CBOR for the given serialised metadata JSON.
|
/// @returns the metadata CBOR for the given serialised metadata JSON.
|
||||||
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
|
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
|
||||||
|
|
||||||
/// @returns the computer source mapping string.
|
|
||||||
std::string computeSourceMapping(evmasm::AssemblyItems const& _items) const;
|
|
||||||
|
|
||||||
/// @returns the contract ABI as a JSON object.
|
/// @returns the contract ABI as a JSON object.
|
||||||
/// This will generate the JSON object and store it in the Contract object if it is not present yet.
|
/// This will generate the JSON object and store it in the Contract object if it is not present yet.
|
||||||
Json::Value const& contractABI(Contract const&) const;
|
Json::Value const& contractABI(Contract const&) const;
|
||||||
|
@ -1081,7 +1081,7 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
|||||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
||||||
wildcardMatchesExperimental
|
wildcardMatchesExperimental
|
||||||
))
|
))
|
||||||
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr);
|
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, object.sourceMappings.get());
|
||||||
|
|
||||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
|
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
|
||||||
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
|
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
|
||||||
|
@ -695,7 +695,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
);
|
);
|
||||||
|
|
||||||
bool isIndexed = false;
|
bool isIndexed = false;
|
||||||
bool isDeclaredConst = false;
|
VariableDeclaration::Constantness constantness = VariableDeclaration::Constantness::Mutable;
|
||||||
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
ASTPointer<OverrideSpecifier> overrides = nullptr;
|
||||||
Visibility visibility(Visibility::Default);
|
Visibility visibility(Visibility::Default);
|
||||||
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
VariableDeclaration::Location location = VariableDeclaration::Location::Unspecified;
|
||||||
@ -731,7 +731,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
if (_options.allowIndexed && token == Token::Indexed)
|
if (_options.allowIndexed && token == Token::Indexed)
|
||||||
isIndexed = true;
|
isIndexed = true;
|
||||||
else if (token == Token::Constant)
|
else if (token == Token::Constant)
|
||||||
isDeclaredConst = true;
|
constantness = VariableDeclaration::Constantness::Constant;
|
||||||
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
else if (_options.allowLocationSpecifier && TokenTraits::isLocationSpecifier(token))
|
||||||
{
|
{
|
||||||
if (location != VariableDeclaration::Location::Unspecified)
|
if (location != VariableDeclaration::Location::Unspecified)
|
||||||
@ -790,7 +790,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
|
|||||||
visibility,
|
visibility,
|
||||||
_options.isStateVariable,
|
_options.isStateVariable,
|
||||||
isIndexed,
|
isIndexed,
|
||||||
isDeclaredConst,
|
constantness,
|
||||||
overrides,
|
overrides,
|
||||||
location
|
location
|
||||||
);
|
);
|
||||||
|
@ -82,7 +82,6 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect,
|
|||||||
vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
|
vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
|
||||||
{
|
{
|
||||||
expectValidType(_literal.type, _literal.location);
|
expectValidType(_literal.type, _literal.location);
|
||||||
|
|
||||||
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
|
if (_literal.kind == LiteralKind::String && _literal.value.str().size() > 32)
|
||||||
typeError(
|
typeError(
|
||||||
_literal.location,
|
_literal.location,
|
||||||
@ -93,6 +92,13 @@ vector<YulString> AsmAnalyzer::operator()(Literal const& _literal)
|
|||||||
else if (_literal.kind == LiteralKind::Boolean)
|
else if (_literal.kind == LiteralKind::Boolean)
|
||||||
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "");
|
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "");
|
||||||
|
|
||||||
|
if (!m_dialect.validTypeForLiteral(_literal.kind, _literal.value, _literal.type))
|
||||||
|
typeError(
|
||||||
|
_literal.location,
|
||||||
|
"Invalid type \"" + _literal.type.str() + "\" for literal \"" + _literal.value.str() + "\"."
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
return {_literal.type};
|
return {_literal.type};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +185,8 @@ void AsmAnalyzer::operator()(Assignment const& _assignment)
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (size_t i = 0; i < numVariables; ++i)
|
for (size_t i = 0; i < numVariables; ++i)
|
||||||
checkAssignment(_assignment.variableNames[i]);
|
if (i < types.size())
|
||||||
|
checkAssignment(_assignment.variableNames[i], types[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
||||||
@ -193,6 +200,9 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
|||||||
yul::IdentifierContext::VariableDeclaration,
|
yul::IdentifierContext::VariableDeclaration,
|
||||||
m_currentScope->insideFunction()
|
m_currentScope->insideFunction()
|
||||||
);
|
);
|
||||||
|
for (auto const& variable: _varDecl.variables)
|
||||||
|
expectValidType(variable.type, variable.location);
|
||||||
|
|
||||||
if (_varDecl.value)
|
if (_varDecl.value)
|
||||||
{
|
{
|
||||||
vector<YulString> types = std::visit(*this, *_varDecl.value);
|
vector<YulString> types = std::visit(*this, *_varDecl.value);
|
||||||
@ -204,15 +214,25 @@ void AsmAnalyzer::operator()(VariableDeclaration const& _varDecl)
|
|||||||
to_string(types.size()) +
|
to_string(types.size()) +
|
||||||
" values."
|
" values."
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _varDecl.variables.size(); ++i)
|
||||||
|
{
|
||||||
|
YulString givenType = m_dialect.defaultType;
|
||||||
|
if (i < types.size())
|
||||||
|
givenType = types[i];
|
||||||
|
TypedName const& variable = _varDecl.variables[i];
|
||||||
|
if (variable.type != givenType)
|
||||||
|
typeError(
|
||||||
|
variable.location,
|
||||||
|
"Assigning value of type \"" + givenType.str() + "\" to variable of type \"" + variable.type.str() + "."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (TypedName const& variable: _varDecl.variables)
|
for (TypedName const& variable: _varDecl.variables)
|
||||||
{
|
|
||||||
expectValidType(variable.type, variable.location);
|
|
||||||
m_activeVariables.insert(&std::get<Scope::Variable>(
|
m_activeVariables.insert(&std::get<Scope::Variable>(
|
||||||
m_currentScope->identifiers.at(variable.name))
|
m_currentScope->identifiers.at(variable.name))
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
|
void AsmAnalyzer::operator()(FunctionDefinition const& _funDef)
|
||||||
@ -291,6 +311,11 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::reverse(argTypes.begin(), argTypes.end());
|
||||||
|
|
||||||
|
if (parameterTypes && parameterTypes->size() == argTypes.size())
|
||||||
|
for (size_t i = 0; i < parameterTypes->size(); ++i)
|
||||||
|
expectType((*parameterTypes)[i], argTypes[i], locationOf(_funCall.arguments[i]));
|
||||||
|
|
||||||
if (m_success)
|
if (m_success)
|
||||||
{
|
{
|
||||||
@ -306,7 +331,7 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
|||||||
|
|
||||||
void AsmAnalyzer::operator()(If const& _if)
|
void AsmAnalyzer::operator()(If const& _if)
|
||||||
{
|
{
|
||||||
expectExpression(*_if.condition);
|
expectBoolExpression(*_if.condition);
|
||||||
|
|
||||||
(*this)(_if.body);
|
(*this)(_if.body);
|
||||||
}
|
}
|
||||||
@ -315,32 +340,15 @@ void AsmAnalyzer::operator()(Switch const& _switch)
|
|||||||
{
|
{
|
||||||
yulAssert(_switch.expression, "");
|
yulAssert(_switch.expression, "");
|
||||||
|
|
||||||
expectExpression(*_switch.expression);
|
YulString valueType = expectExpression(*_switch.expression);
|
||||||
|
|
||||||
YulString caseType;
|
|
||||||
bool mismatchingTypes = false;
|
|
||||||
for (auto const& _case: _switch.cases)
|
|
||||||
if (_case.value)
|
|
||||||
{
|
|
||||||
if (caseType.empty())
|
|
||||||
caseType = _case.value->type;
|
|
||||||
else if (caseType != _case.value->type)
|
|
||||||
{
|
|
||||||
mismatchingTypes = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mismatchingTypes)
|
|
||||||
m_errorReporter.typeError(
|
|
||||||
_switch.location,
|
|
||||||
"Switch cases have non-matching types."
|
|
||||||
);
|
|
||||||
|
|
||||||
set<u256> cases;
|
set<u256> cases;
|
||||||
for (auto const& _case: _switch.cases)
|
for (auto const& _case: _switch.cases)
|
||||||
{
|
{
|
||||||
if (_case.value)
|
if (_case.value)
|
||||||
{
|
{
|
||||||
|
expectType(valueType, _case.value->type, _case.value->location);
|
||||||
|
|
||||||
// We cannot use "expectExpression" here because *_case.value is not an
|
// We cannot use "expectExpression" here because *_case.value is not an
|
||||||
// Expression and would be converted to an Expression otherwise.
|
// Expression and would be converted to an Expression otherwise.
|
||||||
(*this)(*_case.value);
|
(*this)(*_case.value);
|
||||||
@ -366,8 +374,7 @@ void AsmAnalyzer::operator()(ForLoop const& _for)
|
|||||||
// condition, the body and the post part inside.
|
// condition, the body and the post part inside.
|
||||||
m_currentScope = &scope(&_for.pre);
|
m_currentScope = &scope(&_for.pre);
|
||||||
|
|
||||||
expectExpression(*_for.condition);
|
expectBoolExpression(*_for.condition);
|
||||||
|
|
||||||
// backup outer for-loop & create new state
|
// backup outer for-loop & create new state
|
||||||
auto outerForLoop = m_currentForLoop;
|
auto outerForLoop = m_currentForLoop;
|
||||||
m_currentForLoop = &_for;
|
m_currentForLoop = &_for;
|
||||||
@ -403,10 +410,24 @@ YulString AsmAnalyzer::expectExpression(Expression const& _expr)
|
|||||||
return types.empty() ? m_dialect.defaultType : types.front();
|
return types.empty() ? m_dialect.defaultType : types.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsmAnalyzer::checkAssignment(Identifier const& _variable)
|
void AsmAnalyzer::expectBoolExpression(Expression const& _expr)
|
||||||
|
{
|
||||||
|
YulString type = expectExpression(_expr);
|
||||||
|
if (type != m_dialect.boolType)
|
||||||
|
typeError(locationOf(_expr),
|
||||||
|
"Expected a value of boolean type \"" +
|
||||||
|
m_dialect.boolType.str() +
|
||||||
|
"\" but got \"" +
|
||||||
|
type.str() +
|
||||||
|
"\""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsmAnalyzer::checkAssignment(Identifier const& _variable, YulString _valueType)
|
||||||
{
|
{
|
||||||
yulAssert(!_variable.name.empty(), "");
|
yulAssert(!_variable.name.empty(), "");
|
||||||
size_t numErrorsBefore = m_errorReporter.errors().size();
|
size_t numErrorsBefore = m_errorReporter.errors().size();
|
||||||
|
YulString const* variableType = nullptr;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
|
if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
|
||||||
{
|
{
|
||||||
@ -418,6 +439,8 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
|
|||||||
_variable.location,
|
_variable.location,
|
||||||
"Variable " + _variable.name.str() + " used before it was declared."
|
"Variable " + _variable.name.str() + " used before it was declared."
|
||||||
);
|
);
|
||||||
|
else
|
||||||
|
variableType = &std::get<Scope::Variable>(*var).type;
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
else if (m_resolver)
|
else if (m_resolver)
|
||||||
@ -427,6 +450,7 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
|
|||||||
if (variableSize != size_t(-1))
|
if (variableSize != size_t(-1))
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
|
variableType = &m_dialect.defaultType;
|
||||||
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
|
yulAssert(variableSize == 1, "Invalid stack size of external reference.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -438,6 +462,17 @@ void AsmAnalyzer::checkAssignment(Identifier const& _variable)
|
|||||||
if (numErrorsBefore == m_errorReporter.errors().size())
|
if (numErrorsBefore == m_errorReporter.errors().size())
|
||||||
declarationError(_variable.location, "Variable not found or variable not lvalue.");
|
declarationError(_variable.location, "Variable not found or variable not lvalue.");
|
||||||
}
|
}
|
||||||
|
if (variableType && *variableType != _valueType)
|
||||||
|
typeError(_variable.location,
|
||||||
|
"Assigning a value of type \"" +
|
||||||
|
_valueType.str() +
|
||||||
|
"\" to a variable of type \"" +
|
||||||
|
variableType->str() +
|
||||||
|
"\"."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (m_success)
|
||||||
|
yulAssert(variableType, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Scope& AsmAnalyzer::scope(Block const* _block)
|
Scope& AsmAnalyzer::scope(Block const* _block)
|
||||||
@ -447,15 +482,28 @@ Scope& AsmAnalyzer::scope(Block const* _block)
|
|||||||
yulAssert(scopePtr, "Scope requested but not present.");
|
yulAssert(scopePtr, "Scope requested but not present.");
|
||||||
return *scopePtr;
|
return *scopePtr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
|
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
if (!_type.empty() && !m_dialect.types.count(_type))
|
if (!m_dialect.types.count(_type))
|
||||||
m_errorReporter.typeError(
|
typeError(
|
||||||
_location,
|
_location,
|
||||||
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
|
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, SourceLocation const& _location)
|
||||||
|
{
|
||||||
|
if (_expectedType != _givenType)
|
||||||
|
typeError(_location,
|
||||||
|
"Expected a value of type \"" +
|
||||||
|
_expectedType.str() +
|
||||||
|
"\" but got \"" +
|
||||||
|
_givenType.str() +
|
||||||
|
"\""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
|
bool AsmAnalyzer::warnOnInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
|
||||||
{
|
{
|
||||||
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
|
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
|
||||||
|
@ -96,13 +96,18 @@ private:
|
|||||||
/// Visits the expression, expects that it evaluates to exactly one value and
|
/// Visits the expression, expects that it evaluates to exactly one value and
|
||||||
/// returns the type. Reports errors on errors and returns the default type.
|
/// returns the type. Reports errors on errors and returns the default type.
|
||||||
YulString expectExpression(Expression const& _expr);
|
YulString expectExpression(Expression const& _expr);
|
||||||
|
/// Vists the expression and expects it to return a single boolean value.
|
||||||
|
/// Reports an error otherwise.
|
||||||
|
void expectBoolExpression(Expression const& _expr);
|
||||||
bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
|
bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
|
||||||
|
|
||||||
/// Verifies that a variable to be assigned to exists and can be assigned to.
|
/// Verifies that a variable to be assigned to exists, can be assigned to
|
||||||
void checkAssignment(Identifier const& _variable);
|
/// and has the same type as the value.
|
||||||
|
void checkAssignment(Identifier const& _variable, YulString _valueType);
|
||||||
|
|
||||||
Scope& scope(Block const* _block);
|
Scope& scope(Block const* _block);
|
||||||
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
|
void expectValidType(YulString _type, langutil::SourceLocation const& _location);
|
||||||
|
void expectType(YulString _expectedType, YulString _givenType, langutil::SourceLocation const& _location);
|
||||||
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
|
bool warnOnInstructions(evmasm::Instruction _instr, langutil::SourceLocation const& _location);
|
||||||
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
|
bool warnOnInstructions(std::string const& _instrIdentifier, langutil::SourceLocation const& _location);
|
||||||
|
|
||||||
|
@ -204,6 +204,12 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
|
|||||||
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
|
compileEVM(adapter, false, m_optimiserSettings.optimizeStackAllocation);
|
||||||
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
|
object.bytecode = make_shared<evmasm::LinkerObject>(assembly.assemble());
|
||||||
object.assembly = assembly.assemblyString();
|
object.assembly = assembly.assemblyString();
|
||||||
|
object.sourceMappings = make_unique<string>(
|
||||||
|
evmasm::AssemblyItem::computeSourceMapping(
|
||||||
|
assembly.items(),
|
||||||
|
{{scanner().charStream() ? scanner().charStream()->name() : "", 0}}
|
||||||
|
)
|
||||||
|
);
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
case Machine::EVM15:
|
case Machine::EVM15:
|
||||||
|
@ -48,6 +48,7 @@ struct MachineAssemblyObject
|
|||||||
{
|
{
|
||||||
std::shared_ptr<evmasm::LinkerObject> bytecode;
|
std::shared_ptr<evmasm::LinkerObject> bytecode;
|
||||||
std::string assembly;
|
std::string assembly;
|
||||||
|
std::unique_ptr<std::string> sourceMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -114,6 +115,8 @@ private:
|
|||||||
std::shared_ptr<yul::Object> m_parserResult;
|
std::shared_ptr<yul::Object> m_parserResult;
|
||||||
langutil::ErrorList m_errors;
|
langutil::ErrorList m_errors;
|
||||||
langutil::ErrorReporter m_errorReporter;
|
langutil::ErrorReporter m_errorReporter;
|
||||||
|
|
||||||
|
std::unique_ptr<std::string> m_sourceMappings;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,8 @@ add_library(yul
|
|||||||
optimiser/Suite.h
|
optimiser/Suite.h
|
||||||
optimiser/SyntacticalEquality.cpp
|
optimiser/SyntacticalEquality.cpp
|
||||||
optimiser/SyntacticalEquality.h
|
optimiser/SyntacticalEquality.h
|
||||||
|
optimiser/TypeInfo.cpp
|
||||||
|
optimiser/TypeInfo.h
|
||||||
optimiser/UnusedPruner.cpp
|
optimiser/UnusedPruner.cpp
|
||||||
optimiser/UnusedPruner.h
|
optimiser/UnusedPruner.h
|
||||||
optimiser/VarDeclInitializer.cpp
|
optimiser/VarDeclInitializer.cpp
|
||||||
|
38
libyul/ControlFlowSideEffects.h
Normal file
38
libyul/ControlFlowSideEffects.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace solidity::yul
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Side effects of code related to control flow.
|
||||||
|
*/
|
||||||
|
struct ControlFlowSideEffects
|
||||||
|
{
|
||||||
|
/// If true, this code terminates the control flow.
|
||||||
|
/// State may or may not be reverted as indicated by the ``reverts`` flag.
|
||||||
|
bool terminates = false;
|
||||||
|
/// If true, this code reverts all state changes in the transaction.
|
||||||
|
/// Whenever this is true, ``terminates`` has to be true as well.
|
||||||
|
bool reverts = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -19,9 +19,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libyul/Dialect.h>
|
#include <libyul/Dialect.h>
|
||||||
|
#include <libyul/AsmData.h>
|
||||||
|
|
||||||
using namespace solidity::yul;
|
using namespace solidity::yul;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace solidity::langutil;
|
||||||
|
|
||||||
|
Literal Dialect::zeroLiteralForType(solidity::yul::YulString _type) const
|
||||||
|
{
|
||||||
|
if (_type == boolType && _type != defaultType)
|
||||||
|
return {SourceLocation{}, LiteralKind::Boolean, "false"_yulstring, _type};
|
||||||
|
return {SourceLocation{}, LiteralKind::Number, "0"_yulstring, _type};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Dialect::validTypeForLiteral(LiteralKind _kind, YulString, YulString _type) const
|
||||||
|
{
|
||||||
|
if (_kind == LiteralKind::Boolean)
|
||||||
|
return _type == boolType;
|
||||||
|
else
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Dialect const& Dialect::yulDeprecated()
|
Dialect const& Dialect::yulDeprecated()
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <libyul/YulString.h>
|
#include <libyul/YulString.h>
|
||||||
#include <libyul/SideEffects.h>
|
#include <libyul/SideEffects.h>
|
||||||
|
#include <libyul/ControlFlowSideEffects.h>
|
||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ namespace solidity::yul
|
|||||||
|
|
||||||
class YulString;
|
class YulString;
|
||||||
using Type = YulString;
|
using Type = YulString;
|
||||||
|
enum class LiteralKind;
|
||||||
|
struct Literal;
|
||||||
|
|
||||||
struct BuiltinFunction
|
struct BuiltinFunction
|
||||||
{
|
{
|
||||||
@ -40,6 +43,7 @@ struct BuiltinFunction
|
|||||||
std::vector<Type> parameters;
|
std::vector<Type> parameters;
|
||||||
std::vector<Type> returns;
|
std::vector<Type> returns;
|
||||||
SideEffects sideEffects;
|
SideEffects sideEffects;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects;
|
||||||
/// If true, this is the msize instruction.
|
/// If true, this is the msize instruction.
|
||||||
bool isMSize = false;
|
bool isMSize = false;
|
||||||
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
||||||
@ -52,15 +56,21 @@ struct Dialect: boost::noncopyable
|
|||||||
YulString defaultType;
|
YulString defaultType;
|
||||||
/// Type used for the literals "true" and "false".
|
/// Type used for the literals "true" and "false".
|
||||||
YulString boolType;
|
YulString boolType;
|
||||||
std::set<YulString> types;
|
std::set<YulString> types = {{}};
|
||||||
|
|
||||||
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
|
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
|
||||||
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
|
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
|
||||||
|
|
||||||
virtual BuiltinFunction const* discardFunction() const { return nullptr; }
|
virtual BuiltinFunction const* discardFunction(YulString /* _type */) const { return nullptr; }
|
||||||
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
|
virtual BuiltinFunction const* equalityFunction(YulString /* _type */) const { return nullptr; }
|
||||||
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
|
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
|
||||||
|
|
||||||
|
/// Check whether the given type is legal for the given literal value.
|
||||||
|
/// Should only be called if the type exists in the dialect at all.
|
||||||
|
virtual bool validTypeForLiteral(LiteralKind _kind, YulString _value, YulString _type) const;
|
||||||
|
|
||||||
|
virtual Literal zeroLiteralForType(YulString _type) const;
|
||||||
|
|
||||||
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
|
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
|
||||||
|
|
||||||
Dialect() = default;
|
Dialect() = default;
|
||||||
|
@ -52,6 +52,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
|||||||
f.parameters.resize(info.args);
|
f.parameters.resize(info.args);
|
||||||
f.returns.resize(info.ret);
|
f.returns.resize(info.ret);
|
||||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||||
|
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
|
||||||
|
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
|
||||||
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
f.isMSize = _instruction == evmasm::Instruction::MSIZE;
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
f.instruction = _instruction;
|
f.instruction = _instruction;
|
||||||
@ -290,6 +292,28 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
|
|||||||
m_functions["u256_to_bool"_yulstring].returns = {"bool"_yulstring};
|
m_functions["u256_to_bool"_yulstring].returns = {"bool"_yulstring};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuiltinFunctionForEVM const* EVMDialectTyped::discardFunction(YulString _type) const
|
||||||
|
{
|
||||||
|
if (_type == "bool"_yulstring)
|
||||||
|
return builtin("popbool"_yulstring);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yulAssert(_type == defaultType, "");
|
||||||
|
return builtin("pop"_yulstring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFunctionForEVM const* EVMDialectTyped::equalityFunction(YulString _type) const
|
||||||
|
{
|
||||||
|
if (_type == "bool"_yulstring)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yulAssert(_type == defaultType, "");
|
||||||
|
return builtin("eq"_yulstring);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EVMDialectTyped const& EVMDialectTyped::instance(langutil::EVMVersion _version)
|
EVMDialectTyped const& EVMDialectTyped::instance(langutil::EVMVersion _version)
|
||||||
{
|
{
|
||||||
static map<langutil::EVMVersion, unique_ptr<EVMDialectTyped const>> dialects;
|
static map<langutil::EVMVersion, unique_ptr<EVMDialectTyped const>> dialects;
|
||||||
|
@ -68,8 +68,8 @@ struct EVMDialect: public Dialect
|
|||||||
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
|
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
|
||||||
BuiltinFunctionForEVM const* builtin(YulString _name) const override;
|
BuiltinFunctionForEVM const* builtin(YulString _name) const override;
|
||||||
|
|
||||||
BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); }
|
BuiltinFunctionForEVM const* discardFunction(YulString /*_type*/) const override { return builtin("pop"_yulstring); }
|
||||||
BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); }
|
BuiltinFunctionForEVM const* equalityFunction(YulString /*_type*/) const override { return builtin("eq"_yulstring); }
|
||||||
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); }
|
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); }
|
||||||
|
|
||||||
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
|
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
|
||||||
@ -102,6 +102,10 @@ struct EVMDialectTyped: public EVMDialect
|
|||||||
/// Constructor, should only be used internally. Use the factory function below.
|
/// Constructor, should only be used internally. Use the factory function below.
|
||||||
EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess);
|
EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess);
|
||||||
|
|
||||||
|
BuiltinFunctionForEVM const* discardFunction(YulString _type) const override;
|
||||||
|
BuiltinFunctionForEVM const* equalityFunction(YulString _type) const override;
|
||||||
|
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("not"_yulstring); }
|
||||||
|
|
||||||
static EVMDialectTyped const& instance(langutil::EVMVersion _version);
|
static EVMDialectTyped const& instance(langutil::EVMVersion _version);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1235,7 +1235,7 @@ Object EVMToEwasmTranslator::run(Object const& _object)
|
|||||||
MainFunction{}(ast);
|
MainFunction{}(ast);
|
||||||
ForLoopConditionIntoBody::run(context, ast);
|
ForLoopConditionIntoBody::run(context, ast);
|
||||||
ExpressionSplitter::run(context, ast);
|
ExpressionSplitter::run(context, ast);
|
||||||
WordSizeTransform::run(m_dialect, WasmDialect::instance().defaultType, ast, nameDispenser);
|
WordSizeTransform::run(m_dialect, WasmDialect::instance(), ast, nameDispenser);
|
||||||
|
|
||||||
NameDisplacer{nameDispenser, m_polyfillFunctions}(ast);
|
NameDisplacer{nameDispenser, m_polyfillFunctions}(ast);
|
||||||
for (auto const& st: m_polyfill->statements)
|
for (auto const& st: m_polyfill->statements)
|
||||||
@ -1251,8 +1251,12 @@ Object EVMToEwasmTranslator::run(Object const& _object)
|
|||||||
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames());
|
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, WasmDialect::instance(), {}, _object.dataNames());
|
||||||
if (!analyzer.analyze(*ret.code))
|
if (!analyzer.analyze(*ret.code))
|
||||||
{
|
{
|
||||||
// TODO the errors here are "wrong" because they have invalid source references!
|
string message = "Invalid code generated after EVM to wasm translation.\n";
|
||||||
string message;
|
message += "Note that the source locations in the errors below will reference the original, not the translated code.\n";
|
||||||
|
message += "Translated code:\n";
|
||||||
|
message += "----------------------------------\n";
|
||||||
|
message += ret.toString(&WasmDialect::instance());
|
||||||
|
message += "----------------------------------\n";
|
||||||
for (auto const& err: errors)
|
for (auto const& err: errors)
|
||||||
message += langutil::SourceReferenceFormatter::formatErrorInformation(*err);
|
message += langutil::SourceReferenceFormatter::formatErrorInformation(*err);
|
||||||
yulAssert(false, message);
|
yulAssert(false, message);
|
||||||
|
@ -91,13 +91,16 @@ WasmDialect::WasmDialect()
|
|||||||
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
||||||
|
|
||||||
// Drop is actually overloaded for all types, but Yul does not support that.
|
// Drop is actually overloaded for all types, but Yul does not support that.
|
||||||
// We could introduce "i32.drop".
|
// Because of that, we introduce "i32.drop".
|
||||||
addFunction("drop", {i64}, {});
|
addFunction("drop", {i64}, {});
|
||||||
|
addFunction("i32.drop", {i32}, {});
|
||||||
|
|
||||||
addFunction("nop", {}, {});
|
addFunction("nop", {}, {});
|
||||||
addFunction("unreachable", {}, {}, false);
|
addFunction("unreachable", {}, {}, false);
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
||||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
|
||||||
|
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
|
||||||
|
|
||||||
addFunction("datasize", {i64}, {i64}, true, true);
|
addFunction("datasize", {i64}, {i64}, true, true);
|
||||||
addFunction("dataoffset", {i64}, {i64}, true, true);
|
addFunction("dataoffset", {i64}, {i64}, true, true);
|
||||||
@ -114,6 +117,22 @@ BuiltinFunction const* WasmDialect::builtin(YulString _name) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuiltinFunction const* WasmDialect::discardFunction(YulString _type) const
|
||||||
|
{
|
||||||
|
if (_type == "i32"_yulstring)
|
||||||
|
return builtin("i32.drop"_yulstring);
|
||||||
|
yulAssert(_type == "i64"_yulstring, "");
|
||||||
|
return builtin("drop"_yulstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
BuiltinFunction const* WasmDialect::equalityFunction(YulString _type) const
|
||||||
|
{
|
||||||
|
if (_type == "i32"_yulstring)
|
||||||
|
return builtin("i32.eq"_yulstring);
|
||||||
|
yulAssert(_type == "i64"_yulstring, "");
|
||||||
|
return builtin("i64.eq"_yulstring);
|
||||||
|
}
|
||||||
|
|
||||||
WasmDialect const& WasmDialect::instance()
|
WasmDialect const& WasmDialect::instance()
|
||||||
{
|
{
|
||||||
static std::unique_ptr<WasmDialect> dialect;
|
static std::unique_ptr<WasmDialect> dialect;
|
||||||
@ -130,7 +149,13 @@ void WasmDialect::addEthereumExternals()
|
|||||||
static string const i64{"i64"};
|
static string const i64{"i64"};
|
||||||
static string const i32{"i32"};
|
static string const i32{"i32"};
|
||||||
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
||||||
struct External { string name; vector<string> parameters; vector<string> returns; };
|
struct External
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
vector<string> parameters;
|
||||||
|
vector<string> returns;
|
||||||
|
ControlFlowSideEffects controlFlowSideEffects = ControlFlowSideEffects{};
|
||||||
|
};
|
||||||
static vector<External> externals{
|
static vector<External> externals{
|
||||||
{"getAddress", {i32ptr}, {}},
|
{"getAddress", {i32ptr}, {}},
|
||||||
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
||||||
@ -158,11 +183,11 @@ void WasmDialect::addEthereumExternals()
|
|||||||
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||||
{"getBlockNumber", {}, {i64}},
|
{"getBlockNumber", {}, {i64}},
|
||||||
{"getTxOrigin", {i32ptr}, {}},
|
{"getTxOrigin", {i32ptr}, {}},
|
||||||
{"finish", {i32ptr, i32}, {}},
|
{"finish", {i32ptr, i32}, {}, ControlFlowSideEffects{true, false}},
|
||||||
{"revert", {i32ptr, i32}, {}},
|
{"revert", {i32ptr, i32}, {}, ControlFlowSideEffects{true, true}},
|
||||||
{"getReturnDataSize", {}, {i32}},
|
{"getReturnDataSize", {}, {i32}},
|
||||||
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||||
{"selfDestruct", {i32ptr}, {}},
|
{"selfDestruct", {i32ptr}, {}, ControlFlowSideEffects{true, false}},
|
||||||
{"getBlockTimestamp", {}, {i64}}
|
{"getBlockTimestamp", {}, {i64}}
|
||||||
};
|
};
|
||||||
for (External const& ext: externals)
|
for (External const& ext: externals)
|
||||||
@ -176,6 +201,7 @@ void WasmDialect::addEthereumExternals()
|
|||||||
f.returns.emplace_back(YulString(p));
|
f.returns.emplace_back(YulString(p));
|
||||||
// TODO some of them are side effect free.
|
// TODO some of them are side effect free.
|
||||||
f.sideEffects = SideEffects::worst();
|
f.sideEffects = SideEffects::worst();
|
||||||
|
f.controlFlowSideEffects = ext.controlFlowSideEffects;
|
||||||
f.isMSize = false;
|
f.isMSize = false;
|
||||||
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
||||||
f.literalArguments = false;
|
f.literalArguments = false;
|
||||||
|
@ -35,19 +35,19 @@ struct Object;
|
|||||||
/**
|
/**
|
||||||
* Yul dialect for Wasm as a backend.
|
* Yul dialect for Wasm as a backend.
|
||||||
*
|
*
|
||||||
* Builtin functions are a subset of the wasm instructions, always implicitly assuming
|
* Builtin functions are a subset of the wasm instructions.
|
||||||
* unsigned 64 bit types.
|
*
|
||||||
|
* There is a builtin function `i32.drop` that takes an i32, while `drop` takes i64.
|
||||||
*
|
*
|
||||||
* !This is subject to changes!
|
|
||||||
*/
|
*/
|
||||||
struct WasmDialect: public Dialect
|
struct WasmDialect: public Dialect
|
||||||
{
|
{
|
||||||
WasmDialect();
|
WasmDialect();
|
||||||
|
|
||||||
BuiltinFunction const* builtin(YulString _name) const override;
|
BuiltinFunction const* builtin(YulString _name) const override;
|
||||||
BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); }
|
BuiltinFunction const* discardFunction(YulString _type) const override;
|
||||||
BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); }
|
BuiltinFunction const* equalityFunction(YulString _type) const override;
|
||||||
BuiltinFunction const* booleanNegationFunction() const override { return builtin("i64.eqz"_yulstring); }
|
BuiltinFunction const* booleanNegationFunction() const override { return builtin("i32.eqz"_yulstring); }
|
||||||
|
|
||||||
std::set<YulString> fixedFunctionNames() const override { return {"main"_yulstring}; }
|
std::set<YulString> fixedFunctionNames() const override { return {"main"_yulstring}; }
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ void WordSizeTransform::operator()(FunctionCall& _fc)
|
|||||||
if (fun->literalArguments)
|
if (fun->literalArguments)
|
||||||
{
|
{
|
||||||
for (Expression& arg: _fc.arguments)
|
for (Expression& arg: _fc.arguments)
|
||||||
get<Literal>(arg).type = m_defaultType;
|
get<Literal>(arg).type = m_targetDialect.defaultType;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +106,17 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
for (int i = 0; i < 3; i++)
|
for (int i = 0; i < 3; i++)
|
||||||
ret.push_back(VariableDeclaration{
|
ret.push_back(VariableDeclaration{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{TypedName{varDecl.location, newLhs[i], m_defaultType}},
|
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||||
make_unique<Expression>(Literal{locationOf(*varDecl.value), LiteralKind::Number, "0"_yulstring, m_defaultType})
|
make_unique<Expression>(Literal{
|
||||||
|
locationOf(*varDecl.value),
|
||||||
|
LiteralKind::Number,
|
||||||
|
"0"_yulstring,
|
||||||
|
m_targetDialect.defaultType
|
||||||
|
})
|
||||||
});
|
});
|
||||||
ret.push_back(VariableDeclaration{
|
ret.push_back(VariableDeclaration{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{TypedName{varDecl.location, newLhs[3], m_defaultType}},
|
{TypedName{varDecl.location, newLhs[3], m_targetDialect.defaultType}},
|
||||||
std::move(varDecl.value)
|
std::move(varDecl.value)
|
||||||
});
|
});
|
||||||
return {std::move(ret)};
|
return {std::move(ret)};
|
||||||
@ -133,7 +138,7 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
ret.push_back(
|
ret.push_back(
|
||||||
VariableDeclaration{
|
VariableDeclaration{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{TypedName{varDecl.location, newLhs[i], m_defaultType}},
|
{TypedName{varDecl.location, newLhs[i], m_targetDialect.defaultType}},
|
||||||
std::move(newRhs[i])
|
std::move(newRhs[i])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -163,7 +168,12 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
ret.push_back(Assignment{
|
ret.push_back(Assignment{
|
||||||
assignment.location,
|
assignment.location,
|
||||||
{Identifier{assignment.location, newLhs[i]}},
|
{Identifier{assignment.location, newLhs[i]}},
|
||||||
make_unique<Expression>(Literal{locationOf(*assignment.value), LiteralKind::Number, "0"_yulstring, m_defaultType})
|
make_unique<Expression>(Literal{
|
||||||
|
locationOf(*assignment.value),
|
||||||
|
LiteralKind::Number,
|
||||||
|
"0"_yulstring,
|
||||||
|
m_targetDialect.defaultType
|
||||||
|
})
|
||||||
});
|
});
|
||||||
ret.push_back(Assignment{
|
ret.push_back(Assignment{
|
||||||
assignment.location,
|
assignment.location,
|
||||||
@ -209,14 +219,25 @@ void WordSizeTransform::operator()(Block& _block)
|
|||||||
|
|
||||||
void WordSizeTransform::run(
|
void WordSizeTransform::run(
|
||||||
Dialect const& _inputDialect,
|
Dialect const& _inputDialect,
|
||||||
YulString _targetDefaultType,
|
Dialect const& _targetDialect,
|
||||||
Block& _ast,
|
Block& _ast,
|
||||||
NameDispenser& _nameDispenser
|
NameDispenser& _nameDispenser
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Free the name `or_bool`.
|
// Free the name `or_bool`.
|
||||||
NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast);
|
NameDisplacer{_nameDispenser, {"or_bool"_yulstring}}(_ast);
|
||||||
WordSizeTransform{_inputDialect, _nameDispenser, _targetDefaultType}(_ast);
|
WordSizeTransform{_inputDialect, _targetDialect, _nameDispenser}(_ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
WordSizeTransform::WordSizeTransform(
|
||||||
|
Dialect const& _inputDialect,
|
||||||
|
Dialect const& _targetDialect,
|
||||||
|
NameDispenser& _nameDispenser
|
||||||
|
):
|
||||||
|
m_inputDialect(_inputDialect),
|
||||||
|
m_targetDialect(_targetDialect),
|
||||||
|
m_nameDispenser(_nameDispenser)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)
|
void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)
|
||||||
@ -227,7 +248,7 @@ void WordSizeTransform::rewriteVarDeclList(TypedNameList& _nameList)
|
|||||||
{
|
{
|
||||||
TypedNameList ret;
|
TypedNameList ret;
|
||||||
for (auto newName: generateU64IdentifierNames(_n.name))
|
for (auto newName: generateU64IdentifierNames(_n.name))
|
||||||
ret.emplace_back(TypedName{_n.location, newName, m_defaultType});
|
ret.emplace_back(TypedName{_n.location, newName, m_targetDialect.defaultType});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -291,7 +312,7 @@ vector<Statement> WordSizeTransform::handleSwitchInternal(
|
|||||||
|
|
||||||
for (auto& c: cases)
|
for (auto& c: cases)
|
||||||
{
|
{
|
||||||
Literal label{_location, LiteralKind::Number, YulString(c.first.str()), m_defaultType};
|
Literal label{_location, LiteralKind::Number, YulString(c.first.str()), m_targetDialect.defaultType};
|
||||||
ret.cases.emplace_back(Case{
|
ret.cases.emplace_back(Case{
|
||||||
c.second.front().location,
|
c.second.front().location,
|
||||||
make_unique<Literal>(std::move(label)),
|
make_unique<Literal>(std::move(label)),
|
||||||
@ -312,7 +333,7 @@ vector<Statement> WordSizeTransform::handleSwitchInternal(
|
|||||||
Assignment{
|
Assignment{
|
||||||
_location,
|
_location,
|
||||||
{{_location, _runDefaultFlag}},
|
{{_location, _runDefaultFlag}},
|
||||||
make_unique<Expression>(Literal{_location, LiteralKind::Number, "1"_yulstring, m_defaultType})
|
make_unique<Expression>(Literal{_location, LiteralKind::Boolean, "true"_yulstring, m_targetDialect.boolType})
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
});
|
});
|
||||||
@ -337,7 +358,7 @@ std::vector<Statement> WordSizeTransform::handleSwitch(Switch& _switch)
|
|||||||
_switch.cases.pop_back();
|
_switch.cases.pop_back();
|
||||||
ret.emplace_back(VariableDeclaration{
|
ret.emplace_back(VariableDeclaration{
|
||||||
_switch.location,
|
_switch.location,
|
||||||
{TypedName{_switch.location, runDefaultFlag, m_defaultType}},
|
{TypedName{_switch.location, runDefaultFlag, m_targetDialect.boolType}},
|
||||||
{}
|
{}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -392,7 +413,7 @@ array<unique_ptr<Expression>, 4> WordSizeTransform::expandValue(Expression const
|
|||||||
lit.location,
|
lit.location,
|
||||||
LiteralKind::Number,
|
LiteralKind::Number,
|
||||||
YulString(currentVal.str()),
|
YulString(currentVal.str()),
|
||||||
m_defaultType
|
m_targetDialect.defaultType
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ namespace solidity::yul
|
|||||||
* take four times the parameters and each of type u64.
|
* take four times the parameters and each of type u64.
|
||||||
* In addition, it uses a single other builtin function called `or_bool` that
|
* In addition, it uses a single other builtin function called `or_bool` that
|
||||||
* takes four u64 parameters and is supposed to return the logical disjunction
|
* takes four u64 parameters and is supposed to return the logical disjunction
|
||||||
* of them as a u64 value. If this name is already used somewhere, it is renamed.
|
* of them as a i32 value. If this name is already used somewhere, it is renamed.
|
||||||
*
|
*
|
||||||
* Prerequisite: Disambiguator, ForLoopConditionIntoBody, ExpressionSplitter
|
* Prerequisite: Disambiguator, ForLoopConditionIntoBody, ExpressionSplitter
|
||||||
*/
|
*/
|
||||||
@ -69,7 +69,7 @@ public:
|
|||||||
|
|
||||||
static void run(
|
static void run(
|
||||||
Dialect const& _inputDialect,
|
Dialect const& _inputDialect,
|
||||||
YulString _targetDefaultType,
|
Dialect const& _targetDialect,
|
||||||
Block& _ast,
|
Block& _ast,
|
||||||
NameDispenser& _nameDispenser
|
NameDispenser& _nameDispenser
|
||||||
);
|
);
|
||||||
@ -77,13 +77,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
explicit WordSizeTransform(
|
explicit WordSizeTransform(
|
||||||
Dialect const& _inputDialect,
|
Dialect const& _inputDialect,
|
||||||
NameDispenser& _nameDispenser,
|
Dialect const& _targetDialect,
|
||||||
YulString _defaultType
|
NameDispenser& _nameDispenser
|
||||||
):
|
);
|
||||||
m_inputDialect(_inputDialect),
|
|
||||||
m_nameDispenser(_nameDispenser),
|
|
||||||
m_defaultType(_defaultType)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
void rewriteVarDeclList(std::vector<TypedName>&);
|
void rewriteVarDeclList(std::vector<TypedName>&);
|
||||||
void rewriteIdentifierList(std::vector<Identifier>&);
|
void rewriteIdentifierList(std::vector<Identifier>&);
|
||||||
@ -103,8 +99,8 @@ private:
|
|||||||
std::vector<Expression> expandValueToVector(Expression const& _e);
|
std::vector<Expression> expandValueToVector(Expression const& _e);
|
||||||
|
|
||||||
Dialect const& m_inputDialect;
|
Dialect const& m_inputDialect;
|
||||||
|
Dialect const& m_targetDialect;
|
||||||
NameDispenser& m_nameDispenser;
|
NameDispenser& m_nameDispenser;
|
||||||
YulString m_defaultType;
|
|
||||||
/// maps original u256 variable's name to corresponding u64 variables' names
|
/// maps original u256 variable's name to corresponding u64 variables' names
|
||||||
std::map<YulString, std::array<YulString, 4>> m_variableMapping;
|
std::map<YulString, std::array<YulString, 4>> m_variableMapping;
|
||||||
};
|
};
|
||||||
|
@ -77,12 +77,7 @@ void ConditionalSimplifier::operator()(Block& _block)
|
|||||||
Assignment{
|
Assignment{
|
||||||
location,
|
location,
|
||||||
{Identifier{location, condition}},
|
{Identifier{location, condition}},
|
||||||
make_unique<Expression>(Literal{
|
make_unique<Expression>(m_dialect.zeroLiteralForType(m_dialect.boolType))
|
||||||
location,
|
|
||||||
LiteralKind::Number,
|
|
||||||
"0"_yulstring,
|
|
||||||
{}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
#include <libyul/optimiser/ControlFlowSimplifier.h>
|
#include <libyul/optimiser/ControlFlowSimplifier.h>
|
||||||
#include <libyul/optimiser/Semantics.h>
|
#include <libyul/optimiser/Semantics.h>
|
||||||
#include <libyul/optimiser/OptimiserStep.h>
|
#include <libyul/optimiser/OptimiserStep.h>
|
||||||
|
#include <libyul/optimiser/TypeInfo.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/Utilities.h>
|
#include <libyul/Utilities.h>
|
||||||
#include <libyul/Dialect.h>
|
#include <libyul/Dialect.h>
|
||||||
@ -38,14 +39,13 @@ namespace
|
|||||||
|
|
||||||
ExpressionStatement makeDiscardCall(
|
ExpressionStatement makeDiscardCall(
|
||||||
langutil::SourceLocation const& _location,
|
langutil::SourceLocation const& _location,
|
||||||
Dialect const& _dialect,
|
BuiltinFunction const& _discardFunction,
|
||||||
Expression&& _expression
|
Expression&& _expression
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
yulAssert(_dialect.discardFunction(), "No discard function available.");
|
|
||||||
return {_location, FunctionCall{
|
return {_location, FunctionCall{
|
||||||
_location,
|
_location,
|
||||||
Identifier{_location, _dialect.discardFunction()->name},
|
Identifier{_location, _discardFunction.name},
|
||||||
{std::move(_expression)}
|
{std::move(_expression)}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
@ -74,62 +74,12 @@ void removeEmptyCasesFromSwitch(Switch& _switchStmt)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionalStatements reduceNoCaseSwitch(Dialect const& _dialect, Switch& _switchStmt)
|
|
||||||
{
|
|
||||||
yulAssert(_switchStmt.cases.empty(), "Expected no case!");
|
|
||||||
if (!_dialect.discardFunction())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
auto loc = locationOf(*_switchStmt.expression);
|
|
||||||
|
|
||||||
return make_vector<Statement>(makeDiscardCall(
|
|
||||||
loc,
|
|
||||||
_dialect,
|
|
||||||
std::move(*_switchStmt.expression)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _switchStmt)
|
|
||||||
{
|
|
||||||
yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
|
|
||||||
|
|
||||||
auto& switchCase = _switchStmt.cases.front();
|
|
||||||
auto loc = locationOf(*_switchStmt.expression);
|
|
||||||
if (switchCase.value)
|
|
||||||
{
|
|
||||||
if (!_dialect.equalityFunction())
|
|
||||||
return {};
|
|
||||||
return make_vector<Statement>(If{
|
|
||||||
std::move(_switchStmt.location),
|
|
||||||
make_unique<Expression>(FunctionCall{
|
|
||||||
loc,
|
|
||||||
Identifier{loc, _dialect.equalityFunction()->name},
|
|
||||||
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
|
|
||||||
}),
|
|
||||||
std::move(switchCase.body)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!_dialect.discardFunction())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return make_vector<Statement>(
|
|
||||||
makeDiscardCall(
|
|
||||||
loc,
|
|
||||||
_dialect,
|
|
||||||
std::move(*_switchStmt.expression)
|
|
||||||
),
|
|
||||||
std::move(switchCase.body)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
||||||
{
|
{
|
||||||
ControlFlowSimplifier{_context.dialect}(_ast);
|
TypeInfo typeInfo(_context.dialect, _ast);
|
||||||
|
ControlFlowSimplifier{_context.dialect, typeInfo}(_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ControlFlowSimplifier::operator()(Block& _block)
|
void ControlFlowSimplifier::operator()(Block& _block)
|
||||||
@ -194,12 +144,12 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
|||||||
GenericVisitor visitor{
|
GenericVisitor visitor{
|
||||||
VisitorFallback<OptionalStatements>{},
|
VisitorFallback<OptionalStatements>{},
|
||||||
[&](If& _ifStmt) -> OptionalStatements {
|
[&](If& _ifStmt) -> OptionalStatements {
|
||||||
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction())
|
if (_ifStmt.body.statements.empty() && m_dialect.discardFunction(m_dialect.boolType))
|
||||||
{
|
{
|
||||||
OptionalStatements s = vector<Statement>{};
|
OptionalStatements s = vector<Statement>{};
|
||||||
s->emplace_back(makeDiscardCall(
|
s->emplace_back(makeDiscardCall(
|
||||||
_ifStmt.location,
|
_ifStmt.location,
|
||||||
m_dialect,
|
*m_dialect.discardFunction(m_dialect.boolType),
|
||||||
std::move(*_ifStmt.condition)
|
std::move(*_ifStmt.condition)
|
||||||
));
|
));
|
||||||
return s;
|
return s;
|
||||||
@ -211,9 +161,9 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
|||||||
removeEmptyCasesFromSwitch(_switchStmt);
|
removeEmptyCasesFromSwitch(_switchStmt);
|
||||||
|
|
||||||
if (_switchStmt.cases.empty())
|
if (_switchStmt.cases.empty())
|
||||||
return reduceNoCaseSwitch(m_dialect, _switchStmt);
|
return reduceNoCaseSwitch(_switchStmt);
|
||||||
else if (_switchStmt.cases.size() == 1)
|
else if (_switchStmt.cases.size() == 1)
|
||||||
return reduceSingleCaseSwitch(m_dialect, _switchStmt);
|
return reduceSingleCaseSwitch(_switchStmt);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -231,3 +181,58 @@ void ControlFlowSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OptionalStatements ControlFlowSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const
|
||||||
|
{
|
||||||
|
yulAssert(_switchStmt.cases.empty(), "Expected no case!");
|
||||||
|
BuiltinFunction const* discardFunction =
|
||||||
|
m_dialect.discardFunction(m_typeInfo.typeOf(*_switchStmt.expression));
|
||||||
|
if (!discardFunction)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto loc = locationOf(*_switchStmt.expression);
|
||||||
|
|
||||||
|
return make_vector<Statement>(makeDiscardCall(
|
||||||
|
loc,
|
||||||
|
*discardFunction,
|
||||||
|
std::move(*_switchStmt.expression)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionalStatements ControlFlowSimplifier::reduceSingleCaseSwitch(Switch& _switchStmt) const
|
||||||
|
{
|
||||||
|
yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
|
||||||
|
|
||||||
|
auto& switchCase = _switchStmt.cases.front();
|
||||||
|
auto loc = locationOf(*_switchStmt.expression);
|
||||||
|
YulString type = m_typeInfo.typeOf(*_switchStmt.expression);
|
||||||
|
if (switchCase.value)
|
||||||
|
{
|
||||||
|
if (!m_dialect.equalityFunction(type))
|
||||||
|
return {};
|
||||||
|
return make_vector<Statement>(If{
|
||||||
|
std::move(_switchStmt.location),
|
||||||
|
make_unique<Expression>(FunctionCall{
|
||||||
|
loc,
|
||||||
|
Identifier{loc, m_dialect.equalityFunction(type)->name},
|
||||||
|
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
|
||||||
|
}),
|
||||||
|
std::move(switchCase.body)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!m_dialect.discardFunction(type))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return make_vector<Statement>(
|
||||||
|
makeDiscardCall(
|
||||||
|
loc,
|
||||||
|
*m_dialect.discardFunction(type),
|
||||||
|
std::move(*_switchStmt.expression)
|
||||||
|
),
|
||||||
|
std::move(switchCase.body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ namespace solidity::yul
|
|||||||
{
|
{
|
||||||
struct Dialect;
|
struct Dialect;
|
||||||
struct OptimiserStepContext;
|
struct OptimiserStepContext;
|
||||||
|
class TypeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simplifies several control-flow structures:
|
* Simplifies several control-flow structures:
|
||||||
@ -61,11 +62,18 @@ public:
|
|||||||
void visit(Statement& _st) override;
|
void visit(Statement& _st) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {}
|
ControlFlowSimplifier(Dialect const& _dialect, TypeInfo const& _typeInfo):
|
||||||
|
m_dialect(_dialect),
|
||||||
|
m_typeInfo(_typeInfo)
|
||||||
|
{}
|
||||||
|
|
||||||
void simplify(std::vector<Statement>& _statements);
|
void simplify(std::vector<Statement>& _statements);
|
||||||
|
|
||||||
|
std::optional<std::vector<Statement>> reduceNoCaseSwitch(Switch& _switchStmt) const;
|
||||||
|
std::optional<std::vector<Statement>> reduceSingleCaseSwitch(Switch& _switchStmt) const;
|
||||||
|
|
||||||
Dialect const& m_dialect;
|
Dialect const& m_dialect;
|
||||||
|
TypeInfo const& m_typeInfo;
|
||||||
size_t m_numBreakStatements = 0;
|
size_t m_numBreakStatements = 0;
|
||||||
size_t m_numContinueStatements = 0;
|
size_t m_numContinueStatements = 0;
|
||||||
};
|
};
|
||||||
|
@ -23,11 +23,13 @@
|
|||||||
|
|
||||||
#include <libyul/optimiser/ASTWalker.h>
|
#include <libyul/optimiser/ASTWalker.h>
|
||||||
#include <libyul/optimiser/OptimiserStep.h>
|
#include <libyul/optimiser/OptimiserStep.h>
|
||||||
|
#include <libyul/optimiser/TypeInfo.h>
|
||||||
|
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
#include <libyul/Dialect.h>
|
#include <libyul/Dialect.h>
|
||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
|
|
||||||
@ -39,7 +41,8 @@ using namespace solidity::langutil;
|
|||||||
|
|
||||||
void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast)
|
void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast)
|
||||||
{
|
{
|
||||||
ExpressionSplitter{_context.dialect, _context.dispenser}(_ast);
|
TypeInfo typeInfo(_context.dialect, _ast);
|
||||||
|
ExpressionSplitter{_context.dialect, _context.dispenser, typeInfo}(_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExpressionSplitter::operator()(FunctionCall& _funCall)
|
void ExpressionSplitter::operator()(FunctionCall& _funCall)
|
||||||
@ -103,10 +106,13 @@ void ExpressionSplitter::outlineExpression(Expression& _expr)
|
|||||||
|
|
||||||
SourceLocation location = locationOf(_expr);
|
SourceLocation location = locationOf(_expr);
|
||||||
YulString var = m_nameDispenser.newName({});
|
YulString var = m_nameDispenser.newName({});
|
||||||
|
YulString type = m_typeInfo.typeOf(_expr);
|
||||||
m_statementsToPrefix.emplace_back(VariableDeclaration{
|
m_statementsToPrefix.emplace_back(VariableDeclaration{
|
||||||
location,
|
location,
|
||||||
{{TypedName{location, var, {}}}},
|
{{TypedName{location, var, type}}},
|
||||||
make_unique<Expression>(std::move(_expr))
|
make_unique<Expression>(std::move(_expr))
|
||||||
});
|
});
|
||||||
_expr = Identifier{location, var};
|
_expr = Identifier{location, var};
|
||||||
|
m_typeInfo.setVariableType(var, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
namespace solidity::yul
|
namespace solidity::yul
|
||||||
{
|
{
|
||||||
|
|
||||||
class NameCollector;
|
|
||||||
struct Dialect;
|
struct Dialect;
|
||||||
struct OptimiserStepContext;
|
struct OptimiserStepContext;
|
||||||
|
class TypeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimiser component that modifies an AST in place, turning complex
|
* Optimiser component that modifies an AST in place, turning complex
|
||||||
@ -68,8 +68,14 @@ public:
|
|||||||
void operator()(Block& _block) override;
|
void operator()(Block& _block) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ExpressionSplitter(Dialect const& _dialect, NameDispenser& _nameDispenser):
|
explicit ExpressionSplitter(
|
||||||
m_dialect(_dialect), m_nameDispenser(_nameDispenser)
|
Dialect const& _dialect,
|
||||||
|
NameDispenser& _nameDispenser,
|
||||||
|
TypeInfo& _typeInfo
|
||||||
|
):
|
||||||
|
m_dialect(_dialect),
|
||||||
|
m_nameDispenser(_nameDispenser),
|
||||||
|
m_typeInfo(_typeInfo)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// Replaces the expression by a variable if it is a function call or functional
|
/// Replaces the expression by a variable if it is a function call or functional
|
||||||
@ -82,6 +88,7 @@ private:
|
|||||||
std::vector<Statement> m_statementsToPrefix;
|
std::vector<Statement> m_statementsToPrefix;
|
||||||
Dialect const& m_dialect;
|
Dialect const& m_dialect;
|
||||||
NameDispenser& m_nameDispenser;
|
NameDispenser& m_nameDispenser;
|
||||||
|
TypeInfo& m_typeInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,9 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop)
|
|||||||
_forLoop.condition = make_unique<Expression>(
|
_forLoop.condition = make_unique<Expression>(
|
||||||
Literal {
|
Literal {
|
||||||
loc,
|
loc,
|
||||||
LiteralKind::Number,
|
LiteralKind::Boolean,
|
||||||
"1"_yulstring,
|
"true"_yulstring,
|
||||||
{}
|
m_dialect.boolType
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include <libyul/optimiser/Semantics.h>
|
#include <libyul/optimiser/Semantics.h>
|
||||||
#include <libyul/Exceptions.h>
|
#include <libyul/Exceptions.h>
|
||||||
#include <libyul/AsmData.h>
|
#include <libyul/AsmData.h>
|
||||||
|
#include <libyul/Dialect.h>
|
||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
#include <libsolutil/Visitor.h>
|
#include <libsolutil/Visitor.h>
|
||||||
@ -41,11 +42,11 @@ using namespace solidity::yul;
|
|||||||
|
|
||||||
void FullInliner::run(OptimiserStepContext& _context, Block& _ast)
|
void FullInliner::run(OptimiserStepContext& _context, Block& _ast)
|
||||||
{
|
{
|
||||||
FullInliner{_ast, _context.dispenser}.run();
|
FullInliner{_ast, _context.dispenser, _context.dialect}.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser):
|
FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect):
|
||||||
m_ast(_ast), m_nameDispenser(_dispenser)
|
m_ast(_ast), m_nameDispenser(_dispenser), m_dialect(_dialect)
|
||||||
{
|
{
|
||||||
// Determine constants
|
// Determine constants
|
||||||
SSAValueTracker tracker;
|
SSAValueTracker tracker;
|
||||||
@ -139,7 +140,7 @@ void FullInliner::updateCodeSize(FunctionDefinition const& _fun)
|
|||||||
|
|
||||||
void FullInliner::handleBlock(YulString _currentFunctionName, Block& _block)
|
void FullInliner::handleBlock(YulString _currentFunctionName, Block& _block)
|
||||||
{
|
{
|
||||||
InlineModifier{*this, m_nameDispenser, _currentFunctionName}(_block);
|
InlineModifier{*this, m_nameDispenser, _currentFunctionName, m_dialect}(_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FullInliner::recursive(FunctionDefinition const& _fun) const
|
bool FullInliner::recursive(FunctionDefinition const& _fun) const
|
||||||
@ -198,7 +199,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC
|
|||||||
if (_value)
|
if (_value)
|
||||||
varDecl.value = make_unique<Expression>(std::move(*_value));
|
varDecl.value = make_unique<Expression>(std::move(*_value));
|
||||||
else
|
else
|
||||||
varDecl.value = make_unique<Expression>(Literal{{}, LiteralKind::Number, YulString{"0"}, {}});
|
varDecl.value = make_unique<Expression>(m_dialect.zeroLiteralForType(varDecl.variables.front().type));
|
||||||
newStatements.emplace_back(std::move(varDecl));
|
newStatements.emplace_back(std::move(varDecl));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class FullInliner: public ASTModifier
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static constexpr char const* name{"FullInliner"};
|
static constexpr char const* name{"FullInliner"};
|
||||||
static void run(OptimiserStepContext&, Block& _ast);
|
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||||
|
|
||||||
/// Inlining heuristic.
|
/// Inlining heuristic.
|
||||||
/// @param _callSite the name of the function in which the function call is located.
|
/// @param _callSite the name of the function in which the function call is located.
|
||||||
@ -89,7 +89,7 @@ public:
|
|||||||
void tentativelyUpdateCodeSize(YulString _function, YulString _callSite);
|
void tentativelyUpdateCodeSize(YulString _function, YulString _callSite);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FullInliner(Block& _ast, NameDispenser& _dispenser);
|
FullInliner(Block& _ast, NameDispenser& _dispenser, Dialect const& _dialect);
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
void updateCodeSize(FunctionDefinition const& _fun);
|
void updateCodeSize(FunctionDefinition const& _fun);
|
||||||
@ -108,6 +108,7 @@ private:
|
|||||||
std::set<YulString> m_constants;
|
std::set<YulString> m_constants;
|
||||||
std::map<YulString, size_t> m_functionSizes;
|
std::map<YulString, size_t> m_functionSizes;
|
||||||
NameDispenser& m_nameDispenser;
|
NameDispenser& m_nameDispenser;
|
||||||
|
Dialect const& m_dialect;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,10 +118,11 @@ private:
|
|||||||
class InlineModifier: public ASTModifier
|
class InlineModifier: public ASTModifier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, YulString _functionName):
|
InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, YulString _functionName, Dialect const& _dialect):
|
||||||
m_currentFunction(std::move(_functionName)),
|
m_currentFunction(std::move(_functionName)),
|
||||||
m_driver(_driver),
|
m_driver(_driver),
|
||||||
m_nameDispenser(_nameDispenser)
|
m_nameDispenser(_nameDispenser),
|
||||||
|
m_dialect(_dialect)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void operator()(Block& _block) override;
|
void operator()(Block& _block) override;
|
||||||
@ -132,6 +134,7 @@ private:
|
|||||||
YulString m_currentFunction;
|
YulString m_currentFunction;
|
||||||
FullInliner& m_driver;
|
FullInliner& m_driver;
|
||||||
NameDispenser& m_nameDispenser;
|
NameDispenser& m_nameDispenser;
|
||||||
|
Dialect const& m_dialect;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
|
|
||||||
|
#include <libyul/optimiser/TypeInfo.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
using namespace solidity::yul;
|
using namespace solidity::yul;
|
||||||
@ -42,8 +44,14 @@ namespace
|
|||||||
class IntroduceSSA: public ASTModifier
|
class IntroduceSSA: public ASTModifier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit IntroduceSSA(NameDispenser& _nameDispenser, set<YulString> const& _variablesToReplace):
|
explicit IntroduceSSA(
|
||||||
m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace)
|
NameDispenser& _nameDispenser,
|
||||||
|
set<YulString> const& _variablesToReplace,
|
||||||
|
TypeInfo& _typeInfo
|
||||||
|
):
|
||||||
|
m_nameDispenser(_nameDispenser),
|
||||||
|
m_variablesToReplace(_variablesToReplace),
|
||||||
|
m_typeInfo(_typeInfo)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void operator()(Block& _block) override;
|
void operator()(Block& _block) override;
|
||||||
@ -51,6 +59,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
NameDispenser& m_nameDispenser;
|
NameDispenser& m_nameDispenser;
|
||||||
set<YulString> const& m_variablesToReplace;
|
set<YulString> const& m_variablesToReplace;
|
||||||
|
TypeInfo const& m_typeInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -83,10 +92,10 @@ void IntroduceSSA::operator()(Block& _block)
|
|||||||
{
|
{
|
||||||
YulString oldName = var.name;
|
YulString oldName = var.name;
|
||||||
YulString newName = m_nameDispenser.newName(oldName);
|
YulString newName = m_nameDispenser.newName(oldName);
|
||||||
newVariables.emplace_back(TypedName{loc, newName, {}});
|
newVariables.emplace_back(TypedName{loc, newName, var.type});
|
||||||
statements.emplace_back(VariableDeclaration{
|
statements.emplace_back(VariableDeclaration{
|
||||||
loc,
|
loc,
|
||||||
{TypedName{loc, oldName, {}}},
|
{TypedName{loc, oldName, var.type}},
|
||||||
make_unique<Expression>(Identifier{loc, newName})
|
make_unique<Expression>(Identifier{loc, newName})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -110,7 +119,11 @@ void IntroduceSSA::operator()(Block& _block)
|
|||||||
{
|
{
|
||||||
YulString oldName = var.name;
|
YulString oldName = var.name;
|
||||||
YulString newName = m_nameDispenser.newName(oldName);
|
YulString newName = m_nameDispenser.newName(oldName);
|
||||||
newVariables.emplace_back(TypedName{loc, newName, {}});
|
newVariables.emplace_back(TypedName{
|
||||||
|
loc,
|
||||||
|
newName,
|
||||||
|
m_typeInfo.typeOfVariable(oldName)
|
||||||
|
});
|
||||||
statements.emplace_back(Assignment{
|
statements.emplace_back(Assignment{
|
||||||
loc,
|
loc,
|
||||||
{Identifier{loc, oldName}},
|
{Identifier{loc, oldName}},
|
||||||
@ -136,9 +149,12 @@ class IntroduceControlFlowSSA: public ASTModifier
|
|||||||
public:
|
public:
|
||||||
explicit IntroduceControlFlowSSA(
|
explicit IntroduceControlFlowSSA(
|
||||||
NameDispenser& _nameDispenser,
|
NameDispenser& _nameDispenser,
|
||||||
set<YulString> const& _variablesToReplace
|
set<YulString> const& _variablesToReplace,
|
||||||
|
TypeInfo const& _typeInfo
|
||||||
):
|
):
|
||||||
m_nameDispenser(_nameDispenser), m_variablesToReplace(_variablesToReplace)
|
m_nameDispenser(_nameDispenser),
|
||||||
|
m_variablesToReplace(_variablesToReplace),
|
||||||
|
m_typeInfo(_typeInfo)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void operator()(FunctionDefinition& _function) override;
|
void operator()(FunctionDefinition& _function) override;
|
||||||
@ -153,6 +169,7 @@ private:
|
|||||||
set<YulString> m_variablesInScope;
|
set<YulString> m_variablesInScope;
|
||||||
/// Set of variables that do not have a specific value.
|
/// Set of variables that do not have a specific value.
|
||||||
set<YulString> m_variablesToReassign;
|
set<YulString> m_variablesToReassign;
|
||||||
|
TypeInfo const& m_typeInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
|
void IntroduceControlFlowSSA::operator()(FunctionDefinition& _function)
|
||||||
@ -221,7 +238,7 @@ void IntroduceControlFlowSSA::operator()(Block& _block)
|
|||||||
YulString newName = m_nameDispenser.newName(toReassign);
|
YulString newName = m_nameDispenser.newName(toReassign);
|
||||||
toPrepend.emplace_back(VariableDeclaration{
|
toPrepend.emplace_back(VariableDeclaration{
|
||||||
locationOf(_s),
|
locationOf(_s),
|
||||||
{TypedName{locationOf(_s), newName, {}}},
|
{TypedName{locationOf(_s), newName, m_typeInfo.typeOfVariable(toReassign)}},
|
||||||
make_unique<Expression>(Identifier{locationOf(_s), toReassign})
|
make_unique<Expression>(Identifier{locationOf(_s), toReassign})
|
||||||
});
|
});
|
||||||
assignedVariables.insert(toReassign);
|
assignedVariables.insert(toReassign);
|
||||||
@ -375,10 +392,11 @@ void PropagateValues::operator()(Block& _block)
|
|||||||
|
|
||||||
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
|
void SSATransform::run(OptimiserStepContext& _context, Block& _ast)
|
||||||
{
|
{
|
||||||
|
TypeInfo typeInfo(_context.dialect, _ast);
|
||||||
Assignments assignments;
|
Assignments assignments;
|
||||||
assignments(_ast);
|
assignments(_ast);
|
||||||
IntroduceSSA{_context.dispenser, assignments.names()}(_ast);
|
IntroduceSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
|
||||||
IntroduceControlFlowSSA{_context.dispenser, assignments.names()}(_ast);
|
IntroduceControlFlowSSA{_context.dispenser, assignments.names(), typeInfo}(_ast);
|
||||||
PropagateValues{assignments.names()}(_ast);
|
PropagateValues{assignments.names()}(_ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
103
libyul/optimiser/TypeInfo.cpp
Normal file
103
libyul/optimiser/TypeInfo.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Helper class that keeps track of the types while performing optimizations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <libyul/optimiser/TypeInfo.h>
|
||||||
|
|
||||||
|
#include <libyul/optimiser/NameCollector.h>
|
||||||
|
|
||||||
|
#include <libyul/AsmData.h>
|
||||||
|
#include <libyul/Dialect.h>
|
||||||
|
|
||||||
|
#include <libsolutil/Visitor.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace solidity::yul;
|
||||||
|
using namespace solidity::util;
|
||||||
|
|
||||||
|
class TypeInfo::TypeCollector: public ASTWalker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit TypeCollector(Block const& _block)
|
||||||
|
{
|
||||||
|
(*this)(_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
using ASTWalker::operator();
|
||||||
|
void operator()(VariableDeclaration const& _varDecl) override
|
||||||
|
{
|
||||||
|
for (auto const& var: _varDecl.variables)
|
||||||
|
variableTypes[var.name] = var.type;
|
||||||
|
}
|
||||||
|
void operator()(FunctionDefinition const& _funDef) override
|
||||||
|
{
|
||||||
|
ASTWalker::operator()(_funDef);
|
||||||
|
|
||||||
|
auto& funType = functionTypes[_funDef.name];
|
||||||
|
for (auto const arg: _funDef.parameters)
|
||||||
|
{
|
||||||
|
funType.parameters.emplace_back(arg.type);
|
||||||
|
variableTypes[arg.name] = arg.type;
|
||||||
|
}
|
||||||
|
for (auto const ret: _funDef.returnVariables)
|
||||||
|
{
|
||||||
|
funType.returns.emplace_back(ret.type);
|
||||||
|
variableTypes[ret.name] = ret.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<YulString, YulString> variableTypes;
|
||||||
|
std::map<YulString, FunctionType> functionTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TypeInfo::TypeInfo(Dialect const& _dialect, Block const& _ast):
|
||||||
|
m_dialect(_dialect)
|
||||||
|
{
|
||||||
|
TypeCollector types(_ast);
|
||||||
|
m_functionTypes = std::move(types.functionTypes);
|
||||||
|
m_variableTypes = std::move(types.variableTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
YulString TypeInfo::typeOf(Expression const& _expression) const
|
||||||
|
{
|
||||||
|
return std::visit(GenericVisitor{
|
||||||
|
[&](FunctionCall const& _funCall) {
|
||||||
|
YulString name = _funCall.functionName.name;
|
||||||
|
vector<YulString> const* retTypes = nullptr;
|
||||||
|
if (BuiltinFunction const* fun = m_dialect.builtin(name))
|
||||||
|
retTypes = &fun->returns;
|
||||||
|
else
|
||||||
|
retTypes = &m_functionTypes.at(name).returns;
|
||||||
|
yulAssert(retTypes && retTypes->size() == 1, "Call to typeOf for non-single-value expression.");
|
||||||
|
return retTypes->front();
|
||||||
|
},
|
||||||
|
[&](Identifier const& _identifier) {
|
||||||
|
return m_variableTypes.at(_identifier.name);
|
||||||
|
},
|
||||||
|
[&](Literal const& _literal) {
|
||||||
|
return _literal.type;
|
||||||
|
}
|
||||||
|
}, _expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
YulString TypeInfo::typeOfVariable(YulString _name) const
|
||||||
|
{
|
||||||
|
return m_variableTypes.at(_name);
|
||||||
|
}
|
64
libyul/optimiser/TypeInfo.h
Normal file
64
libyul/optimiser/TypeInfo.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Helper class that keeps track of the types while performing optimizations.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libyul/AsmDataForward.h>
|
||||||
|
#include <libyul/YulString.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
namespace solidity::yul
|
||||||
|
{
|
||||||
|
struct Dialect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class that keeps track of the types while performing optimizations.
|
||||||
|
*
|
||||||
|
* Only works on disambiguated sources!
|
||||||
|
*/
|
||||||
|
class TypeInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TypeInfo(Dialect const& _dialect, Block const& _ast);
|
||||||
|
|
||||||
|
void setVariableType(YulString _name, YulString _type) { m_variableTypes[_name] = _type; }
|
||||||
|
|
||||||
|
/// @returns the type of an expression that is assumed to return exactly one value.
|
||||||
|
YulString typeOf(Expression const& _expression) const;
|
||||||
|
|
||||||
|
/// \returns the type of variable
|
||||||
|
YulString typeOfVariable(YulString _name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class TypeCollector;
|
||||||
|
|
||||||
|
struct FunctionType
|
||||||
|
{
|
||||||
|
std::vector<YulString> parameters;
|
||||||
|
std::vector<YulString> returns;
|
||||||
|
};
|
||||||
|
|
||||||
|
Dialect const& m_dialect;
|
||||||
|
std::map<YulString, YulString> m_variableTypes;
|
||||||
|
std::map<YulString, FunctionType> m_functionTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -100,10 +100,10 @@ void UnusedPruner::operator()(Block& _block)
|
|||||||
subtractReferences(ReferencesCounter::countReferences(*varDecl.value));
|
subtractReferences(ReferencesCounter::countReferences(*varDecl.value));
|
||||||
statement = Block{std::move(varDecl.location), {}};
|
statement = Block{std::move(varDecl.location), {}};
|
||||||
}
|
}
|
||||||
else if (varDecl.variables.size() == 1 && m_dialect.discardFunction())
|
else if (varDecl.variables.size() == 1 && m_dialect.discardFunction(varDecl.variables.front().type))
|
||||||
statement = ExpressionStatement{varDecl.location, FunctionCall{
|
statement = ExpressionStatement{varDecl.location, FunctionCall{
|
||||||
varDecl.location,
|
varDecl.location,
|
||||||
{varDecl.location, m_dialect.discardFunction()->name},
|
{varDecl.location, m_dialect.discardFunction(varDecl.variables.front().type)->name},
|
||||||
{*std::move(varDecl.value)}
|
{*std::move(varDecl.value)}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <libsolutil/CommonData.h>
|
#include <libsolutil/CommonData.h>
|
||||||
#include <libsolutil/Visitor.h>
|
#include <libsolutil/Visitor.h>
|
||||||
|
#include <libyul/Dialect.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity;
|
using namespace solidity;
|
||||||
@ -32,14 +33,14 @@ void VarDeclInitializer::operator()(Block& _block)
|
|||||||
using OptionalStatements = std::optional<vector<Statement>>;
|
using OptionalStatements = std::optional<vector<Statement>>;
|
||||||
util::GenericVisitor visitor{
|
util::GenericVisitor visitor{
|
||||||
util::VisitorFallback<OptionalStatements>{},
|
util::VisitorFallback<OptionalStatements>{},
|
||||||
[](VariableDeclaration& _varDecl) -> OptionalStatements
|
[this](VariableDeclaration& _varDecl) -> OptionalStatements
|
||||||
{
|
{
|
||||||
if (_varDecl.value)
|
if (_varDecl.value)
|
||||||
return {};
|
return {};
|
||||||
Literal zero{{}, LiteralKind::Number, YulString{"0"}, {}};
|
|
||||||
if (_varDecl.variables.size() == 1)
|
if (_varDecl.variables.size() == 1)
|
||||||
{
|
{
|
||||||
_varDecl.value = make_unique<Expression>(std::move(zero));
|
_varDecl.value = make_unique<Expression>(m_dialect.zeroLiteralForType(_varDecl.variables.front().type));
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -47,7 +48,10 @@ void VarDeclInitializer::operator()(Block& _block)
|
|||||||
OptionalStatements ret{vector<Statement>{}};
|
OptionalStatements ret{vector<Statement>{}};
|
||||||
langutil::SourceLocation loc{std::move(_varDecl.location)};
|
langutil::SourceLocation loc{std::move(_varDecl.location)};
|
||||||
for (auto& var: _varDecl.variables)
|
for (auto& var: _varDecl.variables)
|
||||||
ret->emplace_back(VariableDeclaration{loc, {std::move(var)}, make_unique<Expression>(zero)});
|
{
|
||||||
|
unique_ptr<Expression> expr = make_unique<Expression >(m_dialect.zeroLiteralForType(var.type));
|
||||||
|
ret->emplace_back(VariableDeclaration{loc, {std::move(var)}, std::move(expr)});
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,14 @@ class VarDeclInitializer: public ASTModifier
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static constexpr char const* name{"VarDeclInitializer"};
|
static constexpr char const* name{"VarDeclInitializer"};
|
||||||
static void run(OptimiserStepContext&, Block& _ast) { VarDeclInitializer{}(_ast); }
|
static void run(OptimiserStepContext& _ctx, Block& _ast) { VarDeclInitializer{_ctx.dialect}(_ast); }
|
||||||
|
|
||||||
void operator()(Block& _block) override;
|
void operator()(Block& _block) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit VarDeclInitializer(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||||
|
|
||||||
|
Dialect const& m_dialect;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ SOLC=${REPO_ROOT}/${SOLIDITY_BUILD_DIR}/solc/solc
|
|||||||
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
|
SPLITSOURCES=${REPO_ROOT}/scripts/splitSources.py
|
||||||
|
|
||||||
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
|
SYNTAXTESTS_DIR="${REPO_ROOT}/test/libsolidity/syntaxTests"
|
||||||
|
ASTJSONTESTS_DIR="${REPO_ROOT}/test/libsolidity/ASTJSON"
|
||||||
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
|
NSOURCES="$(find $SYNTAXTESTS_DIR -type f | wc -l)"
|
||||||
|
|
||||||
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
|
# DEV_DIR="${REPO_ROOT}/../tmp/contracts/"
|
||||||
@ -75,7 +76,7 @@ echo "Looking at $NSOURCES .sol files..."
|
|||||||
WORKINGDIR=$PWD
|
WORKINGDIR=$PWD
|
||||||
|
|
||||||
# for solfile in $(find $DEV_DIR -name *.sol)
|
# for solfile in $(find $DEV_DIR -name *.sol)
|
||||||
for solfile in $(find $SYNTAXTESTS_DIR -name *.sol)
|
for solfile in $(find $SYNTAXTESTS_DIR $ASTJSONTESTS_DIR -name *.sol)
|
||||||
do
|
do
|
||||||
echo -n "."
|
echo -n "."
|
||||||
# create a temporary sub-directory
|
# create a temporary sub-directory
|
||||||
|
@ -126,7 +126,7 @@ EOF
|
|||||||
git config user.email "chris@ethereum.org"
|
git config user.email "chris@ethereum.org"
|
||||||
git clean -f -d -x
|
git clean -f -d -x
|
||||||
|
|
||||||
DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date=short)
|
DIRNAME=$(cd "$REPO_ROOT" && git show -s --format="%cd-%H" --date="format:%Y-%m-%d-%H-%M")
|
||||||
mkdir -p "$DIRNAME"
|
mkdir -p "$DIRNAME"
|
||||||
REPORT="$DIRNAME/$ZIP_SUFFIX.txt"
|
REPORT="$DIRNAME/$ZIP_SUFFIX.txt"
|
||||||
cp ../report.txt "$REPORT"
|
cp ../report.txt "$REPORT"
|
||||||
|
@ -127,6 +127,7 @@ static string const g_strInterface = "interface";
|
|||||||
static string const g_strYul = "yul";
|
static string const g_strYul = "yul";
|
||||||
static string const g_strYulDialect = "yul-dialect";
|
static string const g_strYulDialect = "yul-dialect";
|
||||||
static string const g_strIR = "ir";
|
static string const g_strIR = "ir";
|
||||||
|
static string const g_strIROptimized = "ir-optimized";
|
||||||
static string const g_strIPFS = "ipfs";
|
static string const g_strIPFS = "ipfs";
|
||||||
static string const g_strLicense = "license";
|
static string const g_strLicense = "license";
|
||||||
static string const g_strLibraries = "libraries";
|
static string const g_strLibraries = "libraries";
|
||||||
@ -190,6 +191,7 @@ static string const g_argImportAst = g_strImportAst;
|
|||||||
static string const g_argInputFile = g_strInputFile;
|
static string const g_argInputFile = g_strInputFile;
|
||||||
static string const g_argYul = g_strYul;
|
static string const g_argYul = g_strYul;
|
||||||
static string const g_argIR = g_strIR;
|
static string const g_argIR = g_strIR;
|
||||||
|
static string const g_argIROptimized = g_strIROptimized;
|
||||||
static string const g_argEwasm = g_strEwasm;
|
static string const g_argEwasm = g_strEwasm;
|
||||||
static string const g_argLibraries = g_strLibraries;
|
static string const g_argLibraries = g_strLibraries;
|
||||||
static string const g_argLink = g_strLink;
|
static string const g_argLink = g_strLink;
|
||||||
@ -336,36 +338,50 @@ void CommandLineInterface::handleOpcode(string const& _contract)
|
|||||||
|
|
||||||
void CommandLineInterface::handleIR(string const& _contractName)
|
void CommandLineInterface::handleIR(string const& _contractName)
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argIR))
|
if (!m_args.count(g_argIR))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argOutputDir))
|
sout() << "IR:" << endl;
|
||||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
|
sout() << m_compiler->yulIR(_contractName) << endl;
|
||||||
else
|
}
|
||||||
{
|
}
|
||||||
sout() << "IR:" << endl;
|
|
||||||
sout() << m_compiler->yulIR(_contractName) << endl;
|
void CommandLineInterface::handleIROptimized(string const& _contractName)
|
||||||
}
|
{
|
||||||
|
if (!m_args.count(g_argIROptimized))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sout() << "Optimized IR:" << endl;
|
||||||
|
sout() << m_compiler->yulIROptimized(_contractName) << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandLineInterface::handleEwasm(string const& _contractName)
|
void CommandLineInterface::handleEwasm(string const& _contractName)
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argEwasm))
|
if (!m_args.count(g_argEwasm))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_args.count(g_argOutputDir))
|
||||||
{
|
{
|
||||||
if (m_args.count(g_argOutputDir))
|
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
|
||||||
{
|
createFile(
|
||||||
createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
|
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
||||||
createFile(
|
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
||||||
m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
|
);
|
||||||
asString(m_compiler->ewasmObject(_contractName).bytecode)
|
}
|
||||||
);
|
else
|
||||||
}
|
{
|
||||||
else
|
sout() << "Ewasm text:" << endl;
|
||||||
{
|
sout() << m_compiler->ewasm(_contractName) << endl;
|
||||||
sout() << "Ewasm text:" << endl;
|
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
|
||||||
sout() << m_compiler->ewasm(_contractName) << endl;
|
|
||||||
sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,6 +828,7 @@ Allowed options)",
|
|||||||
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
|
(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
|
||||||
(g_argAbi.c_str(), "ABI specification of the contracts.")
|
(g_argAbi.c_str(), "ABI specification of the contracts.")
|
||||||
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
||||||
|
(g_argIROptimized.c_str(), "Optimized intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
|
||||||
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
|
(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
|
||||||
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
|
(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
|
||||||
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
|
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
|
||||||
@ -1123,7 +1140,7 @@ bool CommandLineInterface::processInput()
|
|||||||
m_compiler->setRevertStringBehaviour(m_revertStrings);
|
m_compiler->setRevertStringBehaviour(m_revertStrings);
|
||||||
// TODO: Perhaps we should not compile unless requested
|
// TODO: Perhaps we should not compile unless requested
|
||||||
|
|
||||||
m_compiler->enableIRGeneration(m_args.count(g_argIR));
|
m_compiler->enableIRGeneration(m_args.count(g_argIR) || m_args.count(g_argIROptimized));
|
||||||
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
|
m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));
|
||||||
|
|
||||||
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
|
||||||
@ -1631,6 +1648,7 @@ void CommandLineInterface::outputCompilationResults()
|
|||||||
|
|
||||||
handleBytecode(contract);
|
handleBytecode(contract);
|
||||||
handleIR(contract);
|
handleIR(contract);
|
||||||
|
handleIROptimized(contract);
|
||||||
handleEwasm(contract);
|
handleEwasm(contract);
|
||||||
handleSignatureHashes(contract);
|
handleSignatureHashes(contract);
|
||||||
handleMetadata(contract);
|
handleMetadata(contract);
|
||||||
|
@ -65,6 +65,7 @@ private:
|
|||||||
void handleBinary(std::string const& _contract);
|
void handleBinary(std::string const& _contract);
|
||||||
void handleOpcode(std::string const& _contract);
|
void handleOpcode(std::string const& _contract);
|
||||||
void handleIR(std::string const& _contract);
|
void handleIR(std::string const& _contract);
|
||||||
|
void handleIROptimized(std::string const& _contract);
|
||||||
void handleEwasm(std::string const& _contract);
|
void handleEwasm(std::string const& _contract);
|
||||||
void handleBytecode(std::string const& _contract);
|
void handleBytecode(std::string const& _contract);
|
||||||
void handleSignatureHashes(std::string const& _contract);
|
void handleSignatureHashes(std::string const& _contract);
|
||||||
|
@ -143,16 +143,22 @@ set(yul_phaser_sources
|
|||||||
yulPhaser/Common.cpp
|
yulPhaser/Common.cpp
|
||||||
yulPhaser/CommonTest.cpp
|
yulPhaser/CommonTest.cpp
|
||||||
yulPhaser/Chromosome.cpp
|
yulPhaser/Chromosome.cpp
|
||||||
|
yulPhaser/FitnessMetrics.cpp
|
||||||
|
yulPhaser/GeneticAlgorithms.cpp
|
||||||
yulPhaser/Population.cpp
|
yulPhaser/Population.cpp
|
||||||
yulPhaser/Program.cpp
|
yulPhaser/Program.cpp
|
||||||
|
yulPhaser/Selections.cpp
|
||||||
yulPhaser/SimulationRNG.cpp
|
yulPhaser/SimulationRNG.cpp
|
||||||
|
|
||||||
# FIXME: yul-phaser is not a library so I can't just add it to target_link_libraries().
|
# FIXME: yul-phaser is not a library so I can't just add it to target_link_libraries().
|
||||||
# My current workaround is just to include its source files here but this introduces
|
# My current workaround is just to include its source files here but this introduces
|
||||||
# unnecessary duplication. Create a library or find a way to reuse the list in both places.
|
# unnecessary duplication. Create a library or find a way to reuse the list in both places.
|
||||||
../tools/yulPhaser/Chromosome.cpp
|
../tools/yulPhaser/Chromosome.cpp
|
||||||
|
../tools/yulPhaser/FitnessMetrics.cpp
|
||||||
|
../tools/yulPhaser/GeneticAlgorithms.cpp
|
||||||
../tools/yulPhaser/Population.cpp
|
../tools/yulPhaser/Population.cpp
|
||||||
../tools/yulPhaser/Program.cpp
|
../tools/yulPhaser/Program.cpp
|
||||||
|
../tools/yulPhaser/Selections.cpp
|
||||||
../tools/yulPhaser/SimulationRNG.cpp
|
../tools/yulPhaser/SimulationRNG.cpp
|
||||||
)
|
)
|
||||||
detect_stray_source_files("${yul_phaser_sources}" "yulPhaser/")
|
detect_stray_source_files("${yul_phaser_sources}" "yulPhaser/")
|
||||||
|
@ -53,12 +53,16 @@ ExecutionFramework::ExecutionFramework(langutil::EVMVersion _evmVersion):
|
|||||||
m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
|
m_optimiserSettings = solidity::frontend::OptimiserSettings::full();
|
||||||
else if (solidity::test::CommonOptions::get().optimize)
|
else if (solidity::test::CommonOptions::get().optimize)
|
||||||
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
|
m_optimiserSettings = solidity::frontend::OptimiserSettings::standard();
|
||||||
m_evmHost->reset();
|
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExecutionFramework::reset()
|
||||||
|
{
|
||||||
|
m_evmHost->reset();
|
||||||
for (size_t i = 0; i < 10; i++)
|
for (size_t i = 0; i < 10; i++)
|
||||||
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
|
m_evmHost->accounts[EVMHost::convertToEVMC(account(i))].balance =
|
||||||
EVMHost::convertToEVMC(u256(1) << 100);
|
EVMHost::convertToEVMC(u256(1) << 100);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(
|
std::pair<bool, string> ExecutionFramework::compareAndCreateMessage(
|
||||||
|
@ -251,6 +251,8 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void reset();
|
||||||
|
|
||||||
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
|
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
|
||||||
void sendEther(Address const& _to, u256 const& _value);
|
void sendEther(Address const& _to, u256 const& _value);
|
||||||
size_t currentTimestamp();
|
size_t currentTimestamp();
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <test/Common.h>
|
||||||
#include <test/TestCase.h>
|
#include <test/TestCase.h>
|
||||||
|
|
||||||
#include <libsolutil/StringUtils.h>
|
#include <libsolutil/StringUtils.h>
|
||||||
@ -52,14 +53,18 @@ bool TestCase::isTestFilename(boost::filesystem::path const& _filename)
|
|||||||
!boost::starts_with(_filename.string(), ".");
|
!boost::starts_with(_filename.string(), ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TestCase::validateSettings(langutil::EVMVersion)
|
void TestCase::validateSettings()
|
||||||
{
|
{
|
||||||
if (!m_settings.empty())
|
if (!m_settings.empty())
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"Unknown setting(s): " +
|
"Unknown setting(s): " +
|
||||||
util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
|
util::joinHumanReadable(m_settings | boost::adaptors::map_keys)
|
||||||
);
|
);
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
bool TestCase::shouldRun()
|
||||||
|
{
|
||||||
|
return m_shouldRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
|
pair<map<string, string>, size_t> TestCase::parseSourcesAndSettingsWithLineNumbers(istream& _stream)
|
||||||
@ -157,20 +162,19 @@ void TestCase::expect(string::iterator& _it, string::iterator _end, string::valu
|
|||||||
++_it;
|
++_it;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVersion)
|
void EVMVersionRestrictedTestCase::validateSettings()
|
||||||
{
|
{
|
||||||
if (!m_settings.count("EVMVersion"))
|
if (!m_settings.count("EVMVersion"))
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
string versionString = m_settings["EVMVersion"];
|
string versionString = m_settings["EVMVersion"];
|
||||||
m_validatedSettings["EVMVersion"] = versionString;
|
m_validatedSettings["EVMVersion"] = versionString;
|
||||||
m_settings.erase("EVMVersion");
|
m_settings.erase("EVMVersion");
|
||||||
|
|
||||||
if (!TestCase::validateSettings(_evmVersion))
|
TestCase::validateSettings();
|
||||||
return false;
|
|
||||||
|
|
||||||
if (versionString.empty())
|
if (versionString.empty())
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
string comparator;
|
string comparator;
|
||||||
size_t versionBegin = 0;
|
size_t versionBegin = 0;
|
||||||
@ -188,18 +192,23 @@ bool EVMVersionRestrictedTestCase::validateSettings(langutil::EVMVersion _evmVer
|
|||||||
if (!version)
|
if (!version)
|
||||||
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
|
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM version: \"" + versionString + "\""});
|
||||||
|
|
||||||
|
langutil::EVMVersion evmVersion = solidity::test::CommonOptions::get().evmVersion();
|
||||||
|
bool comparisonResult;
|
||||||
if (comparator == ">")
|
if (comparator == ">")
|
||||||
return _evmVersion > version;
|
comparisonResult = evmVersion > version;
|
||||||
else if (comparator == ">=")
|
else if (comparator == ">=")
|
||||||
return _evmVersion >= version;
|
comparisonResult = evmVersion >= version;
|
||||||
else if (comparator == "<")
|
else if (comparator == "<")
|
||||||
return _evmVersion < version;
|
comparisonResult = evmVersion < version;
|
||||||
else if (comparator == "<=")
|
else if (comparator == "<=")
|
||||||
return _evmVersion <= version;
|
comparisonResult = evmVersion <= version;
|
||||||
else if (comparator == "=")
|
else if (comparator == "=")
|
||||||
return _evmVersion == version;
|
comparisonResult = evmVersion == version;
|
||||||
else if (comparator == "!")
|
else if (comparator == "!")
|
||||||
return !(_evmVersion == version);
|
comparisonResult = !(evmVersion == version);
|
||||||
else
|
else
|
||||||
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
|
BOOST_THROW_EXCEPTION(runtime_error{"Invalid EVM comparator: \"" + comparator + "\""});
|
||||||
|
|
||||||
|
if (!comparisonResult)
|
||||||
|
m_shouldRun = false;
|
||||||
}
|
}
|
||||||
|
@ -70,10 +70,12 @@ public:
|
|||||||
|
|
||||||
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
|
/// Validates the settings, i.e. moves them from m_settings to m_validatedSettings.
|
||||||
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
|
/// Throws a runtime exception if any setting is left at this class (i.e. unknown setting).
|
||||||
|
virtual void validateSettings();
|
||||||
|
|
||||||
/// Returns true, if the test case is supported in the current environment and false
|
/// Returns true, if the test case is supported in the current environment and false
|
||||||
/// otherwise which causes this test to be skipped.
|
/// otherwise which causes this test to be skipped.
|
||||||
/// This might check e.g. for restrictions on the EVM version.
|
/// This might check e.g. for restrictions on the EVM version.
|
||||||
virtual bool validateSettings(langutil::EVMVersion /*_evmVersion*/);
|
bool shouldRun();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
|
std::pair<std::map<std::string, std::string>, std::size_t> parseSourcesAndSettingsWithLineNumbers(std::istream& _file);
|
||||||
@ -102,13 +104,14 @@ protected:
|
|||||||
std::map<std::string, std::string> m_settings;
|
std::map<std::string, std::string> m_settings;
|
||||||
/// Updated settings after validation.
|
/// Updated settings after validation.
|
||||||
std::map<std::string, std::string> m_validatedSettings;
|
std::map<std::string, std::string> m_validatedSettings;
|
||||||
|
|
||||||
|
bool m_shouldRun = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
class EVMVersionRestrictedTestCase: public TestCase
|
class EVMVersionRestrictedTestCase: public TestCase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Returns true, if the test case is supported for EVM version @arg _evmVersion, false otherwise.
|
void validateSettings() override;
|
||||||
bool validateSettings(langutil::EVMVersion _evmVersion) override;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,8 @@ int registerTests(
|
|||||||
{
|
{
|
||||||
stringstream errorStream;
|
stringstream errorStream;
|
||||||
auto testCase = _testCaseCreator(config);
|
auto testCase = _testCaseCreator(config);
|
||||||
if (testCase->validateSettings(solidity::test::CommonOptions::get().evmVersion()))
|
testCase->validateSettings();
|
||||||
|
if (testCase->shouldRun())
|
||||||
switch (testCase->run(errorStream))
|
switch (testCase->run(errorStream))
|
||||||
{
|
{
|
||||||
case TestCase::TestResult::Success:
|
case TestCase::TestResult::Success:
|
||||||
|
@ -33,7 +33,19 @@ set -e
|
|||||||
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
|
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
|
||||||
SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
|
SOLIDITY_BUILD_DIR=${SOLIDITY_BUILD_DIR:-build}
|
||||||
source "${REPO_ROOT}/scripts/common.sh"
|
source "${REPO_ROOT}/scripts/common.sh"
|
||||||
SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/solc"
|
|
||||||
|
case "$OSTYPE" in
|
||||||
|
msys)
|
||||||
|
SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/Release/solc.exe"
|
||||||
|
|
||||||
|
# prevents msys2 path translation for a remapping test
|
||||||
|
export MSYS2_ARG_CONV_EXCL="="
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
SOLC="$REPO_ROOT/${SOLIDITY_BUILD_DIR}/solc/solc"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
INTERACTIVE=true
|
INTERACTIVE=true
|
||||||
if ! tty -s || [ "$CI" ]
|
if ! tty -s || [ "$CI" ]
|
||||||
then
|
then
|
||||||
|
1
test/cmdlineTests/standard_empty_file_name/exit
Normal file
1
test/cmdlineTests/standard_empty_file_name/exit
Normal file
@ -0,0 +1 @@
|
|||||||
|
0
|
10
test/cmdlineTests/standard_empty_file_name/input.json
Normal file
10
test/cmdlineTests/standard_empty_file_name/input.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"language": "Solidity",
|
||||||
|
"sources":
|
||||||
|
{
|
||||||
|
"":
|
||||||
|
{
|
||||||
|
"content": "pragma solidity >=0.0; import {A} from \".\";"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
test/cmdlineTests/standard_empty_file_name/output.json
Normal file
4
test/cmdlineTests/standard_empty_file_name/output.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{"errors":[{"component":"general","formattedMessage":":1:24: DeclarationError: Declaration \"A\" not found in \"\" (referenced as \".\").
|
||||||
|
pragma solidity >=0.0; import {A} from \".\";
|
||||||
|
^------------------^
|
||||||
|
","message":"Declaration \"A\" not found in \"\" (referenced as \".\").","severity":"error","type":"DeclarationError"}],"sources":{}}
|
@ -14,7 +14,7 @@
|
|||||||
sstore
|
sstore
|
||||||
/* \"A\":0:42 */
|
/* \"A\":0:42 */
|
||||||
pop
|
pop
|
||||||
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"object\" {
|
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"object\" {
|
||||||
code {
|
code {
|
||||||
let x := mload(0)
|
let x := mload(0)
|
||||||
sstore(add(x, 0), 0)
|
sstore(add(x, 0), 0)
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
pop
|
pop
|
||||||
stop
|
stop
|
||||||
data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263
|
data_4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45 616263
|
||||||
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"NamedObject\" {
|
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
|
||||||
code {
|
code {
|
||||||
let x := dataoffset(\"DataName\")
|
let x := dataoffset(\"DataName\")
|
||||||
sstore(add(x, 0), 0)
|
sstore(add(x, 0), 0)
|
||||||
|
@ -22,7 +22,7 @@ sub_0: assembly {
|
|||||||
/* \"A\":137:149 */
|
/* \"A\":137:149 */
|
||||||
revert
|
revert
|
||||||
}
|
}
|
||||||
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":""}},"ir":"object \"NamedObject\" {
|
","bytecode":{"linkReferences":{},"object":"bytecode removed","opcodes":"opcodes removed","sourceMap":"sourceMap removed"}},"ir":"object \"NamedObject\" {
|
||||||
code {
|
code {
|
||||||
let x := dataoffset(\"DataName\")
|
let x := dataoffset(\"DataName\")
|
||||||
sstore(add(x, 0), 0)
|
sstore(add(x, 0), 0)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user