mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7497 from ethereum/develop
Merge develop into release for 0.5.12
This commit is contained in:
commit
7709ece95f
@ -7,8 +7,8 @@ The docker images are build locally on the developer machine:
|
||||
```!sh
|
||||
cd .circleci/docker/
|
||||
|
||||
docker build -t ethereum/solc-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 .
|
||||
docker push solidity/solc-buildpack-deps:ubuntu1904
|
||||
docker build -t ethereum/solidity-buildpack-deps:ubuntu1904 -f Dockerfile.ubuntu1904 .
|
||||
docker push ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
```
|
||||
|
||||
which you can find on Dockerhub after the push at:
|
||||
@ -16,3 +16,13 @@ which you can find on Dockerhub after the push at:
|
||||
https://hub.docker.com/r/ethereum/solidity-buildpack-deps
|
||||
|
||||
where the image tag reflects the target OS to build Solidity and run its test on.
|
||||
|
||||
### Testing docker images locally
|
||||
|
||||
```!sh
|
||||
cd solidity
|
||||
# Mounts your local solidity directory in docker container for testing
|
||||
docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904 /bin/bash
|
||||
cd /src/solidity
|
||||
<commands_to_test_build_with_new_docker_image>
|
||||
```
|
@ -22,7 +22,7 @@ defaults:
|
||||
name: Build
|
||||
command: |
|
||||
set -ex
|
||||
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
|
||||
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" -o -n "$FORCE_RELEASE" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
|
||||
echo -n "$CIRCLE_SHA1" > commit_hash.txt
|
||||
mkdir -p build
|
||||
cd build
|
||||
@ -96,22 +96,46 @@ defaults:
|
||||
name: soltest
|
||||
command: ./.circleci/soltest.sh
|
||||
|
||||
- run_soltest_all: &run_soltest_all
|
||||
name: soltest_all
|
||||
command: ./.circleci/soltest_all.sh
|
||||
|
||||
- run_cmdline_tests: &run_cmdline_tests
|
||||
name: command line tests
|
||||
command: ./test/cmdlineTests.sh
|
||||
|
||||
- test_steps: &test_steps
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- test_ubuntu1904: &test_ubuntu1904
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
steps: *test_steps
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- test_ubuntu1904_clang: &test_ubuntu1904_clang
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- test_ubuntu1904_all: &test_ubuntu1904
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run: *run_soltest_all
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
- test_asan: &test_asan
|
||||
<<: *test_ubuntu1904
|
||||
@ -138,6 +162,16 @@ defaults:
|
||||
requires:
|
||||
- b_ubu
|
||||
|
||||
- workflow_ubuntu1904_clang: &workflow_ubuntu1904_clang
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
- b_ubu_clang
|
||||
|
||||
- workflow_ubuntu1904_release: &workflow_ubuntu1904_release
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
- b_ubu_release
|
||||
|
||||
- workflow_ubuntu1904_codecov: &workflow_ubuntu1904_codecov
|
||||
<<: *workflow_trigger_on_tags
|
||||
requires:
|
||||
@ -163,6 +197,28 @@ defaults:
|
||||
requires:
|
||||
- b_ubu_ossfuzz
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Notification Templates
|
||||
- gitter_notify_failure: &gitter_notify_failure
|
||||
name: Gitter notify failure
|
||||
command: >-
|
||||
curl -X POST -i
|
||||
-i -H "Content-Type: application/json"
|
||||
-H "Accept: application/json"
|
||||
-H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages"
|
||||
-d '{"text":" ❌ Nightly job **'$CIRCLE_JOB'** failed on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}'
|
||||
when: on_fail
|
||||
|
||||
- gitter_notify_success: &gitter_notify_success
|
||||
name: Gitter notify success
|
||||
command: >-
|
||||
curl -X POST -i
|
||||
-i -H "Content-Type: application/json"
|
||||
-H "Accept: application/json"
|
||||
-H "Authorization: Bearer $GITTER_API_TOKEN" "https://api.gitter.im/v1/rooms/$GITTER_NOTIFY_ROOM_ID/chatMessages"
|
||||
-d '{"text":" ✅ Nightly job **'$CIRCLE_JOB'** succeeded on **'$CIRCLE_BRANCH'**. Please see '$CIRCLE_BUILD_URL' for details."}'
|
||||
when: on_success
|
||||
|
||||
# -----------------------------------------------------------------------------------------------
|
||||
jobs:
|
||||
|
||||
@ -240,6 +296,18 @@ jobs:
|
||||
pip install --user z3-solver
|
||||
- run: *run_proofs
|
||||
|
||||
b_ubu_clang: &build_ubuntu1904_clang
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904-clang
|
||||
environment:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu: &build_ubuntu1904
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
@ -249,6 +317,11 @@ jobs:
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu_release: &build_ubuntu1904_release
|
||||
<<: *build_ubuntu1904
|
||||
environment:
|
||||
FORCE_RELEASE: ON
|
||||
|
||||
b_ubu18: &build_ubuntu1804
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1804
|
||||
@ -292,24 +365,24 @@ jobs:
|
||||
command: codecov --flags all --gcov-root build
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
# Builds in C++17 mode and uses debug build in order to speed up.
|
||||
# Builds in C++20 mode and uses debug build in order to speed up.
|
||||
# Do *NOT* store any artifacts or workspace as we don't run tests on this build.
|
||||
b_ubu_cxx17:
|
||||
b_ubu_cxx20:
|
||||
<<: *build_ubuntu1904
|
||||
environment:
|
||||
CMAKE_BUILD_TYPE: Debug
|
||||
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx17.cmake -DUSE_CVC4=OFF
|
||||
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/cxx20.cmake -DUSE_CVC4=OFF
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
|
||||
b_ubu_ossfuzz:
|
||||
<<: *build_ubuntu1904
|
||||
<<: *build_ubuntu1904_clang
|
||||
environment:
|
||||
TERM: xterm
|
||||
CC: /usr/bin/clang-8
|
||||
CXX: /usr/bin/clang++-8
|
||||
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
CMAKE_OPTIONS: -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
|
||||
steps:
|
||||
- checkout
|
||||
- run: *setup_prerelease_commit_hash
|
||||
@ -317,7 +390,7 @@ jobs:
|
||||
- persist_to_workspace: *artifacts_executables_ossfuzz
|
||||
|
||||
t_ubu_ossfuzz: &t_ubu_ossfuzz
|
||||
<<: *test_ubuntu1904
|
||||
<<: *test_ubuntu1904_clang
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
@ -328,6 +401,8 @@ jobs:
|
||||
mkdir -p test_results
|
||||
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
|
||||
scripts/regressions.py -o test_results
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
@ -445,6 +520,18 @@ jobs:
|
||||
path: docs/_build/html/
|
||||
destination: docs-html
|
||||
|
||||
t_ubu_soltest: &t_ubu_soltest
|
||||
<<: *test_ubuntu1904
|
||||
|
||||
t_ubu_clang_soltest: &t_ubu_clang_soltest
|
||||
<<: *test_ubuntu1904_clang
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_release_soltest: &t_ubu_release_soltest
|
||||
<<: *t_ubu_soltest
|
||||
|
||||
t_ubu_cli: &t_ubu_cli
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1904
|
||||
@ -458,6 +545,9 @@ jobs:
|
||||
- store_test_results: *store_test_results
|
||||
- store_artifacts: *artifacts_test_results
|
||||
|
||||
t_ubu_release_cli: &t_ubu_release_cli
|
||||
<<: *t_ubu_cli
|
||||
|
||||
t_ubu_asan_cli:
|
||||
<<: *t_ubu_cli
|
||||
environment:
|
||||
@ -478,63 +568,9 @@ jobs:
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 0
|
||||
flags: --no-smt
|
||||
ASAN_OPTIONS: check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2
|
||||
|
||||
t_ubu_homestead:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: homestead
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_homestead_opt:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: homestead
|
||||
OPTIMIZE: 1
|
||||
|
||||
t_ubu_byzantium:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: byzantium
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_byzantium_opt:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: byzantium
|
||||
OPTIMIZE: 1
|
||||
|
||||
t_ubu_constantinople:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_constantinople_opt:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 1
|
||||
|
||||
t_ubu_constantinople_opt_abiv2:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 1
|
||||
ABI_ENCODER_V2: 1
|
||||
|
||||
t_ubu_petersburg:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: petersburg
|
||||
OPTIMIZE: 0
|
||||
|
||||
t_ubu_petersburg_opt:
|
||||
<<: *test_ubuntu1904
|
||||
environment:
|
||||
EVM: petersburg
|
||||
OPTIMIZE: 1
|
||||
|
||||
t_ems_solcjs:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
@ -564,6 +600,8 @@ jobs:
|
||||
name: External GnosisSafe tests
|
||||
command: |
|
||||
test/externalTests/gnosis.sh /tmp/workspace/soljson.js || test/externalTests/gnosis.sh /tmp/workspace/soljson.js
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
|
||||
t_ems_external_zeppelin:
|
||||
docker:
|
||||
@ -578,6 +616,8 @@ jobs:
|
||||
name: External Zeppelin tests
|
||||
command: |
|
||||
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
|
||||
t_ems_external_colony:
|
||||
docker:
|
||||
@ -596,6 +636,8 @@ jobs:
|
||||
name: External ColonyNetworks tests
|
||||
command: |
|
||||
test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js
|
||||
- run: *gitter_notify_failure
|
||||
- run: *gitter_notify_success
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@ -612,26 +654,25 @@ workflows:
|
||||
# build-only
|
||||
- b_docs: *workflow_trigger_on_tags
|
||||
- b_archlinux: *workflow_trigger_on_tags
|
||||
- b_ubu_cxx17: *workflow_trigger_on_tags
|
||||
- b_ubu_cxx20: *workflow_trigger_on_tags
|
||||
- b_ubu_ossfuzz: *workflow_trigger_on_tags
|
||||
|
||||
# OS/X build and tests
|
||||
- b_osx: *workflow_trigger_on_tags
|
||||
- t_osx_cli: *workflow_osx
|
||||
|
||||
# Ubuntu 18.10 build and tests
|
||||
# Ubuntu build and tests
|
||||
- b_ubu: *workflow_trigger_on_tags
|
||||
- b_ubu18: *workflow_trigger_on_tags
|
||||
- t_ubu_cli: *workflow_ubuntu1904
|
||||
- t_ubu_homestead: *workflow_ubuntu1904
|
||||
- t_ubu_homestead_opt: *workflow_ubuntu1904
|
||||
- t_ubu_byzantium: *workflow_ubuntu1904
|
||||
- t_ubu_byzantium_opt: *workflow_ubuntu1904
|
||||
- t_ubu_constantinople: *workflow_ubuntu1904
|
||||
- t_ubu_constantinople_opt: *workflow_ubuntu1904
|
||||
- t_ubu_constantinople_opt_abiv2: *workflow_ubuntu1904
|
||||
- t_ubu_petersburg: *workflow_ubuntu1904
|
||||
- t_ubu_petersburg_opt: *workflow_ubuntu1904
|
||||
- t_ubu_soltest: *workflow_ubuntu1904
|
||||
- b_ubu_clang: *workflow_trigger_on_tags
|
||||
- t_ubu_clang_soltest: *workflow_ubuntu1904_clang
|
||||
|
||||
# Ubuntu fake release build and tests
|
||||
- b_ubu_release: *workflow_trigger_on_tags
|
||||
- t_ubu_release_cli: *workflow_ubuntu1904_release
|
||||
- t_ubu_release_soltest: *workflow_ubuntu1904_release
|
||||
|
||||
# ASan build and tests
|
||||
- b_ubu_asan: *workflow_trigger_on_tags
|
||||
@ -651,6 +692,7 @@ workflows:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- develop_060
|
||||
|
||||
jobs:
|
||||
# Emscripten builds and external tests
|
||||
|
131
.circleci/docker/Dockerfile.clang.ubuntu1904
Normal file
131
.circleci/docker/Dockerfile.clang.ubuntu1904
Normal file
@ -0,0 +1,131 @@
|
||||
# vim:syntax=dockerfile
|
||||
#------------------------------------------------------------------------------
|
||||
# Dockerfile for building and testing Solidity Compiler on CI
|
||||
# Target: Ubuntu 19.04 (Disco Dingo) 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 buildpack-deps:disco AS base
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -ex; \
|
||||
dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \
|
||||
echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \
|
||||
apt-get update; \
|
||||
apt-get install -qqy --no-install-recommends \
|
||||
build-essential \
|
||||
software-properties-common \
|
||||
cmake ninja-build \
|
||||
clang++-8 llvm-8-dev \
|
||||
libjsoncpp-dev \
|
||||
libleveldb1d \
|
||||
; \
|
||||
apt-get install -qy python-pip python-sphinx; \
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \
|
||||
update-alternatives --install /usr/bin/clang clang /usr/bin/clang-8 1; \
|
||||
update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-8 1; \
|
||||
pip install codecov; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM base AS libraries
|
||||
|
||||
ENV CC clang
|
||||
ENV CXX clang++
|
||||
|
||||
# Boost
|
||||
RUN git clone --recursive -b boost-1.69.0 https://github.com/boostorg/boost.git \
|
||||
/usr/src/boost; \
|
||||
cd /usr/src/boost; \
|
||||
./bootstrap.sh --with-toolset=clang --prefix=/usr; \
|
||||
./b2 toolset=clang headers; \
|
||||
./b2 toolset=clang variant=release \
|
||||
system regex 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.5 https://github.com/Z3Prover/z3.git \
|
||||
/usr/src/z3; \
|
||||
cd /usr/src/z3; \
|
||||
python scripts/mk_make.py --prefix=/usr ; \
|
||||
cd build; \
|
||||
make -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 d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
|
||||
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
|
||||
|
||||
# ETHASH
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \
|
||||
cd ethash; \
|
||||
mkdir build; \
|
||||
cd build; \
|
||||
cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
|
||||
ninja; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/ethash
|
||||
|
||||
# INTX
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \
|
||||
cd intx; \
|
||||
mkdir build; \
|
||||
cd build; \
|
||||
cmake .. -G Ninja -DBUILD_SHARED_LIBS=ON -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
|
||||
ninja; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/intx;
|
||||
|
||||
# EVMONE
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.1.0" --recurse-submodules https://github.com/ethereum/evmone.git; \
|
||||
cd evmone; \
|
||||
mkdir build; \
|
||||
cd build; \
|
||||
cmake -G Ninja -DBUILD_SHARED_LIBS=ON -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
|
117
.circleci/docker/Dockerfile.ubuntu1804
Normal file
117
.circleci/docker/Dockerfile.ubuntu1804
Normal file
@ -0,0 +1,117 @@
|
||||
# vim:syntax=dockerfile
|
||||
#------------------------------------------------------------------------------
|
||||
# Dockerfile for building and testing Solidity Compiler on CI
|
||||
# Target: Ubuntu 18.04 (Bionic Beaver)
|
||||
# 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 buildpack-deps:bionic AS base
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -ex; \
|
||||
dist=$(grep DISTRIB_CODENAME /etc/lsb-release | cut -d= -f2); \
|
||||
echo "deb http://ppa.launchpad.net/ethereum/cpp-build-deps/ubuntu $dist main" >> /etc/apt/sources.list ; \
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1c52189c923f6ca9 ; \
|
||||
apt-get update; \
|
||||
apt-get install -qqy --no-install-recommends \
|
||||
build-essential \
|
||||
software-properties-common \
|
||||
cmake ninja-build clang++-8 \
|
||||
libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \
|
||||
libboost-program-options-dev \
|
||||
libjsoncpp-dev \
|
||||
llvm-8-dev libz3-static-dev \
|
||||
; \
|
||||
apt-get install -qy python-pip python-sphinx; \
|
||||
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1; \
|
||||
pip install codecov; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM base AS libraries
|
||||
|
||||
# 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 d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
|
||||
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
|
||||
|
||||
# OSSFUZZ: libfuzzer
|
||||
RUN set -ex; \
|
||||
cd /var/tmp; \
|
||||
svn co https://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/fuzzer libfuzzer; \
|
||||
mkdir -p build-libfuzzer; \
|
||||
cd build-libfuzzer; \
|
||||
clang++-8 -O1 -stdlib=libstdc++ -std=c++11 -O2 -fPIC -c ../libfuzzer/*.cpp -I../libfuzzer; \
|
||||
ar r /usr/lib/libFuzzingEngine.a *.o; \
|
||||
rm -rf /var/lib/libfuzzer
|
||||
|
||||
# ETHASH
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.4.4" https://github.com/chfast/ethash.git; \
|
||||
cd ethash; \
|
||||
mkdir build; \
|
||||
cd build; \
|
||||
cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DETHASH_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
|
||||
ninja; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/ethash
|
||||
|
||||
# INTX
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.2.0" https://github.com/chfast/intx.git; \
|
||||
cd intx; \
|
||||
mkdir build; \
|
||||
cd build; \
|
||||
cmake .. -G Ninja -DBUILD_SHARED_LIBS=OFF -DINTX_TESTING=OFF -DINTX_BENCHMARKING=OFF -DCMAKE_INSTALL_PREFIX="/usr"; \
|
||||
ninja; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/intx;
|
||||
|
||||
# EVMONE
|
||||
ARG EVMONE_HASH="f10d12c190f55a9d373e78b2dc0074d35d752c02cb536bb6fe754fb3719dd69e"
|
||||
ARG EVMONE_MAJOR="0"
|
||||
ARG EVMONE_MINOR="1"
|
||||
ARG EVMONE_MICRO="0"
|
||||
RUN set -ex; \
|
||||
EVMONE_VERSION="$EVMONE_MAJOR.$EVMONE_MINOR.$EVMONE_MICRO"; \
|
||||
TGZFILE="evmone-$EVMONE_VERSION-linux-x86_64.tar.gz"; \
|
||||
wget https://github.com/ethereum/evmone/releases/download/v$EVMONE_VERSION/$TGZFILE; \
|
||||
sha256sum $TGZFILE; \
|
||||
tar xzpf $TGZFILE -C /usr; \
|
||||
rm -f $TGZFILE;
|
||||
|
||||
FROM base
|
||||
COPY --from=libraries /usr/lib /usr/lib
|
||||
COPY --from=libraries /usr/bin /usr/bin
|
||||
COPY --from=libraries /usr/include /usr/include
|
@ -33,7 +33,7 @@ RUN set -ex; \
|
||||
apt-get install -qqy --no-install-recommends \
|
||||
build-essential \
|
||||
software-properties-common \
|
||||
cmake ninja-build clang++-8 \
|
||||
cmake ninja-build clang++-8 libc++-8-dev libc++abi-8-dev \
|
||||
libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \
|
||||
libboost-program-options-dev \
|
||||
libjsoncpp-dev \
|
||||
@ -46,16 +46,6 @@ RUN set -ex; \
|
||||
|
||||
FROM base AS libraries
|
||||
|
||||
# Z3
|
||||
RUN set -ex; \
|
||||
git clone --depth=1 --branch="Z3-4.8.5" https://github.com/Z3Prover/z3.git /usr/src/z3; \
|
||||
mkdir /usr/src/z3/build; \
|
||||
cd /usr/src/z3/build; \
|
||||
cmake -DCMAKE_BUILD_TYPE="Release" -DCMAKE_INSTALL_PREFIX="/usr" -G "Ninja" ..; \
|
||||
ninja; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/z3
|
||||
|
||||
# OSSFUZZ: libprotobuf-mutator
|
||||
RUN set -ex; \
|
||||
git clone https://github.com/google/libprotobuf-mutator.git \
|
||||
|
@ -45,7 +45,15 @@ mkdir -p test_results
|
||||
# in case we run with ASAN enabled, we must increase stck size.
|
||||
ulimit -s 16384
|
||||
|
||||
BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/$EVM.xml"
|
||||
get_logfile_basename() {
|
||||
local filename="${EVM}"
|
||||
test "${OPTIMIZE}" = "1" && filename="${filename}_opt"
|
||||
test "${ABI_ENCODER_V2}" = "1" && filename="${filename}_abiv2"
|
||||
|
||||
echo -ne "${filename}"
|
||||
}
|
||||
|
||||
BOOST_TEST_ARGS="--color_output=no --show_progress=yes --logger=JUNIT,error,test_results/`get_logfile_basename`.xml"
|
||||
SOLTEST_ARGS="--evm-version=$EVM --evmonepath /usr/lib/libevmone.so $flags"
|
||||
test "${OPTIMIZE}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --optimize"
|
||||
test "${ABI_ENCODER_V2}" = "1" && SOLTEST_ARGS="${SOLTEST_ARGS} --abiencoderv2 --optimize-yul"
|
||||
|
37
.circleci/soltest_all.sh
Executable file
37
.circleci/soltest_all.sh
Executable file
@ -0,0 +1,37 @@
|
||||
#! /bin/bash
|
||||
#------------------------------------------------------------------------------
|
||||
# Bash script to execute the Solidity tests by CircleCI.
|
||||
#
|
||||
# The documentation for solidity is hosted at:
|
||||
#
|
||||
# https://solidity.readthedocs.org
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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.
|
||||
# ------------------------------------------------------------------------------
|
||||
set -e
|
||||
|
||||
REPODIR="$(realpath $(dirname $0)/..)"
|
||||
|
||||
for OPTIMIZE in 0 1; do
|
||||
for EVM in homestead byzantium constantinople petersburg; do
|
||||
EVM=$EVM OPTIMIZE=$OPTIMIZE ${REPODIR}/.circleci/soltest.sh
|
||||
done
|
||||
done
|
||||
|
||||
EVM=constantinople OPTIMIZE=1 ABI_ENCODER_V2=1 ${REPODIR}/.circleci/soltest.sh
|
32
.clang-format
Normal file
32
.clang-format
Normal file
@ -0,0 +1,32 @@
|
||||
# Formatting approximately used in Solidity's C++
|
||||
#
|
||||
# See https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
||||
# For an online formatter to test settings, see
|
||||
# https://zed0.co.uk/clang-format-configurator/
|
||||
# Note that clang-format cannot express the style that closing parentheses
|
||||
# behave similar to closing curly braces in a multi-line setting in that
|
||||
# they have to be on a line of their own at the same indentation level
|
||||
# as the opening part.
|
||||
|
||||
Language: Cpp
|
||||
BasedOnStyle: LLVM
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeBraces: Allman
|
||||
ColumnLimit: 120
|
||||
ContinuationIndentWidth: 4
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 2000
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
TabWidth: 4
|
||||
UseTab: ForIndentation
|
||||
|
||||
# Local Variables:
|
||||
# mode: yaml
|
||||
# End:
|
@ -188,7 +188,7 @@ cache:
|
||||
|
||||
install:
|
||||
- test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh)
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh)
|
||||
- test "$TRAVIS_OS_NAME" != "linux" || (sudo scripts/install_cmake.sh)
|
||||
|
||||
before_script:
|
||||
# Disable tests unless run on the release branch, on tags or with daily cron
|
||||
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5.0)
|
||||
cmake_minimum_required(VERSION 3.9.0)
|
||||
|
||||
set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory")
|
||||
list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR})
|
||||
@ -10,14 +10,9 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.11")
|
||||
set(PROJECT_VERSION "0.5.12")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
|
||||
|
||||
if (${CMAKE_VERSION} VERSION_LESS "3.9.0")
|
||||
# needed for the big endian test for older cmake versions
|
||||
enable_language(C)
|
||||
endif()
|
||||
|
||||
include(TestBigEndian)
|
||||
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
|
||||
if (IS_BIG_ENDIAN)
|
||||
|
24
Changelog.md
24
Changelog.md
@ -1,3 +1,25 @@
|
||||
### 0.5.12 (2019-10-01)
|
||||
|
||||
Language Features:
|
||||
* Type Checker: Allow assignment to external function arguments except for reference types.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* ABI Output: Change sorting order of functions from selector to kind, name.
|
||||
* Optimizer: Add rule that replaces the BYTE opcode by 0 if the first argument is larger than 31.
|
||||
* SMTChecker: Add loop support to the CHC engine.
|
||||
* Yul Optimizer: Take side-effect-freeness of user-defined functions into account.
|
||||
* Yul Optimizer: Remove redundant mload/sload operations.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* Code Generator: Fix internal error when popping a dynamic storage array of mappings.
|
||||
* Name Resolver: Fix wrong source location when warning on shadowed aliases in import declarations.
|
||||
* Scanner: Fix multi-line natspec comment parsing with triple slashes when file is encoded with CRLF instead of LF.
|
||||
* Type System: Fix arrays of recursive structs.
|
||||
* Yul Optimizer: Fix reordering bug in connection with shifted one and mul/div-instructions in for loop conditions.
|
||||
|
||||
|
||||
### 0.5.11 (2019-08-12)
|
||||
|
||||
|
||||
@ -12,9 +34,11 @@ Compiler Features:
|
||||
* Standard JSON Interface: Provide secondary error locations (e.g. the source position of other conflicting declarations).
|
||||
* SMTChecker: Do not erase knowledge about storage pointers if another storage pointer is assigned.
|
||||
* SMTChecker: Support string literal type.
|
||||
* SMTChecker: New Horn-based algorithm that proves assertions via multi-transaction contract invariants.
|
||||
* Standard JSON Interface: Provide AST even on errors if ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` is true.
|
||||
* Yul Optimizer: Do not inline function if it would result in expressions being duplicated that are not cheap.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* ABI decoder: Ensure that decoded arrays always point to distinct memory locations.
|
||||
* Code Generator: Treat dynamically encoded but statically sized arrays and structs in calldata properly.
|
||||
|
@ -19,15 +19,20 @@ if (USE_Z3)
|
||||
find_library(Z3_LIBRARY NAMES z3)
|
||||
find_program(Z3_EXECUTABLE z3 PATH_SUFFIXES bin)
|
||||
|
||||
if(Z3_INCLUDE_DIR AND Z3_LIBRARY AND Z3_EXECUTABLE)
|
||||
execute_process (COMMAND ${Z3_EXECUTABLE} -version
|
||||
OUTPUT_VARIABLE libz3_version_str
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(Z3_INCLUDE_DIR AND Z3_LIBRARY)
|
||||
if(Z3_EXECUTABLE)
|
||||
execute_process (COMMAND ${Z3_EXECUTABLE} -version
|
||||
OUTPUT_VARIABLE libz3_version_str
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1"
|
||||
Z3_VERSION_STRING "${libz3_version_str}")
|
||||
unset(libz3_version_str)
|
||||
string(REGEX REPLACE "^Z3 version ([0-9.]+).*" "\\1"
|
||||
Z3_VERSION_STRING "${libz3_version_str}")
|
||||
unset(libz3_version_str)
|
||||
else()
|
||||
message(WARNING "Could not determine the version of z3, since the z3 executable was not found.")
|
||||
set(Z3_VERSION_STRING "0.0.0")
|
||||
endif()
|
||||
endif()
|
||||
mark_as_advanced(Z3_VERSION_STRING z3_DIR)
|
||||
|
||||
|
@ -15,11 +15,18 @@ set(JSONCPP_INCLUDE_DIR "${prefix}/include")
|
||||
# versions used in the CI runs.
|
||||
if(EMSCRIPTEN)
|
||||
# Do not include all flags in CMAKE_CXX_FLAGS for emscripten,
|
||||
# but only use -std=c++14. Using all flags causes build failures
|
||||
# but only use -std=c++17. Using all flags causes build failures
|
||||
# at the moment.
|
||||
set(JSONCPP_CXX_FLAGS -std=c++14)
|
||||
set(JSONCPP_CXX_FLAGS -std=c++17)
|
||||
else()
|
||||
set(JSONCPP_CXX_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
# jsoncpp uses implicit casts for comparing integer and
|
||||
# floating point numbers. This causes clang-10 (used by ossfuzz builder)
|
||||
# to error on the implicit conversions. Here, we request jsoncpp
|
||||
# to unconditionally use static casts for these conversions by defining the
|
||||
# JSON_USE_INT64_DOUBLE_CONVERSION preprocessor macro. Doing so,
|
||||
# not only gets rid of the implicit conversion error that clang-10 produces
|
||||
# but also forces safer behavior in general.
|
||||
set(JSONCPP_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DJSON_USE_INT64_DOUBLE_CONVERSION")
|
||||
endif()
|
||||
|
||||
set(byproducts "")
|
||||
|
@ -1,4 +0,0 @@
|
||||
# Require C++17.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
4
cmake/toolchains/cxx20.cmake
Normal file
4
cmake/toolchains/cxx20.cmake
Normal file
@ -0,0 +1,4 @@
|
||||
# Require C++20.
|
||||
set(CMAKE_CXX_STANDARD 20) # This requires at least CMake 3.12 to understand this C++20 flag
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
@ -1,4 +1,4 @@
|
||||
# Require C++14.
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
# Require C++17.
|
||||
set(CMAKE_CXX_STANDARD 17) # This requires at least CMake 3.8 to accept this C++17 flag.
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
@ -1,2 +1,11 @@
|
||||
# Require libfuzzer specific flags
|
||||
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++")
|
||||
# Inherit default options
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/default.cmake")
|
||||
# Enable Z3, disable CVC4
|
||||
set(USE_Z3 ON CACHE BOOL "Enable Z3" FORCE)
|
||||
set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
|
||||
# Build fuzzing binaries
|
||||
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
|
||||
# Use libfuzzer as the fuzzing back-end
|
||||
set(LIB_FUZZING_ENGINE "-fsanitize=fuzzer" CACHE STRING "Use libfuzzer back-end" FORCE)
|
||||
# clang/libfuzzer specific flags for ASan instrumentation
|
||||
set(CMAKE_CXX_FLAGS "-O1 -gline-tables-only -fsanitize=address -fsanitize-address-use-after-scope -fsanitize=fuzzer-no-link -stdlib=libstdc++" CACHE STRING "Custom compilation flags" FORCE)
|
||||
|
11
cmake/toolchains/ossfuzz.cmake
Normal file
11
cmake/toolchains/ossfuzz.cmake
Normal file
@ -0,0 +1,11 @@
|
||||
# Inherit default options
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/default.cmake")
|
||||
# Disable CVC4.
|
||||
set(USE_CVC4 OFF CACHE BOOL "Disable CVC4" FORCE)
|
||||
# Enable fuzzers
|
||||
set(OSSFUZZ ON CACHE BOOL "Enable fuzzer build" FORCE)
|
||||
set(LIB_FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE} CACHE STRING "Use fuzzer back-end defined by environment variable" 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)
|
@ -444,17 +444,17 @@ JSON
|
||||
The JSON format for a contract's interface is given by an array of function and/or event descriptions.
|
||||
A function description is a JSON object with the fields:
|
||||
|
||||
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`);
|
||||
- ``name``: the name of the function;
|
||||
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`).
|
||||
- ``name``: the name of the function.
|
||||
- ``inputs``: an array of objects, each of which contains:
|
||||
|
||||
* ``name``: the name of the parameter;
|
||||
* ``name``: the name of the parameter.
|
||||
* ``type``: the canonical type of the parameter (more below).
|
||||
* ``components``: used for tuple types (more below).
|
||||
|
||||
- ``outputs``: an array of objects similar to ``inputs``, can be omitted if function doesn't return anything;
|
||||
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether);
|
||||
- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise;
|
||||
- ``outputs``: an array of objects similar to ``inputs``.
|
||||
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``nonpayable`` (function does not accept Ether) and ``payable`` (function accepts Ether).
|
||||
- ``payable``: ``true`` if function accepts Ether, ``false`` otherwise.
|
||||
- ``constant``: ``true`` if function is either ``pure`` or ``view``, ``false`` otherwise.
|
||||
|
||||
``type`` can be omitted, defaulting to ``"function"``, likewise ``payable`` and ``constant`` can be omitted, both defaulting to ``false``.
|
||||
@ -470,10 +470,10 @@ Constructor and fallback function never have ``name`` or ``outputs``. Fallback f
|
||||
An event description is a JSON object with fairly similar fields:
|
||||
|
||||
- ``type``: always ``"event"``
|
||||
- ``name``: the name of the event;
|
||||
- ``name``: the name of the event.
|
||||
- ``inputs``: an array of objects, each of which contains:
|
||||
|
||||
* ``name``: the name of the parameter;
|
||||
* ``name``: the name of the parameter.
|
||||
* ``type``: the canonical type of the parameter (more below).
|
||||
* ``components``: used for tuple types (more below).
|
||||
* ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment.
|
||||
|
@ -47,7 +47,7 @@ usual ``//`` and ``/* */`` comments. There is one exception: Identifiers in inli
|
||||
these curly braces, you can use the following (see the later sections for more details):
|
||||
|
||||
- literals, i.e. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters)
|
||||
- opcodes in functional style, e.g. ``add(1, mlod(0))``
|
||||
- opcodes in functional style, e.g. ``add(1, mload(0))``
|
||||
- variable declarations, e.g. ``let x := 7``, ``let x := add(y, 3)`` or ``let x`` (initial value of empty (0) is assigned)
|
||||
- identifiers (assembly-local variables and externals if used as inline assembly), e.g. ``add(3, x)``, ``sstore(x_slot, 2)``
|
||||
- assignments, e.g. ``x := add(y, 3)``
|
||||
@ -163,7 +163,8 @@ If an opcode takes arguments (always from the top of the stack), they are given
|
||||
Note that the order of arguments can be seen to be reversed in non-functional style (explained below).
|
||||
Opcodes marked with ``-`` do not push an item onto the stack (do not return a result),
|
||||
those marked with ``*`` are special and all others push exactly one item onto the stack (their "return value").
|
||||
Opcodes marked with ``F``, ``H``, ``B`` or ``C`` are present since Frontier, Homestead, Byzantium or Constantinople, respectively.
|
||||
Opcodes marked with ``F``, ``H``, ``B``, ``C`` or ``I`` are present since Frontier, Homestead,
|
||||
Byzantium, Constantinople or Istanbul, respectively.
|
||||
|
||||
In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
|
||||
but not including position ``b`` and ``storage[p]`` signifies the storage contents at position ``p``.
|
||||
@ -259,6 +260,8 @@ In the grammar, opcodes are represented as pre-defined identifiers.
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| balance(a) | | F | wei balance at address a |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| selfbalance() | | I | equivalent to balance(address()), but cheaper |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| caller | | F | call sender (excluding ``delegatecall``) |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| callvalue | | F | wei sent together with the current call |
|
||||
@ -325,6 +328,8 @@ In the grammar, opcodes are represented as pre-defined identifiers.
|
||||
| log4(p, s, t1, t2, t3, | `-` | F | log with topics t1, t2, t3, t4 and data mem[p...(p+s)) |
|
||||
| t4) | | | |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| chainid | | I | ID of the executing chain (EIP 1344) |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| origin | | F | transaction sender |
|
||||
+-------------------------+-----+---+-----------------------------------------------------------------+
|
||||
| gasprice | | F | gas price of the transaction |
|
||||
|
@ -750,6 +750,10 @@
|
||||
"bugs": [],
|
||||
"released": "2019-08-12"
|
||||
},
|
||||
"0.5.12": {
|
||||
"bugs": [],
|
||||
"released": "2019-10-01"
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [
|
||||
"SignedArrayStorageCopy",
|
||||
|
@ -41,15 +41,11 @@ become the new richest.
|
||||
mostSent = msg.value;
|
||||
}
|
||||
|
||||
function becomeRichest() public payable returns (bool) {
|
||||
if (msg.value > mostSent) {
|
||||
pendingWithdrawals[richest] += msg.value;
|
||||
richest = msg.sender;
|
||||
mostSent = msg.value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
function becomeRichest() public payable {
|
||||
require(msg.value > mostSent, "Not enough money sent.");
|
||||
pendingWithdrawals[richest] += msg.value;
|
||||
richest = msg.sender;
|
||||
mostSent = msg.value;
|
||||
}
|
||||
|
||||
function withdraw() public {
|
||||
@ -76,16 +72,12 @@ This is as opposed to the more intuitive sending pattern:
|
||||
mostSent = msg.value;
|
||||
}
|
||||
|
||||
function becomeRichest() public payable returns (bool) {
|
||||
if (msg.value > mostSent) {
|
||||
// This line can cause problems (explained below).
|
||||
richest.transfer(msg.value);
|
||||
richest = msg.sender;
|
||||
mostSent = msg.value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
function becomeRichest() public payable {
|
||||
require(msg.value > mostSent, "Not enough money sent.");
|
||||
// This line can cause problems (explained below).
|
||||
richest.transfer(msg.value);
|
||||
richest = msg.sender;
|
||||
mostSent = msg.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ Low-Level Interface to Logs
|
||||
|
||||
It is also possible to access the low-level interface to the logging
|
||||
mechanism via the functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4``.
|
||||
``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first
|
||||
Each function ``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first
|
||||
argument will be used for the data part of the log and the others
|
||||
as topics. The event call above can be performed in the same way as
|
||||
|
||||
|
@ -88,12 +88,13 @@ Solidity includes different types of tests, most of them bundled into the
|
||||
`Boost C++ Test Framework <https://www.boost.org/doc/libs/1_69_0/libs/test/doc/html/index.html>`_ application ``soltest``.
|
||||
Running ``build/test/soltest` or its wrapper ``scripts/soltest.sh`` is sufficient for most changes.
|
||||
|
||||
Some tests require the ``libevmone.so`` library, others require ``libz3``.
|
||||
Some tests require the ``evmone`` library, others require ``libz3``.
|
||||
|
||||
The test system will automatically try to discover the location of ``libevmone.so``
|
||||
starting from the current directory. If it does not find it, the relevant tests
|
||||
The test system will automatically try to discover the location of the ``evmone`` library
|
||||
starting from the current directory. The required file is called ``libevmone.so`` on Linux systems,
|
||||
``evmone.dll`` on Windows systems and ``libevmone.dylib`` on MacOS. If it is not found, the relevant tests
|
||||
are skipped. To run all tests, download the library from
|
||||
`Github <https://github.com/ethereum/evmone/releases/download/v0.1.0/evmone-0.1.0-linux-x86_64.tar.gz>`_
|
||||
`Github <https://github.com/ethereum/evmone/releases/tag/v0.1.0>`_
|
||||
and either place it in the project root path or inside the ``deps`` folder.
|
||||
|
||||
If you do not have libz3 installed on your system, you should disable the SMT tests:
|
||||
|
@ -171,7 +171,7 @@ AssemblyVariableDeclaration = 'let' AssemblyIdentifierList ( ':=' AssemblyExpres
|
||||
AssemblyAssignment = AssemblyIdentifierList ':=' AssemblyExpression
|
||||
AssemblyExpression = AssemblyFunctionCall | Identifier | Literal
|
||||
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
|
||||
AssemblySwitch = 'switch' AssemblyExpression ( Case+ AssemblyDefault? | AssemblyDefault )
|
||||
AssemblySwitch = 'switch' AssemblyExpression ( AssemblyCase+ AssemblyDefault? | AssemblyDefault )
|
||||
AssemblyCase = 'case' Literal AssemblyBlock
|
||||
AssemblyDefault = 'default' AssemblyBlock
|
||||
AssemblyForLoop = 'for' AssemblyBlock AssemblyExpression AssemblyBlock AssemblyBlock
|
||||
|
@ -184,7 +184,7 @@ The following are dependencies for all builds of Solidity:
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| Software | Notes |
|
||||
+===================================+=======================================================+
|
||||
| `CMake`_ (version 3.5+) | Cross-platform build file generator. |
|
||||
| `CMake`_ (version 3.9+) | Cross-platform build file generator. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
| `Boost`_ (version 1.65+) | C++ libraries. |
|
||||
+-----------------------------------+-------------------------------------------------------+
|
||||
|
@ -124,7 +124,7 @@ Encoding of the Metadata Hash in the Bytecode
|
||||
=============================================
|
||||
|
||||
Because we might support other ways to retrieve the metadata file in the future,
|
||||
the mapping ``{"bzzr0": <Swarm hash>, "solc": <compiler version>}`` is stored
|
||||
the mapping ``{"bzzr1": <Swarm hash>, "solc": <compiler version>}`` is stored
|
||||
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
|
||||
contain more keys (see below) and the beginning of that
|
||||
encoding is not easy to find, its length is added in a two-byte big-endian
|
||||
@ -132,7 +132,7 @@ encoding. The current version of the Solidity compiler usually adds the followin
|
||||
to the end of the deployed bytecode::
|
||||
|
||||
0xa2
|
||||
0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash>
|
||||
0x65 'b' 'z' 'z' 'r' '1' 0x58 0x20 <32 bytes swarm hash>
|
||||
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
|
||||
0x00 0x32
|
||||
|
||||
@ -150,9 +150,9 @@ will instead use a complete version string including commit hash and build date.
|
||||
are used, the mapping will also contain ``"experimental": true``.
|
||||
|
||||
.. note::
|
||||
The compiler currently uses the "swarm version 0" hash of the metadata,
|
||||
The compiler currently uses the "swarm version 1" hash of the metadata,
|
||||
but this might change in the future, so do not rely on this sequence
|
||||
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '0'``. We might also
|
||||
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '1'``. We might also
|
||||
add additional data to this CBOR structure, so the
|
||||
best option is to use a proper CBOR parser.
|
||||
|
||||
|
@ -254,6 +254,61 @@ if you want all overflows to cause a revert.
|
||||
|
||||
Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also help you check if values are what you expect.
|
||||
|
||||
.. _clearing-mappings:
|
||||
|
||||
Clearing Mappings
|
||||
=================
|
||||
|
||||
The Solidity type ``mapping`` (see :ref:`mapping-types`) is a storage-only key-value data structure that
|
||||
does not keep track of the keys that were assigned a non-zero value.
|
||||
Because of that, cleaning a mapping without extra information about the written
|
||||
keys is not possible.
|
||||
If a ``mapping`` is used as the base type of a dynamic storage array, deleting
|
||||
or popping the array will have no effect over the ``mapping`` elements.
|
||||
The same happens, for example, if a ``mapping`` is used as the type of a member
|
||||
field of a ``struct`` that is the base type of a dynamic storage array.
|
||||
The ``mapping`` is also ignored in assignments of structs or arrays containing
|
||||
a ``mapping``.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract Map {
|
||||
mapping (uint => uint)[] array;
|
||||
|
||||
function allocate(uint _newMaps) public {
|
||||
array.length += _newMaps;
|
||||
}
|
||||
|
||||
function writeMap(uint _map, uint _key, uint _value) public {
|
||||
array[_map][_key] = _value;
|
||||
}
|
||||
|
||||
function readMap(uint _map, uint _key) public view returns (uint) {
|
||||
return array[_map][_key];
|
||||
}
|
||||
|
||||
function eraseMaps() public {
|
||||
delete array;
|
||||
}
|
||||
}
|
||||
|
||||
Consider the example above and the following sequence of calls: ``allocate(10)``,
|
||||
``writeMap(4, 128, 256)``.
|
||||
At this point, calling ``readMap(4, 128)`` returns 256.
|
||||
If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but
|
||||
since its ``mapping`` elements cannot be zeroed, their information stays alive
|
||||
in the contract's storage.
|
||||
After deleting ``array``, calling ``allocate(5)`` allows us to access
|
||||
``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without
|
||||
another call to ``writeMap``.
|
||||
|
||||
If your ``mapping`` information must be deleted, consider using a library similar to
|
||||
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
|
||||
allowing you to traverse the keys and delete their values in the appropriate
|
||||
``mapping``.
|
||||
|
||||
Minor Details
|
||||
=============
|
||||
|
||||
|
@ -407,7 +407,7 @@ should:
|
||||
* open on the same line as the declaration
|
||||
* close on their own line at the same indentation level as the beginning of the
|
||||
declaration.
|
||||
* The opening brace should be proceeded by a single space.
|
||||
* The opening brace should be preceded by a single space.
|
||||
|
||||
Yes::
|
||||
|
||||
@ -953,7 +953,7 @@ naming styles.
|
||||
* ``mixedCase`` (differs from CapitalizedWords by initial lowercase character!)
|
||||
* ``Capitalized_Words_With_Underscores``
|
||||
|
||||
.. note:: When using initialisms in CapWords, capitalize all the letters of the initialisms. Thus HTTPServerError is better than HttpServerError. When using initialisms is mixedCase, capitalize all the letters of the initialisms, except keep the first one lower case if it is the beginning of the name. Thus xmlHTTPRequest is better than XMLHTTPRequest.
|
||||
.. note:: When using initialisms in CapWords, capitalize all the letters of the initialisms. Thus HTTPServerError is better than HttpServerError. When using initialisms in mixedCase, capitalize all the letters of the initialisms, except keep the first one lower case if it is the beginning of the name. Thus xmlHTTPRequest is better than XMLHTTPRequest.
|
||||
|
||||
|
||||
Names to Avoid
|
||||
@ -1139,6 +1139,6 @@ added looks like the one below::
|
||||
}
|
||||
}
|
||||
|
||||
It is recommended that Solidity contracts are fully annontated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI).
|
||||
It is recommended that Solidity contracts are fully annotated using `NatSpec <natspec>`_ for all public interfaces (everything in the ABI).
|
||||
|
||||
Please see the section about `NatSpec <natspec>`_ for a detailed explanation.
|
||||
Please see the section about `NatSpec <natspec>`_ for a detailed explanation.
|
||||
|
@ -17,7 +17,8 @@ byte-representation is all zeros, a type's :ref:`default value <default-value>`.
|
||||
mapping, only its ``keccak256`` hash is used to look up the value.
|
||||
|
||||
Because of this, mappings do not have a length or a concept of a key or
|
||||
value being set.
|
||||
value being set, and therefore cannot be erased without extra information
|
||||
regarding the assigned keys (see :ref:`clearing-mappings`).
|
||||
|
||||
Mappings can only have a data location of ``storage`` and thus
|
||||
are allowed for state variables, as storage reference types
|
||||
|
@ -110,7 +110,7 @@ Modulo
|
||||
|
||||
The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a``
|
||||
by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo
|
||||
results in the same sign as its left operand (or zero) and ``a % n == -(abs(a) % n)`` holds for negative ``a``:
|
||||
results in the same sign as its left operand (or zero) and ``a % n == -(-a % n)`` holds for negative ``a``:
|
||||
|
||||
* ``int256(5) % int256(2) == int256(1)``
|
||||
* ``int256(5) % int256(-2) == int256(1)``
|
||||
|
@ -122,6 +122,9 @@ at each version. Backward compatibility is not guaranteed between each version.
|
||||
- Shifting operators use shifting opcodes and thus need less gas.
|
||||
- ``petersburg`` (**default**)
|
||||
- The compiler behaves the same way as with constantinople.
|
||||
- ``istanbul``
|
||||
- Opcodes ``chainid`` and ``selfbalance`` are available in assembly.
|
||||
- ``berlin`` (**experimental**)
|
||||
|
||||
|
||||
.. _compiler-api:
|
||||
@ -229,7 +232,7 @@ Input Description
|
||||
},
|
||||
// Version of the EVM to compile for.
|
||||
// Affects type checking and code generation. Can be homestead,
|
||||
// tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg
|
||||
// tangerineWhistle, spuriousDragon, byzantium, constantinople, petersburg, istanbul or berlin
|
||||
"evmVersion": "byzantium",
|
||||
// Metadata settings (optional)
|
||||
"metadata": {
|
||||
@ -363,7 +366,7 @@ Output Description
|
||||
// If the language used has no contract names, this field should equal to an empty string.
|
||||
"ContractName": {
|
||||
// The Ethereum Contract ABI. If empty, it is represented as an empty array.
|
||||
// See https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
||||
// See https://solidity.readthedocs.io/en/develop/abi-spec.html
|
||||
"abi": [],
|
||||
// See the Metadata Output documentation (serialised JSON string)
|
||||
"metadata": "{...}",
|
||||
|
12
docs/yul.rst
12
docs/yul.rst
@ -244,14 +244,14 @@ We will use a destructuring notation for the AST nodes.
|
||||
G1, L1, mode
|
||||
E(G, L, FunctionDefinition) =
|
||||
G, L, regular
|
||||
E(G, L, <let var1, ..., varn := rhs>: VariableDeclaration) =
|
||||
E(G, L, <var1, ..., varn := rhs>: Assignment)
|
||||
E(G, L, <let var1, ..., varn>: VariableDeclaration) =
|
||||
let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n
|
||||
E(G, L, <let var_1, ..., var_n := rhs>: VariableDeclaration) =
|
||||
E(G, L, <var_1, ..., var_n := rhs>: Assignment)
|
||||
E(G, L, <let var_1, ..., var_n>: VariableDeclaration) =
|
||||
let L1 be a copy of L where L1[$var_i] = 0 for i = 1, ..., n
|
||||
G, L1, regular
|
||||
E(G, L, <var1, ..., varn := rhs>: Assignment) =
|
||||
E(G, L, <var_1, ..., var_n := rhs>: Assignment) =
|
||||
let G1, L1, v1, ..., vn = E(G, L, rhs)
|
||||
let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n
|
||||
let L2 be a copy of L1 where L2[$var_i] = vi for i = 1, ..., n
|
||||
G, L2, regular
|
||||
E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
|
||||
if n >= 1:
|
||||
|
@ -78,17 +78,18 @@ private:
|
||||
/**
|
||||
* Generic breadth first search.
|
||||
*
|
||||
* Note that V needs to be a comparable value type. If it is not, use a pointer type,
|
||||
* but note that this might lead to non-deterministic traversal.
|
||||
*
|
||||
* Example: Gather all (recursive) children in a graph starting at (and including) ``root``:
|
||||
*
|
||||
* Node const* root = ...;
|
||||
* std::set<Node> allNodes = BreadthFirstSearch<Node>{{root}}.run([](Node const& _node, auto&& _addChild) {
|
||||
* std::set<Node const*> allNodes = BreadthFirstSearch<Node const*>{{root}}.run([](Node const* _node, auto&& _addChild) {
|
||||
* // Potentially process ``_node``.
|
||||
* for (Node const& _child: _node.children())
|
||||
* for (Node const& _child: _node->children())
|
||||
* // Potentially filter the children to be visited.
|
||||
* _addChild(_child);
|
||||
* _addChild(&_child);
|
||||
* }).visited;
|
||||
*
|
||||
* Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers).
|
||||
*/
|
||||
template<typename V>
|
||||
struct BreadthFirstSearch
|
||||
@ -102,20 +103,20 @@ struct BreadthFirstSearch
|
||||
{
|
||||
while (!verticesToTraverse.empty())
|
||||
{
|
||||
V const* v = *verticesToTraverse.begin();
|
||||
V v = *verticesToTraverse.begin();
|
||||
verticesToTraverse.erase(verticesToTraverse.begin());
|
||||
visited.insert(v);
|
||||
|
||||
_forEachChild(*v, [this](V const& _vertex) {
|
||||
if (!visited.count(&_vertex))
|
||||
verticesToTraverse.insert(&_vertex);
|
||||
_forEachChild(v, [this](V _vertex) {
|
||||
if (!visited.count(_vertex))
|
||||
verticesToTraverse.emplace(std::move(_vertex));
|
||||
});
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::set<V const*> verticesToTraverse;
|
||||
std::set<V const*> visited{};
|
||||
std::set<V> verticesToTraverse;
|
||||
std::set<V> visited{};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,6 @@ set(sources
|
||||
)
|
||||
|
||||
add_library(devcore ${sources})
|
||||
target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system Threads::Threads)
|
||||
target_link_libraries(devcore PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::regex Boost::system)
|
||||
target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||
add_dependencies(devcore solidity_BuildInfo.h)
|
||||
|
@ -98,6 +98,14 @@ inline std::set<T> operator+(std::set<T>&& _a, U&& _b)
|
||||
ret += std::forward<U>(_b);
|
||||
return ret;
|
||||
}
|
||||
/// Remove one set from another one.
|
||||
template <class T>
|
||||
inline std::set<T>& operator-=(std::set<T>& _a, std::set<T> const& _b)
|
||||
{
|
||||
for (auto const& x: _b)
|
||||
_a.erase(x);
|
||||
return _a;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
{
|
||||
|
@ -596,12 +596,14 @@ LinkerObject const& Assembly::assemble() const
|
||||
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
|
||||
break;
|
||||
case PushSub:
|
||||
assertThrow(i.data() <= size_t(-1), AssemblyException, "");
|
||||
ret.bytecode.push_back(dataRefPush);
|
||||
subRef.insert(make_pair(size_t(i.data()), ret.bytecode.size()));
|
||||
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
|
||||
break;
|
||||
case PushSubSize:
|
||||
{
|
||||
assertThrow(i.data() <= size_t(-1), AssemblyException, "");
|
||||
auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size();
|
||||
i.setPushedValue(u256(s));
|
||||
uint8_t b = max<unsigned>(1, dev::bytesRequired(s));
|
||||
|
@ -188,6 +188,12 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
|
||||
case Instruction::BALANCE:
|
||||
gas = GasCosts::balanceGas(m_evmVersion);
|
||||
break;
|
||||
case Instruction::CHAINID:
|
||||
gas = runGas(Instruction::CHAINID);
|
||||
break;
|
||||
case Instruction::SELFBALANCE:
|
||||
gas = runGas(Instruction::SELFBALANCE);
|
||||
break;
|
||||
default:
|
||||
gas = runGas(_item.instruction());
|
||||
break;
|
||||
|
@ -81,6 +81,8 @@ std::map<std::string, Instruction> const dev::eth::c_instructions =
|
||||
{ "NUMBER", Instruction::NUMBER },
|
||||
{ "DIFFICULTY", Instruction::DIFFICULTY },
|
||||
{ "GASLIMIT", Instruction::GASLIMIT },
|
||||
{ "CHAINID", Instruction::CHAINID },
|
||||
{ "SELFBALANCE", Instruction::SELFBALANCE },
|
||||
{ "POP", Instruction::POP },
|
||||
{ "MLOAD", Instruction::MLOAD },
|
||||
{ "MSTORE", Instruction::MSTORE },
|
||||
@ -225,6 +227,8 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
|
||||
{ Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::CHAINID, { "CHAINID", 0, 0, 1, false, Tier::Base } },
|
||||
{ Instruction::SELFBALANCE, { "SELFBALANCE", 0, 0, 1, false, Tier::Low } },
|
||||
{ Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } },
|
||||
{ Instruction::MLOAD, { "MLOAD", 0, 1, 1, true, Tier::VeryLow } },
|
||||
{ Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } },
|
||||
|
@ -90,6 +90,8 @@ enum class Instruction: uint8_t
|
||||
NUMBER, ///< get the block's number
|
||||
DIFFICULTY, ///< get the block's difficulty
|
||||
GASLIMIT, ///< get the block's gas limit
|
||||
CHAINID, ///< get the config's chainid param
|
||||
SELFBALANCE, ///< get balance of the current account
|
||||
|
||||
POP = 0x50, ///< remove item from stack
|
||||
MLOAD, ///< load word from memory
|
||||
|
@ -259,13 +259,19 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
[=]() { return A.d() >= 256; }
|
||||
});
|
||||
|
||||
// Replace BYTE(A, X), A >= 32 with 0
|
||||
rules.push_back({
|
||||
{Instruction::BYTE, {A, X}},
|
||||
[=]() -> Pattern { return u256(0); },
|
||||
true,
|
||||
[=]() { return A.d() >= 32; }
|
||||
});
|
||||
|
||||
for (auto const& op: std::vector<Instruction>{
|
||||
Instruction::ADDRESS,
|
||||
Instruction::CALLER,
|
||||
Instruction::ORIGIN,
|
||||
Instruction::COINBASE,
|
||||
Instruction::CREATE,
|
||||
Instruction::CREATE2
|
||||
Instruction::COINBASE
|
||||
})
|
||||
{
|
||||
u256 const mask = (u256(1) << 160) - 1;
|
||||
@ -280,6 +286,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5(
|
||||
false
|
||||
});
|
||||
}
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
@ -468,7 +475,8 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
[=]() -> Pattern {
|
||||
return {Instruction::SHL, {Y, X}};
|
||||
},
|
||||
false
|
||||
// Actually only changes the order, does not remove.
|
||||
true
|
||||
});
|
||||
rules.push_back({
|
||||
// MUL(SHL(X, 1), Y) -> SHL(X, Y)
|
||||
@ -485,7 +493,8 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
[=]() -> Pattern {
|
||||
return {Instruction::SHR, {Y, X}};
|
||||
},
|
||||
false
|
||||
// Actually only changes the order, does not remove.
|
||||
true
|
||||
});
|
||||
|
||||
std::function<bool()> feasibilityFunction = [=]() {
|
||||
@ -557,8 +566,48 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart8(
|
||||
return rules;
|
||||
}
|
||||
|
||||
template <class Pattern>
|
||||
std::vector<SimplificationRule<Pattern>> simplificationRuleListPart9(
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern,
|
||||
Pattern W,
|
||||
Pattern X,
|
||||
Pattern Y,
|
||||
Pattern Z
|
||||
)
|
||||
{
|
||||
std::vector<SimplificationRule<Pattern>> rules;
|
||||
|
||||
u256 const mask = (u256(1) << 160) - 1;
|
||||
// CREATE
|
||||
rules.push_back({
|
||||
{Instruction::AND, {{Instruction::CREATE, {W, X, Y}}, mask}},
|
||||
[=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; },
|
||||
false
|
||||
});
|
||||
rules.push_back({
|
||||
{Instruction::AND, {{mask, {Instruction::CREATE, {W, X, Y}}}}},
|
||||
[=]() -> Pattern { return {Instruction::CREATE, {W, X, Y}}; },
|
||||
false
|
||||
});
|
||||
// CREATE2
|
||||
rules.push_back({
|
||||
{Instruction::AND, {{Instruction::CREATE2, {W, X, Y, Z}}, mask}},
|
||||
[=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; },
|
||||
false
|
||||
});
|
||||
rules.push_back({
|
||||
{Instruction::AND, {{mask, {Instruction::CREATE2, {W, X, Y, Z}}}}},
|
||||
[=]() -> Pattern { return {Instruction::CREATE2, {W, X, Y, Z}}; },
|
||||
false
|
||||
});
|
||||
|
||||
return rules;
|
||||
}
|
||||
|
||||
/// @returns a list of simplification rules given certain match placeholders.
|
||||
/// A, B and C should represent constants, X and Y arbitrary expressions.
|
||||
/// A, B and C should represent constants, W, X, Y, and Z arbitrary expressions.
|
||||
/// The simplifications should never change the order of evaluation of
|
||||
/// arbitrary operations.
|
||||
template <class Pattern>
|
||||
@ -566,19 +615,22 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
|
||||
Pattern A,
|
||||
Pattern B,
|
||||
Pattern C,
|
||||
Pattern W,
|
||||
Pattern X,
|
||||
Pattern Y
|
||||
Pattern Y,
|
||||
Pattern Z
|
||||
)
|
||||
{
|
||||
std::vector<SimplificationRule<Pattern>> rules;
|
||||
rules += simplificationRuleListPart1(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart2(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart3(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart4(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart5(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart6(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart7(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart8(A, B, C, X, Y);
|
||||
rules += simplificationRuleListPart1(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart2(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart3(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart4(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart5(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart6(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart7(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart8(A, B, C, W, X);
|
||||
rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z);
|
||||
return rules;
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
|
||||
case Instruction::PC:
|
||||
case Instruction::MSIZE: // depends on previous writes and reads, not only on content
|
||||
case Instruction::BALANCE: // depends on previous calls
|
||||
case Instruction::SELFBALANCE: // depends on previous calls
|
||||
case Instruction::EXTCODESIZE:
|
||||
case Instruction::EXTCODEHASH:
|
||||
case Instruction::RETURNDATACOPY: // depends on previous calls
|
||||
@ -194,6 +195,7 @@ bool SemanticInformation::movable(Instruction _instruction)
|
||||
{
|
||||
case Instruction::KECCAK256:
|
||||
case Instruction::BALANCE:
|
||||
case Instruction::SELFBALANCE:
|
||||
case Instruction::EXTCODESIZE:
|
||||
case Instruction::EXTCODEHASH:
|
||||
case Instruction::RETURNDATASIZE:
|
||||
@ -265,6 +267,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
|
||||
switch (_instruction)
|
||||
{
|
||||
case Instruction::ADDRESS:
|
||||
case Instruction::SELFBALANCE:
|
||||
case Instruction::BALANCE:
|
||||
case Instruction::ORIGIN:
|
||||
case Instruction::CALLER:
|
||||
|
@ -83,15 +83,19 @@ Rules::Rules()
|
||||
Pattern B(Push);
|
||||
Pattern C(Push);
|
||||
// Anything.
|
||||
Pattern W;
|
||||
Pattern X;
|
||||
Pattern Y;
|
||||
Pattern Z;
|
||||
A.setMatchGroup(1, m_matchGroups);
|
||||
B.setMatchGroup(2, m_matchGroups);
|
||||
C.setMatchGroup(3, m_matchGroups);
|
||||
X.setMatchGroup(4, m_matchGroups);
|
||||
Y.setMatchGroup(5, m_matchGroups);
|
||||
W.setMatchGroup(4, m_matchGroups);
|
||||
X.setMatchGroup(5, m_matchGroups);
|
||||
Y.setMatchGroup(6, m_matchGroups);
|
||||
Z.setMatchGroup(7, m_matchGroups);
|
||||
|
||||
addRules(simplificationRuleList(A, B, C, X, Y));
|
||||
addRules(simplificationRuleList(A, B, C, W, X, Y, Z));
|
||||
assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized.");
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,6 @@ inline bool isHexDigit(char c)
|
||||
('A' <= c && c <= 'F');
|
||||
}
|
||||
|
||||
inline bool isLineTerminator(char c)
|
||||
{
|
||||
return c == '\n';
|
||||
}
|
||||
|
||||
inline bool isWhiteSpace(char c)
|
||||
{
|
||||
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
|
||||
|
@ -40,6 +40,10 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const
|
||||
return hasCreate2();
|
||||
case Instruction::EXTCODEHASH:
|
||||
return hasExtCodeHash();
|
||||
case Instruction::CHAINID:
|
||||
return hasChainID();
|
||||
case Instruction::SELFBALANCE:
|
||||
return hasSelfBalance();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
@ -48,10 +48,12 @@ public:
|
||||
static EVMVersion byzantium() { return {Version::Byzantium}; }
|
||||
static EVMVersion constantinople() { return {Version::Constantinople}; }
|
||||
static EVMVersion petersburg() { return {Version::Petersburg}; }
|
||||
static EVMVersion istanbul() { return {Version::Istanbul}; }
|
||||
static EVMVersion berlin() { return {Version::Berlin}; }
|
||||
|
||||
static boost::optional<EVMVersion> fromString(std::string const& _version)
|
||||
{
|
||||
for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg()})
|
||||
for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople(), petersburg(), istanbul(), berlin()})
|
||||
if (_version == v.name())
|
||||
return v;
|
||||
return {};
|
||||
@ -70,6 +72,8 @@ public:
|
||||
case Version::Byzantium: return "byzantium";
|
||||
case Version::Constantinople: return "constantinople";
|
||||
case Version::Petersburg: return "petersburg";
|
||||
case Version::Istanbul: return "istanbul";
|
||||
case Version::Berlin: return "berlin";
|
||||
}
|
||||
return "INVALID";
|
||||
}
|
||||
@ -80,6 +84,8 @@ public:
|
||||
bool hasBitwiseShifting() const { return *this >= constantinople(); }
|
||||
bool hasCreate2() const { return *this >= constantinople(); }
|
||||
bool hasExtCodeHash() const { return *this >= constantinople(); }
|
||||
bool hasChainID() const { return *this >= istanbul(); }
|
||||
bool hasSelfBalance() const { return *this >= istanbul(); }
|
||||
|
||||
bool hasOpcode(dev::eth::Instruction _opcode) const;
|
||||
|
||||
@ -88,7 +94,7 @@ public:
|
||||
bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); }
|
||||
|
||||
private:
|
||||
enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg };
|
||||
enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople, Petersburg, Istanbul, Berlin };
|
||||
|
||||
EVMVersion(Version _version): m_version(_version) {}
|
||||
|
||||
|
@ -280,6 +280,29 @@ Token Scanner::skipSingleLineComment()
|
||||
return Token::Whitespace;
|
||||
}
|
||||
|
||||
bool Scanner::atEndOfLine() const
|
||||
{
|
||||
return m_char == '\n' || m_char == '\r';
|
||||
}
|
||||
|
||||
bool Scanner::tryScanEndOfLine()
|
||||
{
|
||||
if (m_char == '\n')
|
||||
{
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_char == '\r')
|
||||
{
|
||||
if (advance() && m_char == '\n')
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Token Scanner::scanSingleLineDocComment()
|
||||
{
|
||||
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
|
||||
@ -289,7 +312,7 @@ Token Scanner::scanSingleLineDocComment()
|
||||
|
||||
while (!isSourcePastEndOfInput())
|
||||
{
|
||||
if (isLineTerminator(m_char))
|
||||
if (tryScanEndOfLine())
|
||||
{
|
||||
// check if next line is also a documentation comment
|
||||
skipWhitespace();
|
||||
@ -303,7 +326,6 @@ Token Scanner::scanSingleLineDocComment()
|
||||
}
|
||||
else
|
||||
break; // next line is not a documentation comment, we are done
|
||||
|
||||
}
|
||||
else if (isUnicodeLinebreak())
|
||||
// Any line terminator that is not '\n' is considered to end the
|
||||
@ -343,13 +365,13 @@ Token Scanner::scanMultiLineDocComment()
|
||||
bool endFound = false;
|
||||
bool charsAdded = false;
|
||||
|
||||
while (isWhiteSpace(m_char) && !isLineTerminator(m_char))
|
||||
while (isWhiteSpace(m_char) && !atEndOfLine())
|
||||
advance();
|
||||
|
||||
while (!isSourcePastEndOfInput())
|
||||
{
|
||||
//handle newlines in multline comments
|
||||
if (isLineTerminator(m_char))
|
||||
if (atEndOfLine())
|
||||
{
|
||||
skipWhitespace();
|
||||
if (!m_source->isPastEndOfInput(1) && m_source->get(0) == '*' && m_source->get(1) == '*')
|
||||
@ -664,10 +686,12 @@ void Scanner::scanToken()
|
||||
bool Scanner::scanEscape()
|
||||
{
|
||||
char c = m_char;
|
||||
advance();
|
||||
|
||||
// Skip escaped newlines.
|
||||
if (isLineTerminator(c))
|
||||
if (tryScanEndOfLine())
|
||||
return true;
|
||||
advance();
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\'': // fall through
|
||||
|
@ -219,6 +219,12 @@ private:
|
||||
Token skipSingleLineComment();
|
||||
Token skipMultiLineComment();
|
||||
|
||||
/// Tests if current source position is CR, LF or CRLF.
|
||||
bool atEndOfLine() const;
|
||||
|
||||
/// Tries to consume CR, LF or CRLF line terminators and returns success or failure.
|
||||
bool tryScanEndOfLine();
|
||||
|
||||
void scanDecimalDigits();
|
||||
Token scanNumber(char _charSeen = 0);
|
||||
std::tuple<Token, unsigned, unsigned> scanIdentifierOrKeyword();
|
||||
|
@ -142,7 +142,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
||||
ssl,
|
||||
string("This variable is of storage pointer type and can be ") +
|
||||
(variableOccurrence->kind() == VariableOccurrence::Kind::Return ? "returned" : "accessed") +
|
||||
" without prior assignment."
|
||||
" without prior assignment, which would lead to undefined behaviour."
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -151,22 +151,22 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
|
||||
{
|
||||
// collect all nodes reachable from the entry point
|
||||
std::set<CFGNode const*> reachable = BreadthFirstSearch<CFGNode>{{_entry}}.run(
|
||||
[](CFGNode const& _node, auto&& _addChild) {
|
||||
for (CFGNode const* exit: _node.exits)
|
||||
_addChild(*exit);
|
||||
std::set<CFGNode const*> reachable = BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||
[](CFGNode const* _node, auto&& _addChild) {
|
||||
for (CFGNode const* exit: _node->exits)
|
||||
_addChild(exit);
|
||||
}
|
||||
).visited;
|
||||
|
||||
// traverse all paths backwards from exit and revert
|
||||
// and extract (valid) source locations of unreachable nodes into sorted set
|
||||
std::set<SourceLocation> unreachable;
|
||||
BreadthFirstSearch<CFGNode>{{_exit, _revert}}.run(
|
||||
[&](CFGNode const& _node, auto&& _addChild) {
|
||||
if (!reachable.count(&_node) && !_node.location.isEmpty())
|
||||
unreachable.insert(_node.location);
|
||||
for (CFGNode const* entry: _node.entries)
|
||||
_addChild(*entry);
|
||||
BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
|
||||
[&](CFGNode const* _node, auto&& _addChild) {
|
||||
if (!reachable.count(_node) && !_node->location.isEmpty())
|
||||
unreachable.insert(_node->location);
|
||||
for (CFGNode const* entry: _node->entries)
|
||||
_addChild(entry);
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -91,13 +91,13 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
if (!imp->symbolAliases().empty())
|
||||
for (auto const& alias: imp->symbolAliases())
|
||||
{
|
||||
auto declarations = scope->second->resolveName(alias.first->name(), false);
|
||||
auto declarations = scope->second->resolveName(alias.symbol->name(), false);
|
||||
if (declarations.empty())
|
||||
{
|
||||
m_errorReporter.declarationError(
|
||||
imp->location(),
|
||||
"Declaration \"" +
|
||||
alias.first->name() +
|
||||
alias.symbol->name() +
|
||||
"\" not found in \"" +
|
||||
path +
|
||||
"\" (referenced as \"" +
|
||||
@ -109,7 +109,7 @@ bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, So
|
||||
else
|
||||
for (Declaration const* declaration: declarations)
|
||||
if (!DeclarationRegistrationHelper::registerDeclaration(
|
||||
target, *declaration, alias.second.get(), &imp->location(), true, false, m_errorReporter
|
||||
target, *declaration, alias.alias.get(), &alias.location, true, false, m_errorReporter
|
||||
))
|
||||
error = true;
|
||||
}
|
||||
@ -523,7 +523,7 @@ bool DeclarationRegistrationHelper::registerDeclaration(
|
||||
{
|
||||
if (dynamic_cast<MagicVariableDeclaration const*>(shadowedDeclaration))
|
||||
_errorReporter.warning(
|
||||
_declaration.location(),
|
||||
*_errorLocation,
|
||||
"This declaration shadows a builtin symbol."
|
||||
);
|
||||
else
|
||||
|
@ -262,10 +262,7 @@ bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
if (!m_useYulOptimizer)
|
||||
return false;
|
||||
|
||||
if (yul::SideEffectsCollector(
|
||||
_inlineAssembly.dialect(),
|
||||
_inlineAssembly.operations()
|
||||
).containsMSize())
|
||||
if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations()))
|
||||
m_errorReporter.syntaxError(
|
||||
_inlineAssembly.location(),
|
||||
"The msize instruction cannot be used when the Yul optimizer is activated because "
|
||||
|
@ -2515,8 +2515,47 @@ void TypeChecker::requireLValue(Expression const& _expression)
|
||||
_expression.annotation().lValueRequested = true;
|
||||
_expression.accept(*this);
|
||||
|
||||
if (_expression.annotation().isConstant)
|
||||
m_errorReporter.typeError(_expression.location(), "Cannot assign to a constant variable.");
|
||||
else if (!_expression.annotation().isLValue)
|
||||
m_errorReporter.typeError(_expression.location(), "Expression has to be an lvalue.");
|
||||
if (_expression.annotation().isLValue)
|
||||
return;
|
||||
|
||||
return m_errorReporter.typeError(_expression.location(), [&]() {
|
||||
if (_expression.annotation().isConstant)
|
||||
return "Cannot assign to a constant variable.";
|
||||
|
||||
if (auto indexAccess = dynamic_cast<IndexAccess const*>(&_expression))
|
||||
{
|
||||
if (type(indexAccess->baseExpression())->category() == Type::Category::FixedBytes)
|
||||
return "Single bytes in fixed bytes arrays cannot be modified.";
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(indexAccess->baseExpression())))
|
||||
if (arrayType->dataStoredIn(DataLocation::CallData))
|
||||
return "Calldata arrays are read-only.";
|
||||
}
|
||||
|
||||
if (auto memberAccess = dynamic_cast<MemberAccess const*>(&_expression))
|
||||
{
|
||||
if (auto structType = dynamic_cast<StructType const*>(type(memberAccess->expression())))
|
||||
{
|
||||
if (structType->dataStoredIn(DataLocation::CallData))
|
||||
return "Calldata structs are read-only.";
|
||||
}
|
||||
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
|
||||
if (memberAccess->memberName() == "length")
|
||||
switch (arrayType->location())
|
||||
{
|
||||
case DataLocation::Memory:
|
||||
return "Memory arrays cannot be resized.";
|
||||
case DataLocation::CallData:
|
||||
return "Calldata arrays cannot be resized.";
|
||||
case DataLocation::Storage:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto identifier = dynamic_cast<Identifier const*>(&_expression))
|
||||
if (auto varDecl = dynamic_cast<VariableDeclaration const*>(identifier->annotation().referencedDeclaration))
|
||||
if (varDecl->isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(identifier->annotation().type))
|
||||
return "External function arguments of reference type are read-only.";
|
||||
|
||||
return "Expression has to be an lvalue.";
|
||||
}());
|
||||
}
|
||||
|
@ -432,8 +432,13 @@ string Scopable::sourceUnitName() const
|
||||
|
||||
bool VariableDeclaration::isLValue() const
|
||||
{
|
||||
// External function parameters and constant declared variables are Read-Only
|
||||
return !isExternalCallableParameter() && !m_isConstant;
|
||||
// Constant declared variables are Read-Only
|
||||
if (m_isConstant)
|
||||
return false;
|
||||
// External function arguments of reference type are Read-Only
|
||||
if (isExternalCallableParameter() && dynamic_cast<ReferenceType const*>(type()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VariableDeclaration::isLocalVariable() const
|
||||
|
@ -280,22 +280,31 @@ private:
|
||||
class ImportDirective: public Declaration
|
||||
{
|
||||
public:
|
||||
struct SymbolAlias
|
||||
{
|
||||
ASTPointer<Identifier> symbol;
|
||||
ASTPointer<ASTString> alias;
|
||||
SourceLocation location;
|
||||
};
|
||||
|
||||
using SymbolAliasList = std::vector<SymbolAlias>;
|
||||
|
||||
ImportDirective(
|
||||
SourceLocation const& _location,
|
||||
ASTPointer<ASTString> const& _path,
|
||||
ASTPointer<ASTString> const& _unitAlias,
|
||||
std::vector<std::pair<ASTPointer<Identifier>, ASTPointer<ASTString>>>&& _symbolAliases
|
||||
SymbolAliasList _symbolAliases
|
||||
):
|
||||
Declaration(_location, _unitAlias),
|
||||
m_path(_path),
|
||||
m_symbolAliases(_symbolAliases)
|
||||
m_symbolAliases(move(_symbolAliases))
|
||||
{ }
|
||||
|
||||
void accept(ASTVisitor& _visitor) override;
|
||||
void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
ASTString const& path() const { return *m_path; }
|
||||
std::vector<std::pair<ASTPointer<Identifier>, ASTPointer<ASTString>>> const& symbolAliases() const
|
||||
SymbolAliasList const& symbolAliases() const
|
||||
{
|
||||
return m_symbolAliases;
|
||||
}
|
||||
@ -306,9 +315,9 @@ public:
|
||||
private:
|
||||
ASTPointer<ASTString> m_path;
|
||||
/// The aliases for the specific symbols to import. If non-empty import the specific symbols.
|
||||
/// If the second component is empty, import the identifier unchanged.
|
||||
/// If the `alias` component is empty, import the identifier unchanged.
|
||||
/// If both m_unitAlias and m_symbolAlias are empty, import all symbols into the current scope.
|
||||
std::vector<std::pair<ASTPointer<Identifier>, ASTPointer<ASTString>>> m_symbolAliases;
|
||||
SymbolAliasList m_symbolAliases;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -27,6 +27,9 @@
|
||||
#include <libdevcore/UTF8.h>
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
|
||||
@ -240,9 +243,9 @@ bool ASTJsonConverter::visit(ImportDirective const& _node)
|
||||
for (auto const& symbolAlias: _node.symbolAliases())
|
||||
{
|
||||
Json::Value tuple(Json::objectValue);
|
||||
solAssert(symbolAlias.first, "");
|
||||
tuple["foreign"] = nodeId(*symbolAlias.first);
|
||||
tuple["local"] = symbolAlias.second ? Json::Value(*symbolAlias.second) : Json::nullValue;
|
||||
solAssert(symbolAlias.symbol, "");
|
||||
tuple["foreign"] = nodeId(*symbolAlias.symbol);
|
||||
tuple["local"] = symbolAlias.alias ? Json::Value(*symbolAlias.alias) : Json::nullValue;
|
||||
symbolAliases.append(tuple);
|
||||
}
|
||||
attributes.emplace_back("symbolAliases", std::move(symbolAliases));
|
||||
@ -259,7 +262,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
|
||||
make_pair("fullyImplemented", _node.annotation().unimplementedFunctions.empty()),
|
||||
make_pair("linearizedBaseContracts", getContainerIds(_node.annotation().linearizedBaseContracts)),
|
||||
make_pair("baseContracts", toJson(_node.baseContracts())),
|
||||
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies)),
|
||||
make_pair("contractDependencies", getContainerIds(_node.annotation().contractDependencies, true)),
|
||||
make_pair("nodes", toJson(_node.subNodes())),
|
||||
make_pair("scope", idOrNull(_node.scope()))
|
||||
});
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <json/json.h>
|
||||
#include <ostream>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
@ -148,15 +150,23 @@ private:
|
||||
return _node.id();
|
||||
}
|
||||
template<class Container>
|
||||
static Json::Value getContainerIds(Container const& container)
|
||||
static Json::Value getContainerIds(Container const& _container, bool _order = false)
|
||||
{
|
||||
Json::Value tmp(Json::arrayValue);
|
||||
for (auto const& element: container)
|
||||
std::vector<int> tmp;
|
||||
|
||||
for (auto const& element: _container)
|
||||
{
|
||||
solAssert(element, "");
|
||||
tmp.append(nodeId(*element));
|
||||
tmp.push_back(nodeId(*element));
|
||||
}
|
||||
return tmp;
|
||||
if (_order)
|
||||
std::sort(tmp.begin(), tmp.end());
|
||||
Json::Value json(Json::arrayValue);
|
||||
|
||||
for (int val: tmp)
|
||||
json.append(val);
|
||||
|
||||
return json;
|
||||
}
|
||||
static Json::Value typePointerToJson(TypePointer _tp, bool _short = false);
|
||||
static Json::Value typePointerToJson(boost::optional<FuncCallArguments> const& _tps);
|
||||
|
@ -2050,6 +2050,8 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
|
||||
|
||||
bool StructType::isDynamicallyEncoded() const
|
||||
{
|
||||
if (recursive())
|
||||
return true;
|
||||
solAssert(interfaceType(false).get(), "");
|
||||
for (auto t: memoryMemberTypes())
|
||||
{
|
||||
|
@ -896,11 +896,16 @@ void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const
|
||||
// Stack: ArrayReference oldLength
|
||||
m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB;
|
||||
// Stack ArrayReference newLength
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
// Stack ArrayReference newLength ArrayReference newLength;
|
||||
accessIndex(_type, false);
|
||||
// Stack: ArrayReference newLength storage_slot byte_offset
|
||||
StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
|
||||
|
||||
if (_type.baseType()->category() != Type::Category::Mapping)
|
||||
{
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
// Stack ArrayReference newLength ArrayReference newLength;
|
||||
accessIndex(_type, false);
|
||||
// Stack: ArrayReference newLength storage_slot byte_offset
|
||||
StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
|
||||
}
|
||||
|
||||
// Stack: ArrayReference newLength
|
||||
m_context << Instruction::SWAP1 << Instruction::SSTORE;
|
||||
}
|
||||
|
@ -78,8 +78,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
|
||||
void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(_varDecl.isConstant(), "");
|
||||
_varDecl.value()->accept(*this);
|
||||
utils().convertType(*_varDecl.value()->annotation().type, *_varDecl.annotation().type);
|
||||
acceptAndConvert(*_varDecl.value(), *_varDecl.annotation().type);
|
||||
|
||||
// append return
|
||||
m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1);
|
||||
@ -225,14 +224,12 @@ bool ExpressionCompiler::visit(Conditional const& _condition)
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _condition);
|
||||
_condition.condition().accept(*this);
|
||||
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
|
||||
_condition.falseExpression().accept(*this);
|
||||
utils().convertType(*_condition.falseExpression().annotation().type, *_condition.annotation().type);
|
||||
acceptAndConvert(_condition.falseExpression(), *_condition.annotation().type);
|
||||
eth::AssemblyItem endTag = m_context.appendJumpToNew();
|
||||
m_context << trueTag;
|
||||
int offset = _condition.annotation().type->sizeOnStack();
|
||||
m_context.adjustStackOffset(-offset);
|
||||
_condition.trueExpression().accept(*this);
|
||||
utils().convertType(*_condition.trueExpression().annotation().type, *_condition.annotation().type);
|
||||
acceptAndConvert(_condition.trueExpression(), *_condition.annotation().type);
|
||||
m_context << endTag;
|
||||
return false;
|
||||
}
|
||||
@ -322,8 +319,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
|
||||
for (auto const& component: _tuple.components())
|
||||
{
|
||||
component->accept(*this);
|
||||
utils().convertType(*component->annotation().type, *arrayType.baseType(), true);
|
||||
acceptAndConvert(*component, *arrayType.baseType(), true);
|
||||
utils().storeInMemoryDynamic(*arrayType.baseType(), true);
|
||||
}
|
||||
|
||||
@ -451,17 +447,13 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
|
||||
bool swap = m_optimiseOrderLiterals && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression);
|
||||
if (swap)
|
||||
{
|
||||
leftExpression.accept(*this);
|
||||
utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
|
||||
rightExpression.accept(*this);
|
||||
utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
|
||||
acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded);
|
||||
acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded);
|
||||
}
|
||||
else
|
||||
{
|
||||
rightExpression.accept(*this);
|
||||
utils().convertType(*rightExpression.annotation().type, *rightTargetType, cleanupNeeded);
|
||||
leftExpression.accept(*this);
|
||||
utils().convertType(*leftExpression.annotation().type, *leftTargetType, cleanupNeeded);
|
||||
acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded);
|
||||
acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded);
|
||||
}
|
||||
if (TokenTraits::isShiftOp(c_op))
|
||||
// shift only cares about the signedness of both sides
|
||||
@ -483,9 +475,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
solAssert(_functionCall.arguments().size() == 1, "");
|
||||
solAssert(_functionCall.names().empty(), "");
|
||||
Expression const& firstArgument = *_functionCall.arguments().front();
|
||||
firstArgument.accept(*this);
|
||||
utils().convertType(*firstArgument.annotation().type, *_functionCall.annotation().type);
|
||||
acceptAndConvert(*_functionCall.arguments().front(), *_functionCall.annotation().type);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -531,8 +521,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
arguments[i]->accept(*this);
|
||||
utils().convertType(*arguments[i]->annotation().type, *functionType->parameterTypes()[i]);
|
||||
acceptAndConvert(*arguments[i], *functionType->parameterTypes()[i]);
|
||||
utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]);
|
||||
}
|
||||
m_context << Instruction::POP;
|
||||
@ -552,10 +541,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
|
||||
eth::AssemblyItem returnLabel = m_context.pushNewTag();
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
arguments[i]->accept(*this);
|
||||
utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]);
|
||||
}
|
||||
acceptAndConvert(*arguments[i], *function.parameterTypes()[i]);
|
||||
|
||||
{
|
||||
bool shortcutTaken = false;
|
||||
@ -647,8 +633,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// stack layout: contract_address function_id [gas] [value]
|
||||
_functionCall.expression().accept(*this);
|
||||
|
||||
arguments.front()->accept(*this);
|
||||
utils().convertType(*arguments.front()->annotation().type, *TypeProvider::uint256(), true);
|
||||
acceptAndConvert(*arguments.front(), *TypeProvider::uint256(), true);
|
||||
// Note that function is not the original function, but the ".gas" function.
|
||||
// Its values of gasSet and valueSet is equal to the original function's though.
|
||||
unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0);
|
||||
@ -673,11 +658,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// Provide the gas stipend manually at first because we may send zero ether.
|
||||
// Will be zeroed if we send more than zero ether.
|
||||
m_context << u256(eth::GasCosts::callStipend);
|
||||
arguments.front()->accept(*this);
|
||||
utils().convertType(
|
||||
*arguments.front()->annotation().type,
|
||||
*function.parameterTypes().front(), true
|
||||
);
|
||||
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true);
|
||||
// gas <- gas * !value
|
||||
m_context << Instruction::SWAP1 << Instruction::DUP2;
|
||||
m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1;
|
||||
@ -705,8 +686,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
}
|
||||
break;
|
||||
case FunctionType::Kind::Selfdestruct:
|
||||
arguments.front()->accept(*this);
|
||||
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
|
||||
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true);
|
||||
m_context << Instruction::SELFDESTRUCT;
|
||||
break;
|
||||
case FunctionType::Kind::Revert:
|
||||
@ -754,10 +734,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0);
|
||||
for (unsigned arg = logNumber; arg > 0; --arg)
|
||||
{
|
||||
arguments[arg]->accept(*this);
|
||||
utils().convertType(*arguments[arg]->annotation().type, *function.parameterTypes()[arg], true);
|
||||
}
|
||||
acceptAndConvert(*arguments[arg], *function.parameterTypes()[arg], true);
|
||||
arguments.front()->accept(*this);
|
||||
utils().fetchFreeMemoryPointer();
|
||||
solAssert(function.parameterTypes().front()->isValueType(), "");
|
||||
@ -827,23 +804,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
}
|
||||
case FunctionType::Kind::BlockHash:
|
||||
{
|
||||
arguments[0]->accept(*this);
|
||||
utils().convertType(*arguments[0]->annotation().type, *function.parameterTypes()[0], true);
|
||||
acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true);
|
||||
m_context << Instruction::BLOCKHASH;
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::AddMod:
|
||||
case FunctionType::Kind::MulMod:
|
||||
{
|
||||
arguments[2]->accept(*this);
|
||||
utils().convertType(*arguments[2]->annotation().type, *TypeProvider::uint256());
|
||||
acceptAndConvert(*arguments[2], *TypeProvider::uint256());
|
||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||
m_context.appendConditionalInvalid();
|
||||
for (unsigned i = 1; i < 3; i ++)
|
||||
{
|
||||
arguments[2 - i]->accept(*this);
|
||||
utils().convertType(*arguments[2 - i]->annotation().type, *TypeProvider::uint256());
|
||||
}
|
||||
acceptAndConvert(*arguments[2 - i], *TypeProvider::uint256());
|
||||
if (function.kind() == FunctionType::Kind::AddMod)
|
||||
m_context << Instruction::ADDMOD;
|
||||
else
|
||||
@ -927,8 +899,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
solAssert(arguments.size() == 1, "");
|
||||
|
||||
// Fetch requested length.
|
||||
arguments[0]->accept(*this);
|
||||
utils().convertType(*arguments[0]->annotation().type, *TypeProvider::uint256());
|
||||
acceptAndConvert(*arguments[0], *TypeProvider::uint256());
|
||||
|
||||
// Stack: requested_length
|
||||
utils().fetchFreeMemoryPointer();
|
||||
@ -967,8 +938,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
case FunctionType::Kind::Assert:
|
||||
case FunctionType::Kind::Require:
|
||||
{
|
||||
arguments.front()->accept(*this);
|
||||
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
|
||||
acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false);
|
||||
if (arguments.size() > 1)
|
||||
{
|
||||
// Users probably expect the second argument to be evaluated
|
||||
@ -1149,12 +1119,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
if (auto funType = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type))
|
||||
if (funType->bound())
|
||||
{
|
||||
_memberAccess.expression().accept(*this);
|
||||
utils().convertType(
|
||||
*_memberAccess.expression().annotation().type,
|
||||
*funType->selfType(),
|
||||
true
|
||||
);
|
||||
acceptAndConvert(_memberAccess.expression(), *funType->selfType(), true);
|
||||
if (funType->kind() == FunctionType::Kind::Internal)
|
||||
{
|
||||
FunctionDefinition const& funDef = dynamic_cast<decltype(funDef)>(funType->declaration());
|
||||
@ -1568,8 +1533,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
_indexAccess.indexExpression()->accept(*this);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true);
|
||||
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
|
||||
// stack layout: <base_ref> [<length>] <index>
|
||||
switch (arrayType.location())
|
||||
{
|
||||
@ -1597,8 +1561,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
_indexAccess.indexExpression()->accept(*this);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, *TypeProvider::uint256(), true);
|
||||
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
|
||||
// stack layout: <value> <index>
|
||||
// check out-of-bounds access
|
||||
m_context << u256(fixedBytesType.numBytes());
|
||||
@ -2207,8 +2170,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)
|
||||
{
|
||||
solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types.");
|
||||
_expression.accept(*this);
|
||||
utils().convertType(*_expression.annotation().type, _expectedType, true);
|
||||
acceptAndConvert(_expression, _expectedType, true);
|
||||
utils().storeInMemoryDynamic(_expectedType);
|
||||
}
|
||||
|
||||
@ -2217,10 +2179,7 @@ void ExpressionCompiler::appendVariable(VariableDeclaration const& _variable, Ex
|
||||
if (!_variable.isConstant())
|
||||
setLValueFromDeclaration(_variable, _expression);
|
||||
else
|
||||
{
|
||||
_variable.value()->accept(*this);
|
||||
utils().convertType(*_variable.value()->annotation().type, *_variable.annotation().type);
|
||||
}
|
||||
acceptAndConvert(*_variable.value(), *_variable.annotation().type);
|
||||
}
|
||||
|
||||
void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaration, Expression const& _expression)
|
||||
@ -2252,6 +2211,12 @@ bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token _op)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExpressionCompiler::acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded)
|
||||
{
|
||||
_expression.accept(*this);
|
||||
utils().convertType(*_expression.annotation().type, _type, _cleanupNeeded);
|
||||
}
|
||||
|
||||
CompilerUtils ExpressionCompiler::utils()
|
||||
{
|
||||
return CompilerUtils(m_context);
|
||||
|
@ -121,6 +121,8 @@ private:
|
||||
/// operation.
|
||||
static bool cleanupNeededForOp(Type::Category _type, Token _op);
|
||||
|
||||
void acceptAndConvert(Expression const& _expression, Type const& _type, bool _cleanupNeeded = false);
|
||||
|
||||
/// @returns the CompilerUtils object containing the current context.
|
||||
CompilerUtils utils();
|
||||
|
||||
|
@ -74,8 +74,10 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
else
|
||||
m_stateSorts.push_back(smt::smtSort(*var->type()));
|
||||
|
||||
clearIndices();
|
||||
|
||||
string interfaceName = "interface_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
m_interfacePredicate = createBlock(interfaceSort(), interfaceName);
|
||||
m_interfacePredicate = createSymbolicBlock(interfaceSort(), interfaceName);
|
||||
|
||||
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
@ -83,7 +85,7 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
vector<smt::SortPointer>(),
|
||||
boolSort
|
||||
);
|
||||
m_errorPredicate = createBlock(errorFunctionSort, "error");
|
||||
m_errorPredicate = createSymbolicBlock(errorFunctionSort, "error");
|
||||
|
||||
// If the contract has a constructor it is handled as a function.
|
||||
// Otherwise we zero-initialize all state vars.
|
||||
@ -91,7 +93,9 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
if (!_contract.constructor())
|
||||
{
|
||||
string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
m_constructorPredicate = createBlock(constructorSort(), constructorName);
|
||||
m_constructorPredicate = createSymbolicBlock(constructorSort(), constructorName);
|
||||
smt::Expression constructorPred = (*m_constructorPredicate)({});
|
||||
addRule(constructorPred, constructorName);
|
||||
|
||||
for (auto const& var: m_stateVariables)
|
||||
{
|
||||
@ -101,14 +105,7 @@ bool CHC::visit(ContractDefinition const& _contract)
|
||||
m_context.setZeroValue(*symbVar);
|
||||
}
|
||||
|
||||
smt::Expression constructorAppl = (*m_constructorPredicate)({});
|
||||
m_interface->addRule(constructorAppl, constructorName);
|
||||
|
||||
smt::Expression constructorInterface = smt::Expression::implies(
|
||||
constructorAppl && m_context.assertions(),
|
||||
interface()
|
||||
);
|
||||
m_interface->addRule(constructorInterface, constructorName + "_to_" + interfaceName);
|
||||
connectBlocks(constructorPred, interface());
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -119,10 +116,13 @@ void CHC::endVisit(ContractDefinition const& _contract)
|
||||
if (!shouldVisit(_contract))
|
||||
return;
|
||||
|
||||
auto errorAppl = (*m_errorPredicate)({});
|
||||
for (auto const& target: m_verificationTargets)
|
||||
for (unsigned i = 0; i < m_verificationTargets.size(); ++i)
|
||||
{
|
||||
auto const& target = m_verificationTargets.at(i);
|
||||
auto errorAppl = error(i + 1);
|
||||
if (query(errorAppl, target->location()))
|
||||
m_safeAssertions.insert(target);
|
||||
}
|
||||
|
||||
SMTEncoder::endVisit(_contract);
|
||||
}
|
||||
@ -135,6 +135,30 @@ bool CHC::visit(FunctionDefinition const& _function)
|
||||
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented");
|
||||
m_currentFunction = &_function;
|
||||
|
||||
initFunction(_function);
|
||||
|
||||
// Store the constraints related to variable initialization.
|
||||
smt::Expression const& initAssertions = m_context.assertions();
|
||||
m_context.pushSolver();
|
||||
|
||||
solAssert(m_functionBlocks == 0, "");
|
||||
|
||||
createBlock(m_currentFunction);
|
||||
createBlock(&m_currentFunction->body(), "block_");
|
||||
|
||||
auto functionPred = predicate(m_currentFunction);
|
||||
auto bodyPred = predicate(&m_currentFunction->body());
|
||||
|
||||
connectBlocks(interface(), functionPred);
|
||||
connectBlocks(functionPred, bodyPred);
|
||||
|
||||
m_context.popSolver();
|
||||
|
||||
pushBlock(&m_currentFunction->body());
|
||||
|
||||
// We need to re-add the constraints that were created for initialization of variables.
|
||||
m_context.addAssertion(initAssertions);
|
||||
|
||||
SMTEncoder::visit(*m_currentFunction);
|
||||
|
||||
return false;
|
||||
@ -146,7 +170,23 @@ void CHC::endVisit(FunctionDefinition const& _function)
|
||||
return;
|
||||
|
||||
solAssert(m_currentFunction == &_function, "Inlining internal function calls not yet implemented");
|
||||
|
||||
// Function Exit block.
|
||||
createBlock(m_currentFunction);
|
||||
connectBlocks(m_path.back(), predicate(&_function));
|
||||
|
||||
// Rule FunctionExit -> Interface, uses no constraints.
|
||||
clearIndices();
|
||||
m_context.pushSolver();
|
||||
connectBlocks(predicate(&_function), interface());
|
||||
m_context.popSolver();
|
||||
|
||||
m_currentFunction = nullptr;
|
||||
solAssert(m_path.size() == m_functionBlocks, "");
|
||||
while (m_functionBlocks > 0)
|
||||
popBlock();
|
||||
|
||||
solAssert(m_path.empty(), "");
|
||||
|
||||
SMTEncoder::endVisit(_function);
|
||||
}
|
||||
@ -155,8 +195,66 @@ bool CHC::visit(IfStatement const& _if)
|
||||
{
|
||||
solAssert(m_currentFunction, "");
|
||||
|
||||
bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen;
|
||||
m_unknownFunctionCallSeen = false;
|
||||
|
||||
SMTEncoder::visit(_if);
|
||||
|
||||
if (m_unknownFunctionCallSeen)
|
||||
eraseKnowledge();
|
||||
|
||||
m_unknownFunctionCallSeen = unknownFunctionCallWasSeen;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHC::visit(WhileStatement const& _while)
|
||||
{
|
||||
bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen;
|
||||
m_unknownFunctionCallSeen = false;
|
||||
|
||||
solAssert(m_currentFunction, "");
|
||||
|
||||
if (_while.isDoWhile())
|
||||
_while.body().accept(*this);
|
||||
|
||||
visitLoop(
|
||||
_while,
|
||||
&_while.condition(),
|
||||
_while.body(),
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (m_unknownFunctionCallSeen)
|
||||
eraseKnowledge();
|
||||
|
||||
m_unknownFunctionCallSeen = unknownFunctionCallWasSeen;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHC::visit(ForStatement const& _for)
|
||||
{
|
||||
bool unknownFunctionCallWasSeen = m_unknownFunctionCallSeen;
|
||||
m_unknownFunctionCallSeen = false;
|
||||
|
||||
solAssert(m_currentFunction, "");
|
||||
|
||||
if (auto init = _for.initializationExpression())
|
||||
init->accept(*this);
|
||||
|
||||
visitLoop(
|
||||
_for,
|
||||
_for.condition(),
|
||||
_for.body(),
|
||||
_for.loopExpression()
|
||||
);
|
||||
|
||||
if (m_unknownFunctionCallSeen)
|
||||
eraseKnowledge();
|
||||
|
||||
m_unknownFunctionCallSeen = unknownFunctionCallWasSeen;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -164,20 +262,162 @@ void CHC::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
|
||||
|
||||
if (_funCall.annotation().kind == FunctionCallKind::FunctionCall)
|
||||
if (_funCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
{
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
if (funType.kind() == FunctionType::Kind::Assert)
|
||||
visitAssert(_funCall);
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
return;
|
||||
}
|
||||
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
switch (funType.kind())
|
||||
{
|
||||
case FunctionType::Kind::Assert:
|
||||
visitAssert(_funCall);
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Internal:
|
||||
case FunctionType::Kind::External:
|
||||
case FunctionType::Kind::DelegateCall:
|
||||
case FunctionType::Kind::BareCall:
|
||||
case FunctionType::Kind::BareCallCode:
|
||||
case FunctionType::Kind::BareDelegateCall:
|
||||
case FunctionType::Kind::BareStaticCall:
|
||||
case FunctionType::Kind::Creation:
|
||||
case FunctionType::Kind::KECCAK256:
|
||||
case FunctionType::Kind::ECRecover:
|
||||
case FunctionType::Kind::SHA256:
|
||||
case FunctionType::Kind::RIPEMD160:
|
||||
case FunctionType::Kind::BlockHash:
|
||||
case FunctionType::Kind::AddMod:
|
||||
case FunctionType::Kind::MulMod:
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
unknownFunctionCall(_funCall);
|
||||
break;
|
||||
default:
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
break;
|
||||
}
|
||||
|
||||
createReturnedExpressions(_funCall);
|
||||
}
|
||||
|
||||
void CHC::visitAssert(FunctionCall const&)
|
||||
void CHC::endVisit(Break const&)
|
||||
{
|
||||
solAssert(m_breakDest, "");
|
||||
m_breakSeen = true;
|
||||
}
|
||||
|
||||
void CHC::endVisit(Continue const&)
|
||||
{
|
||||
solAssert(m_continueDest, "");
|
||||
m_continueSeen = true;
|
||||
}
|
||||
|
||||
void CHC::visitAssert(FunctionCall const& _funCall)
|
||||
{
|
||||
auto const& args = _funCall.arguments();
|
||||
solAssert(args.size() == 1, "");
|
||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||
|
||||
solAssert(!m_path.empty(), "");
|
||||
|
||||
createErrorBlock();
|
||||
|
||||
smt::Expression assertNeg = !(m_context.expression(*args.front())->currentValue());
|
||||
connectBlocks(m_path.back(), error(), currentPathConditions() && assertNeg);
|
||||
|
||||
m_verificationTargets.push_back(&_funCall);
|
||||
}
|
||||
|
||||
void CHC::unknownFunctionCall(FunctionCall const&)
|
||||
{
|
||||
/// Function calls are not handled at the moment,
|
||||
/// so always erase knowledge.
|
||||
/// TODO remove when function calls get predicates/blocks.
|
||||
eraseKnowledge();
|
||||
|
||||
/// Used to erase outer scope knowledge in loops and ifs.
|
||||
/// TODO remove when function calls get predicates/blocks.
|
||||
m_unknownFunctionCallSeen = true;
|
||||
}
|
||||
|
||||
void CHC::visitLoop(
|
||||
BreakableStatement const& _loop,
|
||||
Expression const* _condition,
|
||||
Statement const& _body,
|
||||
ASTNode const* _postLoop
|
||||
)
|
||||
{
|
||||
bool breakWasSeen = m_breakSeen;
|
||||
bool continueWasSeen = m_continueSeen;
|
||||
m_breakSeen = false;
|
||||
m_continueSeen = false;
|
||||
|
||||
solAssert(m_currentFunction, "");
|
||||
auto const& functionBody = m_currentFunction->body();
|
||||
|
||||
createBlock(&_loop, "loop_header_");
|
||||
createBlock(&_body, "loop_body_");
|
||||
createBlock(&functionBody, "block_");
|
||||
|
||||
connectBlocks(m_path.back(), predicate(&_loop));
|
||||
|
||||
// We need to save the next block here because new blocks
|
||||
// might be created inside the loop body.
|
||||
// This will be m_path.back() in the end of this function.
|
||||
pushBlock(&functionBody);
|
||||
|
||||
smt::Expression loopHeader = predicate(&_loop);
|
||||
pushBlock(&_loop);
|
||||
|
||||
if (_condition)
|
||||
_condition->accept(*this);
|
||||
auto condition = _condition ? expr(*_condition) : smt::Expression(true);
|
||||
|
||||
connectBlocks(loopHeader, predicate(&_body), condition);
|
||||
connectBlocks(loopHeader, predicate(&functionBody), !condition);
|
||||
|
||||
// Loop body visit.
|
||||
pushBlock(&_body);
|
||||
|
||||
m_breakDest = &functionBody;
|
||||
m_continueDest = _postLoop ? _postLoop : &_loop;
|
||||
|
||||
auto functionBlocks = m_functionBlocks;
|
||||
_body.accept(*this);
|
||||
if (_postLoop)
|
||||
{
|
||||
createBlock(_postLoop, "loop_post_");
|
||||
connectBlocks(m_path.back(), predicate(_postLoop));
|
||||
pushBlock(_postLoop);
|
||||
_postLoop->accept(*this);
|
||||
}
|
||||
|
||||
// Back edge.
|
||||
connectBlocks(m_path.back(), predicate(&_loop));
|
||||
|
||||
// Pop all function blocks created by nested inner loops
|
||||
// to adjust the assertion context.
|
||||
for (unsigned i = m_functionBlocks; i > functionBlocks; --i)
|
||||
popBlock();
|
||||
m_functionBlocks = functionBlocks;
|
||||
|
||||
// Loop body
|
||||
popBlock();
|
||||
// Loop header
|
||||
popBlock();
|
||||
|
||||
// New function block starts with indices = 0
|
||||
clearIndices();
|
||||
|
||||
if (m_breakSeen || m_continueSeen)
|
||||
{
|
||||
eraseKnowledge();
|
||||
m_context.resetVariables([](VariableDeclaration const&) { return true; });
|
||||
}
|
||||
|
||||
m_breakSeen = breakWasSeen;
|
||||
m_continueSeen = continueWasSeen;
|
||||
}
|
||||
|
||||
void CHC::reset()
|
||||
@ -186,6 +426,15 @@ void CHC::reset()
|
||||
m_stateVariables.clear();
|
||||
m_verificationTargets.clear();
|
||||
m_safeAssertions.clear();
|
||||
m_unknownFunctionCallSeen = false;
|
||||
m_breakSeen = false;
|
||||
m_continueSeen = false;
|
||||
}
|
||||
|
||||
void CHC::eraseKnowledge()
|
||||
{
|
||||
resetStateVariables();
|
||||
m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); });
|
||||
}
|
||||
|
||||
bool CHC::shouldVisit(ContractDefinition const& _contract) const
|
||||
@ -202,19 +451,35 @@ bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
||||
{
|
||||
if (
|
||||
_function.isPublic() &&
|
||||
_function.isImplemented()
|
||||
_function.isImplemented() &&
|
||||
!_function.isConstructor()
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void CHC::pushBlock(ASTNode const* _node)
|
||||
{
|
||||
clearIndices();
|
||||
m_context.pushSolver();
|
||||
m_path.push_back(predicate(_node));
|
||||
++m_functionBlocks;
|
||||
}
|
||||
|
||||
void CHC::popBlock()
|
||||
{
|
||||
m_context.popSolver();
|
||||
m_path.pop_back();
|
||||
--m_functionBlocks;
|
||||
}
|
||||
|
||||
smt::SortPointer CHC::constructorSort()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
if (!m_currentContract->constructor())
|
||||
return make_shared<smt::FunctionSort>(vector<smt::SortPointer>{}, boolSort);
|
||||
return functionSort(*m_currentContract->constructor());
|
||||
return sort(*m_currentContract->constructor());
|
||||
}
|
||||
|
||||
smt::SortPointer CHC::interfaceSort()
|
||||
@ -226,17 +491,45 @@ smt::SortPointer CHC::interfaceSort()
|
||||
);
|
||||
}
|
||||
|
||||
smt::SortPointer CHC::functionSort(FunctionDefinition const& _function)
|
||||
smt::SortPointer CHC::sort(FunctionDefinition const& _function)
|
||||
{
|
||||
if (m_nodeSorts.count(&_function))
|
||||
return m_nodeSorts.at(&_function);
|
||||
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
auto const& funType = dynamic_cast<FunctionType const&>(*_function.type());
|
||||
return make_shared<smt::FunctionSort>(
|
||||
smt::smtSort(funType.parameterTypes()),
|
||||
vector<smt::SortPointer> varSorts;
|
||||
for (auto const& var: _function.parameters() + _function.returnParameters())
|
||||
varSorts.push_back(smt::smtSort(*var->type()));
|
||||
auto sort = make_shared<smt::FunctionSort>(
|
||||
m_stateSorts + varSorts,
|
||||
boolSort
|
||||
);
|
||||
return m_nodeSorts[&_function] = move(sort);
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(smt::SortPointer _sort, string const& _name)
|
||||
smt::SortPointer CHC::sort(ASTNode const* _node)
|
||||
{
|
||||
if (m_nodeSorts.count(_node))
|
||||
return m_nodeSorts.at(_node);
|
||||
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||
return sort(*funDef);
|
||||
|
||||
auto fSort = dynamic_pointer_cast<smt::FunctionSort>(sort(*m_currentFunction));
|
||||
solAssert(fSort, "");
|
||||
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
vector<smt::SortPointer> varSorts;
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
varSorts.push_back(smt::smtSort(*var->type()));
|
||||
auto functionBodySort = make_shared<smt::FunctionSort>(
|
||||
fSort->domain + varSorts,
|
||||
boolSort
|
||||
);
|
||||
return m_nodeSorts[_node] = move(functionBodySort);
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createSymbolicBlock(smt::SortPointer _sort, string const& _name)
|
||||
{
|
||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||
_sort,
|
||||
@ -273,6 +566,98 @@ smt::Expression CHC::error()
|
||||
return (*m_errorPredicate)({});
|
||||
}
|
||||
|
||||
smt::Expression CHC::error(unsigned _idx)
|
||||
{
|
||||
return m_errorPredicate->valueAtIndex(_idx)({});
|
||||
}
|
||||
|
||||
void CHC::createBlock(ASTNode const* _node, string const& _prefix)
|
||||
{
|
||||
if (m_predicates.count(_node))
|
||||
{
|
||||
m_predicates.at(_node)->increaseIndex();
|
||||
m_interface->registerRelation(m_predicates.at(_node)->currentValue());
|
||||
}
|
||||
else
|
||||
m_predicates[_node] = createSymbolicBlock(sort(_node), _prefix + predicateName(_node));
|
||||
}
|
||||
|
||||
void CHC::createErrorBlock()
|
||||
{
|
||||
solAssert(m_errorPredicate, "");
|
||||
m_errorPredicate->increaseIndex();
|
||||
m_interface->registerRelation(m_errorPredicate->currentValue());
|
||||
}
|
||||
|
||||
void CHC::connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints)
|
||||
{
|
||||
smt::Expression edge = smt::Expression::implies(
|
||||
_from && m_context.assertions() && _constraints,
|
||||
_to
|
||||
);
|
||||
addRule(edge, _from.name + "_to_" + _to.name);
|
||||
}
|
||||
|
||||
vector<smt::Expression> CHC::currentFunctionVariables()
|
||||
{
|
||||
solAssert(m_currentFunction, "");
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return paramExprs;
|
||||
}
|
||||
|
||||
vector<smt::Expression> CHC::currentBlockVariables()
|
||||
{
|
||||
solAssert(m_currentFunction, "");
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return currentFunctionVariables() + paramExprs;
|
||||
}
|
||||
|
||||
void CHC::clearIndices()
|
||||
{
|
||||
for (auto const& var: m_stateVariables)
|
||||
m_context.variable(*var)->resetIndex();
|
||||
if (m_currentFunction)
|
||||
{
|
||||
for (auto const& var: m_currentFunction->parameters() + m_currentFunction->returnParameters())
|
||||
m_context.variable(*var)->resetIndex();
|
||||
for (auto const& var: m_currentFunction->localVariables())
|
||||
m_context.variable(*var)->resetIndex();
|
||||
}
|
||||
}
|
||||
|
||||
string CHC::predicateName(ASTNode const* _node)
|
||||
{
|
||||
string prefix;
|
||||
if (auto funDef = dynamic_cast<FunctionDefinition const*>(_node))
|
||||
{
|
||||
prefix = funDef->isConstructor() ?
|
||||
"constructor" :
|
||||
funDef->isFallback() ?
|
||||
"fallback" :
|
||||
"function_" + funDef->name();
|
||||
prefix += "_";
|
||||
}
|
||||
return prefix + to_string(_node->id());
|
||||
}
|
||||
|
||||
smt::Expression CHC::predicate(ASTNode const* _node)
|
||||
{
|
||||
if (dynamic_cast<FunctionDefinition const*>(_node))
|
||||
return (*m_predicates.at(_node))(currentFunctionVariables());
|
||||
return (*m_predicates.at(_node))(currentBlockVariables());
|
||||
}
|
||||
|
||||
void CHC::addRule(smt::Expression const& _rule, string const& _ruleName)
|
||||
{
|
||||
m_interface->addRule(_rule, _ruleName);
|
||||
}
|
||||
|
||||
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
{
|
||||
smt::CheckResult result;
|
||||
|
@ -58,29 +58,44 @@ private:
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
bool visit(IfStatement const& _node) override;
|
||||
bool visit(WhileStatement const&) override;
|
||||
bool visit(ForStatement const&) override;
|
||||
void endVisit(FunctionCall const& _node) override;
|
||||
void endVisit(Break const& _node) override;
|
||||
void endVisit(Continue const& _node) override;
|
||||
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void unknownFunctionCall(FunctionCall const& _funCall);
|
||||
void visitLoop(
|
||||
BreakableStatement const& _loop,
|
||||
Expression const* _condition,
|
||||
Statement const& _body,
|
||||
ASTNode const* _postLoop
|
||||
);
|
||||
//@}
|
||||
|
||||
/// Helpers.
|
||||
//@{
|
||||
void reset();
|
||||
void eraseKnowledge();
|
||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||
void pushBlock(ASTNode const* _node);
|
||||
void popBlock();
|
||||
//@}
|
||||
|
||||
/// Sort helpers.
|
||||
//@{
|
||||
smt::SortPointer constructorSort();
|
||||
smt::SortPointer interfaceSort();
|
||||
smt::SortPointer functionSort(FunctionDefinition const& _function);
|
||||
smt::SortPointer sort(FunctionDefinition const& _function);
|
||||
smt::SortPointer sort(ASTNode const* _block);
|
||||
//@}
|
||||
|
||||
/// Predicate helpers.
|
||||
//@{
|
||||
/// @returns a new block of given _sort and _name.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(smt::SortPointer _sort, std::string const& _name);
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createSymbolicBlock(smt::SortPointer _sort, std::string const& _name);
|
||||
|
||||
/// Constructor predicate over current variables.
|
||||
smt::Expression constructor();
|
||||
@ -88,10 +103,39 @@ private:
|
||||
smt::Expression interface();
|
||||
/// Error predicate over current variables.
|
||||
smt::Expression error();
|
||||
smt::Expression error(unsigned _idx);
|
||||
|
||||
/// Creates a block for the given _node or increases its SSA index
|
||||
/// if the block already exists which in practice creates a new function.
|
||||
void createBlock(ASTNode const* _node, std::string const& _prefix = "");
|
||||
|
||||
/// Creates a new error block to be used by an assertion.
|
||||
/// Also registers the predicate.
|
||||
void createErrorBlock();
|
||||
|
||||
void connectBlocks(smt::Expression const& _from, smt::Expression const& _to, smt::Expression const& _constraints = smt::Expression(true));
|
||||
|
||||
/// @returns the current symbolic values of the current function's
|
||||
/// input and output parameters.
|
||||
std::vector<smt::Expression> currentFunctionVariables();
|
||||
/// @returns the samve as currentFunctionVariables plus
|
||||
/// local variables.
|
||||
std::vector<smt::Expression> currentBlockVariables();
|
||||
|
||||
/// Sets the SSA indices of the variables in scope to 0.
|
||||
/// Used when starting a new block.
|
||||
void clearIndices();
|
||||
|
||||
/// @returns the predicate name for a given node.
|
||||
std::string predicateName(ASTNode const* _node);
|
||||
/// @returns a predicate application over the current scoped variables.
|
||||
smt::Expression predicate(ASTNode const* _node);
|
||||
//@}
|
||||
|
||||
/// Solver related.
|
||||
//@{
|
||||
/// Adds Horn rule to the solver.
|
||||
void addRule(smt::Expression const& _rule, std::string const& _ruleName);
|
||||
/// @returns true if query is unsatisfiable (safe).
|
||||
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
||||
//@}
|
||||
@ -109,6 +153,9 @@ private:
|
||||
/// Artificial Error predicate.
|
||||
/// Single error block for all assertions.
|
||||
std::unique_ptr<smt::SymbolicVariable> m_errorPredicate;
|
||||
|
||||
/// Maps AST nodes to their predicates.
|
||||
std::unordered_map<ASTNode const*, std::shared_ptr<smt::SymbolicVariable>> m_predicates;
|
||||
//@}
|
||||
|
||||
/// Variables.
|
||||
@ -119,6 +166,9 @@ private:
|
||||
/// State variables.
|
||||
/// Used to create all predicates.
|
||||
std::vector<VariableDeclaration const*> m_stateVariables;
|
||||
|
||||
/// Input sorts for AST nodes.
|
||||
std::map<ASTNode const*, smt::SortPointer> m_nodeSorts;
|
||||
//@}
|
||||
|
||||
/// Verification targets.
|
||||
@ -132,6 +182,22 @@ private:
|
||||
/// Control-flow.
|
||||
//@{
|
||||
FunctionDefinition const* m_currentFunction = nullptr;
|
||||
|
||||
/// Number of basic blocks created for the body of the current function.
|
||||
unsigned m_functionBlocks = 0;
|
||||
/// The current control flow path.
|
||||
std::vector<smt::Expression> m_path;
|
||||
/// Whether a function call was seen in the current scope.
|
||||
bool m_unknownFunctionCallSeen = false;
|
||||
/// Whether a break statement was seen in the current scope.
|
||||
bool m_breakSeen = false;
|
||||
/// Whether a continue statement was seen in the current scope.
|
||||
bool m_continueSeen = false;
|
||||
|
||||
/// Block where a loop break should go to.
|
||||
ASTNode const* m_breakDest;
|
||||
/// Block where a loop continue should go to.
|
||||
ASTNode const* m_continueDest;
|
||||
//@}
|
||||
|
||||
/// CHC solver.
|
||||
|
@ -137,6 +137,8 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
|
||||
return m_context.mkConst(true);
|
||||
else if (n == "false")
|
||||
return m_context.mkConst(false);
|
||||
else if (auto sortSort = dynamic_pointer_cast<SortSort>(_expr.sort))
|
||||
return m_context.mkVar(n, cvc4Sort(*sortSort->inner));
|
||||
else
|
||||
try
|
||||
{
|
||||
@ -187,6 +189,12 @@ CVC4::Expr CVC4Interface::toCVC4Expr(Expression const& _expr)
|
||||
return m_context.mkExpr(CVC4::kind::SELECT, arguments[0], arguments[1]);
|
||||
else if (n == "store")
|
||||
return m_context.mkExpr(CVC4::kind::STORE, arguments[0], arguments[1], arguments[2]);
|
||||
else if (n == "const_array")
|
||||
{
|
||||
shared_ptr<SortSort> sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments[0].sort);
|
||||
solAssert(sortSort, "");
|
||||
return m_context.mkConst(CVC4::ArrayStoreAll(cvc4Sort(*sortSort->inner), arguments[1]));
|
||||
}
|
||||
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
@ -621,8 +621,8 @@ void SMTEncoder::visitFunctionIdentifier(Identifier const& _identifier)
|
||||
auto const& fType = dynamic_cast<FunctionType const&>(*_identifier.annotation().type);
|
||||
if (fType.returnParameterTypes().size() == 1)
|
||||
{
|
||||
defineGlobalVariable(fType.richIdentifier(), _identifier);
|
||||
m_context.createExpression(_identifier, m_context.globalSymbol(fType.richIdentifier()));
|
||||
defineGlobalVariable(fType.identifier(), _identifier);
|
||||
m_context.createExpression(_identifier, m_context.globalSymbol(fType.identifier()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -748,6 +748,12 @@ void SMTEncoder::endVisit(IndexAccess const& _indexAccess)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_indexAccess.indexExpression())
|
||||
{
|
||||
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
|
||||
return;
|
||||
}
|
||||
|
||||
solAssert(array, "");
|
||||
defineExpr(_indexAccess, smt::Expression::select(
|
||||
array->currentValue(),
|
||||
@ -947,18 +953,21 @@ pair<smt::Expression, smt::Expression> SMTEncoder::arithmeticOperation(
|
||||
|
||||
smt::Expression intValueRange = (0 - smt::minValue(intType)) + smt::maxValue(intType) + 1;
|
||||
auto value = smt::Expression::ite(
|
||||
valueNoMod > smt::maxValue(intType) || valueNoMod < smt::minValue(intType),
|
||||
valueNoMod > smt::maxValue(intType),
|
||||
valueNoMod % intValueRange,
|
||||
valueNoMod
|
||||
smt::Expression::ite(
|
||||
valueNoMod < smt::minValue(intType),
|
||||
valueNoMod % intValueRange,
|
||||
valueNoMod
|
||||
)
|
||||
);
|
||||
|
||||
if (intType.isSigned())
|
||||
{
|
||||
value = smt::Expression::ite(
|
||||
value > smt::maxValue(intType),
|
||||
value - intValueRange,
|
||||
value
|
||||
);
|
||||
}
|
||||
|
||||
return {value, valueNoMod};
|
||||
}
|
||||
|
@ -87,6 +87,8 @@ protected:
|
||||
bool visit(MemberAccess const& _node) override;
|
||||
void endVisit(IndexAccess const& _node) override;
|
||||
bool visit(InlineAssembly const& _node) override;
|
||||
void endVisit(Break const&) override {}
|
||||
void endVisit(Continue const&) override {}
|
||||
|
||||
/// Do not visit subtree if node is a RationalNumber.
|
||||
/// Symbolic _expr is the rational literal.
|
||||
|
@ -17,8 +17,6 @@
|
||||
|
||||
#include <libsolidity/formal/SMTLib2Interface.h>
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libdevcore/Keccak256.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
@ -30,7 +28,6 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -49,7 +46,7 @@ void SMTLib2Interface::reset()
|
||||
m_accumulatedOutput.emplace_back();
|
||||
m_variables.clear();
|
||||
write("(set-option :produce-models true)");
|
||||
write("(set-logic QF_UFLIA)");
|
||||
write("(set-logic ALL)");
|
||||
}
|
||||
|
||||
void SMTLib2Interface::push()
|
||||
@ -96,12 +93,12 @@ void SMTLib2Interface::declareFunction(string const& _name, Sort const& _sort)
|
||||
}
|
||||
}
|
||||
|
||||
void SMTLib2Interface::addAssertion(Expression const& _expr)
|
||||
void SMTLib2Interface::addAssertion(smt::Expression const& _expr)
|
||||
{
|
||||
write("(assert " + toSExpr(_expr) + ")");
|
||||
}
|
||||
|
||||
pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> const& _expressionsToEvaluate)
|
||||
pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<smt::Expression> const& _expressionsToEvaluate)
|
||||
{
|
||||
string response = querySolver(
|
||||
boost::algorithm::join(m_accumulatedOutput, "\n") +
|
||||
@ -125,13 +122,28 @@ pair<CheckResult, vector<string>> SMTLib2Interface::check(vector<Expression> con
|
||||
return make_pair(result, values);
|
||||
}
|
||||
|
||||
string SMTLib2Interface::toSExpr(Expression const& _expr)
|
||||
string SMTLib2Interface::toSExpr(smt::Expression const& _expr)
|
||||
{
|
||||
if (_expr.arguments.empty())
|
||||
return _expr.name;
|
||||
std::string sexpr = "(" + _expr.name;
|
||||
for (auto const& arg: _expr.arguments)
|
||||
sexpr += " " + toSExpr(arg);
|
||||
|
||||
std::string sexpr = "(";
|
||||
if (_expr.name == "const_array")
|
||||
{
|
||||
solAssert(_expr.arguments.size() == 2, "");
|
||||
auto sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments.at(0).sort);
|
||||
solAssert(sortSort, "");
|
||||
auto arraySort = dynamic_pointer_cast<ArraySort>(sortSort->inner);
|
||||
solAssert(arraySort, "");
|
||||
sexpr += "(as const " + toSmtLibSort(*arraySort) + ") ";
|
||||
sexpr += toSExpr(_expr.arguments.at(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
sexpr += _expr.name;
|
||||
for (auto const& arg: _expr.arguments)
|
||||
sexpr += " " + toSExpr(arg);
|
||||
}
|
||||
sexpr += ")";
|
||||
return sexpr;
|
||||
}
|
||||
@ -169,7 +181,7 @@ void SMTLib2Interface::write(string _data)
|
||||
m_accumulatedOutput.back() += move(_data) + "\n";
|
||||
}
|
||||
|
||||
string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _expressionsToEvaluate)
|
||||
string SMTLib2Interface::checkSatAndGetValuesCommand(vector<smt::Expression> const& _expressionsToEvaluate)
|
||||
{
|
||||
string command;
|
||||
if (_expressionsToEvaluate.empty())
|
||||
|
@ -50,21 +50,21 @@ public:
|
||||
|
||||
void declareVariable(std::string const&, Sort const&) override;
|
||||
|
||||
void addAssertion(Expression const& _expr) override;
|
||||
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
|
||||
void addAssertion(smt::Expression const& _expr) override;
|
||||
std::pair<CheckResult, std::vector<std::string>> check(std::vector<smt::Expression> const& _expressionsToEvaluate) override;
|
||||
|
||||
std::vector<std::string> unhandledQueries() override { return m_unhandledQueries; }
|
||||
|
||||
private:
|
||||
void declareFunction(std::string const&, Sort const&);
|
||||
|
||||
std::string toSExpr(Expression const& _expr);
|
||||
std::string toSExpr(smt::Expression const& _expr);
|
||||
std::string toSmtLibSort(Sort const& _sort);
|
||||
std::string toSmtLibSort(std::vector<SortPointer> const& _sort);
|
||||
|
||||
void write(std::string _data);
|
||||
|
||||
std::string checkSatAndGetValuesCommand(std::vector<Expression> const& _expressionsToEvaluate);
|
||||
std::string checkSatAndGetValuesCommand(std::vector<smt::Expression> const& _expressionsToEvaluate);
|
||||
std::vector<std::string> parseValues(std::string::const_iterator _start, std::string::const_iterator _end);
|
||||
|
||||
/// Communicates with the solver via the callback. Throws SMTSolverError on error.
|
||||
|
@ -65,7 +65,7 @@ void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort)
|
||||
s->declareVariable(_name, _sort);
|
||||
}
|
||||
|
||||
void SMTPortfolio::addAssertion(Expression const& _expr)
|
||||
void SMTPortfolio::addAssertion(smt::Expression const& _expr)
|
||||
{
|
||||
for (auto const& s: m_solvers)
|
||||
s->addAssertion(_expr);
|
||||
@ -101,7 +101,7 @@ void SMTPortfolio::addAssertion(Expression const& _expr)
|
||||
*
|
||||
* If all solvers return ERROR, the result is ERROR.
|
||||
*/
|
||||
pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const& _expressionsToEvaluate)
|
||||
pair<CheckResult, vector<string>> SMTPortfolio::check(vector<smt::Expression> const& _expressionsToEvaluate)
|
||||
{
|
||||
CheckResult lastResult = CheckResult::ERROR;
|
||||
vector<string> finalValues;
|
||||
|
@ -51,9 +51,9 @@ public:
|
||||
|
||||
void declareVariable(std::string const&, Sort const&) override;
|
||||
|
||||
void addAssertion(Expression const& _expr) override;
|
||||
void addAssertion(smt::Expression const& _expr) override;
|
||||
|
||||
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
|
||||
std::pair<CheckResult, std::vector<std::string>> check(std::vector<smt::Expression> const& _expressionsToEvaluate) override;
|
||||
|
||||
std::vector<std::string> unhandledQueries() override;
|
||||
unsigned solvers() override { return m_solvers.size(); }
|
||||
@ -62,7 +62,7 @@ private:
|
||||
|
||||
std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers;
|
||||
|
||||
std::vector<Expression> m_assertions;
|
||||
std::vector<smt::Expression> m_assertions;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/Types.h>
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libdevcore/Common.h>
|
||||
@ -45,7 +46,8 @@ enum class Kind
|
||||
Int,
|
||||
Bool,
|
||||
Function,
|
||||
Array
|
||||
Array,
|
||||
Sort
|
||||
};
|
||||
|
||||
struct Sort
|
||||
@ -110,12 +112,33 @@ struct ArraySort: public Sort
|
||||
SortPointer range;
|
||||
};
|
||||
|
||||
struct SortSort: public Sort
|
||||
{
|
||||
SortSort(SortPointer _inner): Sort(Kind::Sort), inner(std::move(_inner)) {}
|
||||
bool operator==(Sort const& _other) const override
|
||||
{
|
||||
if (!Sort::operator==(_other))
|
||||
return false;
|
||||
auto _otherSort = dynamic_cast<SortSort const*>(&_other);
|
||||
solAssert(_otherSort, "");
|
||||
solAssert(_otherSort->inner, "");
|
||||
solAssert(inner, "");
|
||||
return *inner == *_otherSort->inner;
|
||||
}
|
||||
|
||||
SortPointer inner;
|
||||
};
|
||||
|
||||
// Forward declaration.
|
||||
SortPointer smtSort(solidity::Type const& _type);
|
||||
|
||||
/// C++ representation of an SMTLIB2 expression.
|
||||
class Expression
|
||||
{
|
||||
friend class SolverInterface;
|
||||
public:
|
||||
explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {}
|
||||
explicit Expression(solidity::TypePointer _type): Expression(_type->toString(), {}, std::make_shared<SortSort>(smtSort(*_type))) {}
|
||||
Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {}
|
||||
Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {}
|
||||
Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {}
|
||||
@ -145,7 +168,8 @@ public:
|
||||
{"/", 2},
|
||||
{"mod", 2},
|
||||
{"select", 2},
|
||||
{"store", 3}
|
||||
{"store", 3},
|
||||
{"const_array", 2}
|
||||
};
|
||||
return operatorsArity.count(name) && operatorsArity.at(name) == arguments.size();
|
||||
}
|
||||
@ -202,6 +226,21 @@ public:
|
||||
);
|
||||
}
|
||||
|
||||
static Expression const_array(Expression _sort, Expression _value)
|
||||
{
|
||||
solAssert(_sort.sort->kind == Kind::Sort, "");
|
||||
auto sortSort = std::dynamic_pointer_cast<SortSort>(_sort.sort);
|
||||
auto arraySort = std::dynamic_pointer_cast<ArraySort>(sortSort->inner);
|
||||
solAssert(sortSort && arraySort, "");
|
||||
solAssert(_value.sort, "");
|
||||
solAssert(*arraySort->range == *_value.sort, "");
|
||||
return Expression(
|
||||
"const_array",
|
||||
std::vector<Expression>{std::move(_sort), std::move(_value)},
|
||||
arraySort
|
||||
);
|
||||
}
|
||||
|
||||
friend Expression operator!(Expression _a)
|
||||
{
|
||||
return Expression("not", std::move(_a), Kind::Bool);
|
||||
|
@ -276,10 +276,30 @@ void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _c
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isNumber(_type->category()))
|
||||
_context.addAssertion(_expr == 0);
|
||||
else if (isBool(_type->category()))
|
||||
_context.addAssertion(_expr == Expression(false));
|
||||
_context.addAssertion(_expr == zeroValue(_type));
|
||||
}
|
||||
|
||||
Expression zeroValue(solidity::TypePointer const& _type)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isSupportedType(_type->category()))
|
||||
{
|
||||
if (isNumber(_type->category()))
|
||||
return 0;
|
||||
if (isBool(_type->category()))
|
||||
return Expression(false);
|
||||
if (isArray(_type->category()) || isMapping(_type->category()))
|
||||
{
|
||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_type))
|
||||
return Expression::const_array(Expression(arrayType), zeroValue(arrayType->baseType()));
|
||||
auto mappingType = dynamic_cast<MappingType const*>(_type);
|
||||
solAssert(mappingType, "");
|
||||
return Expression::const_array(Expression(mappingType), zeroValue(mappingType->valueType()));
|
||||
}
|
||||
solAssert(false, "");
|
||||
}
|
||||
// Unsupported types are abstracted as Int.
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context)
|
||||
|
@ -63,6 +63,7 @@ std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity:
|
||||
|
||||
Expression minValue(solidity::IntegerType const& _type);
|
||||
Expression maxValue(solidity::IntegerType const& _type);
|
||||
Expression zeroValue(solidity::TypePointer const& _type);
|
||||
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context);
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context);
|
||||
|
@ -33,6 +33,18 @@ Z3CHCInterface::Z3CHCInterface():
|
||||
z3::set_param("rewriter.pull_cheap_ite", true);
|
||||
// This needs to be set in the context.
|
||||
m_context->set("timeout", queryTimeout);
|
||||
|
||||
// Spacer options.
|
||||
// These needs to be set in the solver.
|
||||
// https://github.com/Z3Prover/z3/blob/master/src/muz/base/fp_params.pyg
|
||||
z3::params p(*m_context);
|
||||
// These are useful for solving problems with arrays and loops.
|
||||
// Use quantified lemma generalizer.
|
||||
p.set("fp.spacer.q3.use_qgen", true);
|
||||
p.set("fp.spacer.mbqi", false);
|
||||
// Ground pobs by using values from a model.
|
||||
p.set("fp.spacer.ground_pobs", false);
|
||||
m_solver.set(p);
|
||||
}
|
||||
|
||||
void Z3CHCInterface::declareVariable(string const& _name, Sort const& _sort)
|
||||
@ -82,12 +94,14 @@ pair<CheckResult, vector<string>> Z3CHCInterface::query(Expression const& _expr)
|
||||
break;
|
||||
}
|
||||
case z3::check_result::unknown:
|
||||
{
|
||||
result = CheckResult::UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO retrieve model / invariants
|
||||
}
|
||||
catch (z3::exception const& _e)
|
||||
catch (z3::exception const&)
|
||||
{
|
||||
result = CheckResult::ERROR;
|
||||
values.clear();
|
||||
|
@ -132,6 +132,12 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
|
||||
return m_context.bool_val(true);
|
||||
else if (n == "false")
|
||||
return m_context.bool_val(false);
|
||||
else if (_expr.sort->kind == Kind::Sort)
|
||||
{
|
||||
auto sortSort = dynamic_pointer_cast<SortSort>(_expr.sort);
|
||||
solAssert(sortSort, "");
|
||||
return m_context.constant(n.c_str(), z3Sort(*sortSort->inner));
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
@ -178,6 +184,14 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
|
||||
return z3::select(arguments[0], arguments[1]);
|
||||
else if (n == "store")
|
||||
return z3::store(arguments[0], arguments[1], arguments[2]);
|
||||
else if (n == "const_array")
|
||||
{
|
||||
shared_ptr<SortSort> sortSort = std::dynamic_pointer_cast<SortSort>(_expr.arguments[0].sort);
|
||||
solAssert(sortSort, "");
|
||||
auto arraySort = dynamic_pointer_cast<ArraySort>(sortSort->inner);
|
||||
solAssert(arraySort && arraySort->domain, "");
|
||||
return z3::const_array(z3Sort(*arraySort->domain), arguments[1]);
|
||||
}
|
||||
|
||||
solAssert(false, "");
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI)
|
||||
* Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html)
|
||||
*/
|
||||
|
||||
#include <libsolidity/interface/ABI.h>
|
||||
@ -40,7 +40,10 @@ bool anyDataStoredInStorage(TypePointers const& _pointers)
|
||||
|
||||
Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
{
|
||||
Json::Value abi(Json::arrayValue);
|
||||
auto compare = [](Json::Value const& _a, Json::Value const& _b) -> bool {
|
||||
return make_tuple(_a["type"], _a["name"]) < make_tuple(_b["type"], _b["name"]);
|
||||
};
|
||||
multiset<Json::Value, decltype(compare)> abi(compare);
|
||||
|
||||
for (auto it: _contractDef.interfaceFunctions())
|
||||
{
|
||||
@ -71,7 +74,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
it.second->returnParameterTypes(),
|
||||
_contractDef.isLibrary()
|
||||
);
|
||||
abi.append(std::move(method));
|
||||
abi.emplace(std::move(method));
|
||||
}
|
||||
if (_contractDef.constructor())
|
||||
{
|
||||
@ -88,7 +91,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
constrType.parameterTypes(),
|
||||
_contractDef.isLibrary()
|
||||
);
|
||||
abi.append(std::move(method));
|
||||
abi.emplace(std::move(method));
|
||||
}
|
||||
if (_contractDef.fallbackFunction())
|
||||
{
|
||||
@ -98,7 +101,7 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
method["type"] = "fallback";
|
||||
method["payable"] = externalFunctionType->isPayable();
|
||||
method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability());
|
||||
abi.append(std::move(method));
|
||||
abi.emplace(std::move(method));
|
||||
}
|
||||
for (auto const& it: _contractDef.interfaceEvents())
|
||||
{
|
||||
@ -117,10 +120,13 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
params.append(std::move(param));
|
||||
}
|
||||
event["inputs"] = std::move(params);
|
||||
abi.append(std::move(event));
|
||||
abi.emplace(std::move(event));
|
||||
}
|
||||
|
||||
return abi;
|
||||
Json::Value abiJson{Json::arrayValue};
|
||||
for (auto& f: abi)
|
||||
abiJson.append(std::move(f));
|
||||
return abiJson;
|
||||
}
|
||||
|
||||
Json::Value ABI::formatTypeList(
|
||||
|
@ -15,7 +15,7 @@
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Utilities to handle the Contract ABI (https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI)
|
||||
* Utilities to handle the Contract ABI (https://solidity.readthedocs.io/en/develop/abi-spec.html)
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
@ -180,7 +180,7 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
|
||||
expectToken(Token::Import);
|
||||
ASTPointer<ASTString> path;
|
||||
ASTPointer<ASTString> unitAlias = make_shared<string>();
|
||||
vector<pair<ASTPointer<Identifier>, ASTPointer<ASTString>>> symbolAliases;
|
||||
ImportDirective::SymbolAliasList symbolAliases;
|
||||
|
||||
if (m_scanner->currentToken() == Token::StringLiteral)
|
||||
{
|
||||
@ -198,14 +198,16 @@ ASTPointer<ImportDirective> Parser::parseImportDirective()
|
||||
m_scanner->next();
|
||||
while (true)
|
||||
{
|
||||
ASTPointer<Identifier> id = parseIdentifier();
|
||||
ASTPointer<ASTString> alias;
|
||||
SourceLocation aliasLocation = SourceLocation{position(), endPosition(), source()};
|
||||
ASTPointer<Identifier> id = parseIdentifier();
|
||||
if (m_scanner->currentToken() == Token::As)
|
||||
{
|
||||
expectToken(Token::As);
|
||||
aliasLocation = SourceLocation{position(), endPosition(), source()};
|
||||
alias = expectIdentifierToken();
|
||||
}
|
||||
symbolAliases.emplace_back(move(id), move(alias));
|
||||
symbolAliases.emplace_back(ImportDirective::SymbolAlias{move(id), move(alias), aliasLocation});
|
||||
if (m_scanner->currentToken() != Token::Comma)
|
||||
break;
|
||||
m_scanner->next();
|
||||
|
@ -732,6 +732,14 @@ void AsmAnalyzer::warnOnInstructions(dev::eth::Instruction _instr, SourceLocatio
|
||||
{
|
||||
errorForVM("only available for Constantinople-compatible");
|
||||
}
|
||||
else if (_instr == dev::eth::Instruction::CHAINID && !m_evmVersion.hasChainID())
|
||||
{
|
||||
errorForVM("only available for Istanbul-compatible");
|
||||
}
|
||||
else if (_instr == dev::eth::Instruction::SELFBALANCE && !m_evmVersion.hasSelfBalance())
|
||||
{
|
||||
errorForVM("only available for Istanbul-compatible");
|
||||
}
|
||||
else if (
|
||||
_instr == dev::eth::Instruction::JUMP ||
|
||||
_instr == dev::eth::Instruction::JUMPI ||
|
||||
|
@ -62,6 +62,8 @@ add_library(yul
|
||||
optimiser/BlockFlattener.h
|
||||
optimiser/BlockHasher.cpp
|
||||
optimiser/BlockHasher.h
|
||||
optimiser/CallGraphGenerator.cpp
|
||||
optimiser/CallGraphGenerator.h
|
||||
optimiser/CommonSubexpressionEliminator.cpp
|
||||
optimiser/CommonSubexpressionEliminator.h
|
||||
optimiser/ControlFlowSimplifier.cpp
|
||||
@ -86,6 +88,8 @@ add_library(yul
|
||||
optimiser/ExpressionSplitter.h
|
||||
optimiser/ForLoopConditionIntoBody.cpp
|
||||
optimiser/ForLoopConditionIntoBody.h
|
||||
optimiser/ForLoopConditionOutOfBody.cpp
|
||||
optimiser/ForLoopConditionOutOfBody.h
|
||||
optimiser/ForLoopInitRewriter.cpp
|
||||
optimiser/ForLoopInitRewriter.h
|
||||
optimiser/FullInliner.cpp
|
||||
@ -110,6 +114,7 @@ add_library(yul
|
||||
optimiser/NameDispenser.h
|
||||
optimiser/NameDisplacer.cpp
|
||||
optimiser/NameDisplacer.h
|
||||
optimiser/OptimiserStep.h
|
||||
optimiser/OptimizerUtilities.cpp
|
||||
optimiser/OptimizerUtilities.h
|
||||
optimiser/RedundantAssignEliminator.cpp
|
||||
|
@ -21,6 +21,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
@ -45,24 +46,9 @@ struct BuiltinFunction
|
||||
YulString name;
|
||||
std::vector<Type> parameters;
|
||||
std::vector<Type> returns;
|
||||
/// If true, calls to this function can be freely moved and copied (as long as their
|
||||
/// arguments are either variables or also movable) without altering the semantics.
|
||||
/// This means the function cannot depend on storage or memory, cannot have any side-effects,
|
||||
/// but it can depend on state that is constant across an EVM-call.
|
||||
bool movable = false;
|
||||
/// If true, a call to this function can be omitted without changing semantics.
|
||||
bool sideEffectFree = false;
|
||||
/// If true, a call to this function can be omitted without changing semantics if the
|
||||
/// program does not contain the msize instruction.
|
||||
bool sideEffectFreeIfNoMSize = false;
|
||||
SideEffects sideEffects;
|
||||
/// If true, this is the msize instruction.
|
||||
bool isMSize = false;
|
||||
/// If false, storage of the current contract before and after the function is the same
|
||||
/// under every circumstance. If the function does not return, this can be false.
|
||||
bool invalidatesStorage = true;
|
||||
/// If false, memory before and after the function is the same under every circumstance.
|
||||
/// If the function does not return, this can be false.
|
||||
bool invalidatesMemory = true;
|
||||
/// If true, can only accept literals as arguments and they cannot be moved to variables.
|
||||
bool literalArguments = false;
|
||||
};
|
||||
@ -75,6 +61,7 @@ struct Dialect: boost::noncopyable
|
||||
|
||||
virtual BuiltinFunction const* discardFunction() const { return nullptr; }
|
||||
virtual BuiltinFunction const* equalityFunction() const { return nullptr; }
|
||||
virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; }
|
||||
|
||||
virtual std::set<YulString> fixedFunctionNames() const { return {}; }
|
||||
|
||||
|
88
libyul/SideEffects.h
Normal file
88
libyul/SideEffects.h
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
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 yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Side effects of code.
|
||||
*
|
||||
* The default-constructed value applies to the "empty code".
|
||||
*/
|
||||
struct SideEffects
|
||||
{
|
||||
/// If true, expressions in this code can be freely moved and copied without altering the
|
||||
/// semantics.
|
||||
/// At statement level, it means that functions containing this code can be
|
||||
/// called multiple times, their calls can be rearranged and calls can also be
|
||||
/// deleted without changing the semantics.
|
||||
/// This means it cannot depend on storage or memory, cannot have any side-effects,
|
||||
/// but it can depend on state that is constant across an EVM-call.
|
||||
bool movable = true;
|
||||
/// If true, the code can be removed without changing the semantics.
|
||||
bool sideEffectFree = true;
|
||||
/// If true, the code can be removed without changing the semantics as long as
|
||||
/// the whole program does not contain the msize instruction.
|
||||
bool sideEffectFreeIfNoMSize = true;
|
||||
/// If false, storage is guaranteed to be unchanged by the code under all
|
||||
/// circumstances.
|
||||
bool invalidatesStorage = false;
|
||||
/// If false, memory is guaranteed to be unchanged by the code under all
|
||||
/// circumstances.
|
||||
bool invalidatesMemory = false;
|
||||
|
||||
/// @returns the worst-case side effects.
|
||||
static SideEffects worst()
|
||||
{
|
||||
return SideEffects{false, false, false, true, true};
|
||||
}
|
||||
|
||||
/// @returns the combined side effects of two pieces of code.
|
||||
SideEffects operator+(SideEffects const& _other)
|
||||
{
|
||||
return SideEffects{
|
||||
movable && _other.movable,
|
||||
sideEffectFree && _other.sideEffectFree,
|
||||
sideEffectFreeIfNoMSize && _other.sideEffectFreeIfNoMSize,
|
||||
invalidatesStorage || _other.invalidatesStorage,
|
||||
invalidatesMemory || _other.invalidatesMemory
|
||||
};
|
||||
}
|
||||
|
||||
/// Adds the side effects of another piece of code to this side effect.
|
||||
SideEffects& operator+=(SideEffects const& _other)
|
||||
{
|
||||
*this = *this + _other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(SideEffects const& _other) const
|
||||
{
|
||||
return
|
||||
movable == _other.movable &&
|
||||
sideEffectFree == _other.sideEffectFree &&
|
||||
sideEffectFreeIfNoMSize == _other.sideEffectFreeIfNoMSize &&
|
||||
invalidatesStorage == _other.invalidatesStorage &&
|
||||
invalidatesMemory == _other.invalidatesMemory;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -50,12 +50,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
|
||||
f.name = YulString{_name};
|
||||
f.parameters.resize(info.args);
|
||||
f.returns.resize(info.ret);
|
||||
f.movable = eth::SemanticInformation::movable(_instruction);
|
||||
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
|
||||
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
|
||||
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
|
||||
f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
|
||||
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
|
||||
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
|
||||
f.literalArguments = false;
|
||||
f.instruction = _instruction;
|
||||
f.generateCode = [_instruction](
|
||||
@ -75,11 +71,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
||||
string _name,
|
||||
size_t _params,
|
||||
size_t _returns,
|
||||
bool _movable,
|
||||
bool _sideEffectFree,
|
||||
bool _sideEffectFreeIfNoMSize,
|
||||
bool _invalidatesStorage,
|
||||
bool _invalidatesMemory,
|
||||
SideEffects _sideEffects,
|
||||
bool _literalArguments,
|
||||
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
|
||||
)
|
||||
@ -89,13 +81,9 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
|
||||
f.name = name;
|
||||
f.parameters.resize(_params);
|
||||
f.returns.resize(_returns);
|
||||
f.movable = _movable;
|
||||
f.sideEffects = std::move(_sideEffects);
|
||||
f.literalArguments = _literalArguments;
|
||||
f.sideEffectFree = _sideEffectFree;
|
||||
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
|
||||
f.isMSize = false;
|
||||
f.invalidatesStorage = _invalidatesStorage;
|
||||
f.invalidatesMemory = _invalidatesMemory;
|
||||
f.instruction = {};
|
||||
f.generateCode = std::move(_generateCode);
|
||||
return {name, f};
|
||||
@ -116,7 +104,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
|
||||
if (_objectAccess)
|
||||
{
|
||||
builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, [](
|
||||
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, [](
|
||||
FunctionCall const& _call,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _context,
|
||||
@ -137,7 +125,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
_assembly.appendDataSize(_context.subIDs.at(dataName));
|
||||
}
|
||||
}));
|
||||
builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, [](
|
||||
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, [](
|
||||
FunctionCall const& _call,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext& _context,
|
||||
@ -158,15 +146,22 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
|
||||
_assembly.appendDataOffset(_context.subIDs.at(dataName));
|
||||
}
|
||||
}));
|
||||
builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, [](
|
||||
FunctionCall const&,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext&,
|
||||
std::function<void()> _visitArguments
|
||||
) {
|
||||
_visitArguments();
|
||||
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
|
||||
}));
|
||||
builtins.emplace(createFunction(
|
||||
"datacopy",
|
||||
3,
|
||||
0,
|
||||
SideEffects{false, false, false, false, true},
|
||||
false,
|
||||
[](
|
||||
FunctionCall const&,
|
||||
AbstractAssembly& _assembly,
|
||||
BuiltinContext&,
|
||||
std::function<void()> _visitArguments
|
||||
) {
|
||||
_visitArguments();
|
||||
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
|
||||
}
|
||||
));
|
||||
}
|
||||
return builtins;
|
||||
}
|
||||
@ -225,3 +220,14 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version)
|
||||
dialects[_version] = make_unique<EVMDialect>(AsmFlavour::Yul, false, _version);
|
||||
return *dialects[_version];
|
||||
}
|
||||
|
||||
SideEffects EVMDialect::sideEffectsOfInstruction(eth::Instruction _instruction)
|
||||
{
|
||||
return SideEffects{
|
||||
eth::SemanticInformation::movable(_instruction),
|
||||
eth::SemanticInformation::sideEffectFree(_instruction),
|
||||
eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction),
|
||||
eth::SemanticInformation::invalidatesStorage(_instruction),
|
||||
eth::SemanticInformation::invalidatesMemory(_instruction)
|
||||
};
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ struct EVMDialect: public Dialect
|
||||
|
||||
BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); }
|
||||
BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); }
|
||||
BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); }
|
||||
|
||||
static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version);
|
||||
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
|
||||
@ -80,6 +81,8 @@ struct EVMDialect: public Dialect
|
||||
|
||||
bool providesObjectAccess() const { return m_objectAccess; }
|
||||
|
||||
static SideEffects sideEffectsOfInstruction(dev::eth::Instruction _instruction);
|
||||
|
||||
protected:
|
||||
bool const m_objectAccess;
|
||||
langutil::EVMVersion const m_evmVersion;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <libyul/optimiser/FunctionHoister.h>
|
||||
#include <libyul/optimiser/Disambiguator.h>
|
||||
#include <libyul/optimiser/NameDisplacer.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
@ -637,11 +638,14 @@ Object EVMToEWasmTranslator::run(Object const& _object)
|
||||
parsePolyfill();
|
||||
|
||||
Block ast = boost::get<Block>(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code));
|
||||
NameDispenser nameDispenser{m_dialect, ast};
|
||||
FunctionHoister{}(ast);
|
||||
FunctionGrouper{}(ast);
|
||||
set<YulString> reservedIdentifiers;
|
||||
NameDispenser nameDispenser{m_dialect, ast, reservedIdentifiers};
|
||||
OptimiserStepContext context{m_dialect, nameDispenser, reservedIdentifiers};
|
||||
|
||||
FunctionHoister::run(context, ast);
|
||||
FunctionGrouper::run(context, ast);
|
||||
MainFunction{}(ast);
|
||||
ExpressionSplitter{m_dialect, nameDispenser}(ast);
|
||||
ExpressionSplitter::run(context, ast);
|
||||
WordSizeTransform::run(m_dialect, ast, nameDispenser);
|
||||
|
||||
NameDisplacer{nameDispenser, m_polyfillFunctions}(ast);
|
||||
|
@ -55,7 +55,7 @@ string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast)
|
||||
std::vector<wasm::FunctionImport> imports;
|
||||
for (auto& imp: transform.m_functionsToImport)
|
||||
imports.emplace_back(std::move(imp.second));
|
||||
return EWasmToText{}.run(
|
||||
return EWasmToText().run(
|
||||
transform.m_globalVariables,
|
||||
imports,
|
||||
functions
|
||||
|
@ -49,19 +49,19 @@ WasmDialect::WasmDialect():
|
||||
addFunction("i64.eqz", 1, 1);
|
||||
|
||||
addFunction("i64.store", 2, 0, false);
|
||||
m_functions["i64.store"_yulstring].invalidatesStorage = false;
|
||||
m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false;
|
||||
|
||||
addFunction("i64.load", 1, 1, false);
|
||||
m_functions["i64.load"_yulstring].invalidatesStorage = false;
|
||||
m_functions["i64.load"_yulstring].invalidatesMemory = false;
|
||||
m_functions["i64.load"_yulstring].sideEffectFree = true;
|
||||
m_functions["i64.load"_yulstring].sideEffectFreeIfNoMSize = true;
|
||||
m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false;
|
||||
m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false;
|
||||
m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true;
|
||||
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
|
||||
|
||||
addFunction("drop", 1, 0);
|
||||
|
||||
addFunction("unreachable", 0, 0, false);
|
||||
m_functions["unreachable"_yulstring].invalidatesStorage = false;
|
||||
m_functions["unreachable"_yulstring].invalidatesMemory = false;
|
||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
|
||||
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
|
||||
|
||||
addFunction("datasize", 1, 4, true, true);
|
||||
addFunction("dataoffset", 1, 4, true, true);
|
||||
@ -138,14 +138,10 @@ void WasmDialect::addEthereumExternals()
|
||||
f.parameters.emplace_back(YulString(p));
|
||||
for (string const& p: ext.returns)
|
||||
f.returns.emplace_back(YulString(p));
|
||||
f.movable = false;
|
||||
// TODO some of them are side effect free.
|
||||
f.sideEffectFree = false;
|
||||
f.sideEffectFreeIfNoMSize = false;
|
||||
f.sideEffects = SideEffects::worst();
|
||||
f.isMSize = false;
|
||||
f.invalidatesStorage = (ext.name == "storageStore");
|
||||
// TODO some of them do not invalidate memory
|
||||
f.invalidatesMemory = true;
|
||||
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
|
||||
f.literalArguments = false;
|
||||
}
|
||||
}
|
||||
@ -163,11 +159,7 @@ void WasmDialect::addFunction(
|
||||
f.name = name;
|
||||
f.parameters.resize(_params);
|
||||
f.returns.resize(_returns);
|
||||
f.movable = _movable;
|
||||
f.sideEffectFree = _movable;
|
||||
f.sideEffectFreeIfNoMSize = _movable;
|
||||
f.sideEffects = _movable ? SideEffects{} : SideEffects::worst();
|
||||
f.isMSize = false;
|
||||
f.invalidatesStorage = !_movable;
|
||||
f.invalidatesMemory = !_movable;
|
||||
f.literalArguments = _literalArguments;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ struct WasmDialect: public Dialect
|
||||
BuiltinFunction const* builtin(YulString _name) const override;
|
||||
BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); }
|
||||
BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); }
|
||||
BuiltinFunction const* booleanNegationFunction() const override { return builtin("i64.eqz"_yulstring); }
|
||||
|
||||
std::set<YulString> fixedFunctionNames() const override { return {"main"_yulstring}; }
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
@ -24,8 +25,14 @@ namespace yul
|
||||
class BlockFlattener: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"BlockFlattener"};
|
||||
static void run(OptimiserStepContext&, Block& _ast) { BlockFlattener{}(_ast); }
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
private:
|
||||
BlockFlattener() = default;
|
||||
};
|
||||
|
||||
}
|
||||
|
67
libyul/optimiser/CallGraphGenerator.cpp
Normal file
67
libyul/optimiser/CallGraphGenerator.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Specific AST walker that generates the call graph.
|
||||
*/
|
||||
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/optimiser/CallGraphGenerator.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <stack>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
map<YulString, set<YulString>> CallGraphGenerator::callGraph(Block const& _ast)
|
||||
{
|
||||
CallGraphGenerator gen;
|
||||
gen(_ast);
|
||||
return std::move(gen.m_callGraph);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(FunctionalInstruction const& _functionalInstruction)
|
||||
{
|
||||
string name = dev::eth::instructionInfo(_functionalInstruction.instruction).name;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
|
||||
m_callGraph[m_currentFunction].insert(YulString{name});
|
||||
ASTWalker::operator()(_functionalInstruction);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(FunctionCall const& _functionCall)
|
||||
{
|
||||
m_callGraph[m_currentFunction].insert(_functionCall.functionName.name);
|
||||
ASTWalker::operator()(_functionCall);
|
||||
}
|
||||
|
||||
void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
YulString previousFunction = m_currentFunction;
|
||||
m_currentFunction = _functionDefinition.name;
|
||||
yulAssert(m_callGraph.count(m_currentFunction) == 0, "");
|
||||
m_callGraph[m_currentFunction] = {};
|
||||
ASTWalker::operator()(_functionDefinition);
|
||||
m_currentFunction = previousFunction;
|
||||
}
|
||||
|
||||
CallGraphGenerator::CallGraphGenerator()
|
||||
{
|
||||
m_callGraph[YulString{}] = {};
|
||||
}
|
||||
|
58
libyul/optimiser/CallGraphGenerator.h
Normal file
58
libyul/optimiser/CallGraphGenerator.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Specific AST walker that generates the call graph.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <libdevcore/InvertibleMap.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Specific AST walker that generates the call graph.
|
||||
*
|
||||
* The outermost (non-function) context is denoted by the empty string.
|
||||
*/
|
||||
class CallGraphGenerator: public ASTWalker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, std::set<YulString>> callGraph(Block const& _ast);
|
||||
|
||||
using ASTWalker::operator();
|
||||
void operator()(FunctionalInstruction const& _functionalInstruction) override;
|
||||
void operator()(FunctionCall const& _functionCall) override;
|
||||
void operator()(FunctionDefinition const& _functionDefinition) override;
|
||||
|
||||
private:
|
||||
CallGraphGenerator();
|
||||
|
||||
std::map<YulString, std::set<YulString>> m_callGraph;
|
||||
/// The name of the function we are currently visiting during traversal.
|
||||
YulString m_currentFunction;
|
||||
};
|
||||
|
||||
}
|
@ -23,6 +23,9 @@
|
||||
|
||||
#include <libyul/optimiser/Metrics.h>
|
||||
#include <libyul/optimiser/SyntacticalEquality.h>
|
||||
#include <libyul/optimiser/CallGraphGenerator.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Dialect.h>
|
||||
@ -31,6 +34,23 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
void CommonSubexpressionEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
CommonSubexpressionEliminator cse{
|
||||
_context.dialect,
|
||||
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast))
|
||||
};
|
||||
cse(_ast);
|
||||
}
|
||||
|
||||
CommonSubexpressionEliminator::CommonSubexpressionEliminator(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, SideEffects> _functionSideEffects
|
||||
):
|
||||
DataFlowAnalyzer(_dialect, std::move(_functionSideEffects))
|
||||
{
|
||||
}
|
||||
|
||||
void CommonSubexpressionEliminator::visit(Expression& _e)
|
||||
{
|
||||
bool descend = true;
|
||||
|
@ -22,11 +22,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/DataFlowAnalyzer.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
|
||||
struct Dialect;
|
||||
struct SideEffects;
|
||||
|
||||
/**
|
||||
* Optimisation stage that replaces expressions known to be the current value of a variable
|
||||
@ -37,7 +39,14 @@ struct Dialect;
|
||||
class CommonSubexpressionEliminator: public DataFlowAnalyzer
|
||||
{
|
||||
public:
|
||||
CommonSubexpressionEliminator(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {}
|
||||
static constexpr char const* name{"CommonSubexpressionEliminator"};
|
||||
static void run(OptimiserStepContext&, Block& _ast);
|
||||
|
||||
private:
|
||||
CommonSubexpressionEliminator(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, SideEffects> _functionSideEffects
|
||||
);
|
||||
|
||||
protected:
|
||||
using ASTModifier::visit;
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
#include <libyul/optimiser/ControlFlowSimplifier.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/Dialect.h>
|
||||
@ -125,6 +126,11 @@ OptionalStatements reduceSingleCaseSwitch(Dialect const& _dialect, Switch& _swit
|
||||
|
||||
}
|
||||
|
||||
void ControlFlowSimplifier::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
ControlFlowSimplifier{_context.dialect}(_ast);
|
||||
}
|
||||
|
||||
void ControlFlowSimplifier::operator()(Block& _block)
|
||||
{
|
||||
simplify(_block.statements);
|
||||
|
@ -17,10 +17,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
struct Dialect;
|
||||
struct OptimiserStepContext;
|
||||
|
||||
/**
|
||||
* Simplifies several control-flow structures:
|
||||
@ -46,7 +48,8 @@ struct Dialect;
|
||||
class ControlFlowSimplifier: public ASTModifier
|
||||
{
|
||||
public:
|
||||
ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
static constexpr char const* name{"ControlFlowSimplifier"};
|
||||
static void run(OptimiserStepContext&, Block& _ast);
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(Break&) override { ++m_numBreakStatements; }
|
||||
@ -56,6 +59,8 @@ public:
|
||||
void visit(Statement& _st) override;
|
||||
|
||||
private:
|
||||
ControlFlowSimplifier(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
|
||||
void simplify(std::vector<Statement>& _statements);
|
||||
|
||||
Dialect const& m_dialect;
|
||||
|
@ -43,7 +43,6 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement)
|
||||
if (auto vars = isSimpleStore(dev::eth::Instruction::SSTORE, _statement))
|
||||
{
|
||||
ASTModifier::operator()(_statement);
|
||||
m_storage.set(vars->first, vars->second);
|
||||
set<YulString> keysToErase;
|
||||
for (auto const& item: m_storage.values)
|
||||
if (!(
|
||||
@ -53,6 +52,7 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement)
|
||||
keysToErase.insert(item.first);
|
||||
for (YulString const& key: keysToErase)
|
||||
m_storage.eraseKey(key);
|
||||
m_storage.set(vars->first, vars->second);
|
||||
}
|
||||
else if (auto vars = isSimpleStore(dev::eth::Instruction::MSTORE, _statement))
|
||||
{
|
||||
@ -61,11 +61,9 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement)
|
||||
for (auto const& item: m_memory.values)
|
||||
if (!m_knowledgeBase.knownToBeDifferentByAtLeast32(vars->first, item.first))
|
||||
keysToErase.insert(item.first);
|
||||
// TODO is it fine to do that here?
|
||||
// can we also move the storage above?
|
||||
m_memory.set(vars->first, vars->second);
|
||||
for (YulString const& key: keysToErase)
|
||||
m_memory.eraseKey(key);
|
||||
m_memory.set(vars->first, vars->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -213,7 +211,7 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
|
||||
{
|
||||
clearValues(_variables);
|
||||
|
||||
MovableChecker movableChecker{m_dialect};
|
||||
MovableChecker movableChecker{m_dialect, &m_functionSideEffects};
|
||||
if (_value)
|
||||
movableChecker.visit(*_value);
|
||||
else
|
||||
@ -299,7 +297,7 @@ void DataFlowAnalyzer::clearValues(set<YulString> _variables)
|
||||
|
||||
void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block)
|
||||
{
|
||||
SideEffectsCollector sideEffects(m_dialect, _block);
|
||||
SideEffectsCollector sideEffects(m_dialect, _block, &m_functionSideEffects);
|
||||
if (sideEffects.invalidatesStorage())
|
||||
m_storage.clear();
|
||||
if (sideEffects.invalidatesMemory())
|
||||
@ -308,7 +306,7 @@ void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block)
|
||||
|
||||
void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr)
|
||||
{
|
||||
SideEffectsCollector sideEffects(m_dialect, _expr);
|
||||
SideEffectsCollector sideEffects(m_dialect, _expr, &m_functionSideEffects);
|
||||
if (sideEffects.invalidatesStorage())
|
||||
m_storage.clear();
|
||||
if (sideEffects.invalidatesMemory())
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <libyul/optimiser/KnowledgeBase.h>
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/SideEffects.h>
|
||||
|
||||
// TODO avoid
|
||||
#include <libevmasm/Instruction.h>
|
||||
@ -38,6 +39,7 @@
|
||||
namespace yul
|
||||
{
|
||||
struct Dialect;
|
||||
struct SideEffects;
|
||||
|
||||
/**
|
||||
* Base class to perform data flow analysis during AST walks.
|
||||
@ -67,8 +69,16 @@ struct Dialect;
|
||||
class DataFlowAnalyzer: public ASTModifier
|
||||
{
|
||||
public:
|
||||
explicit DataFlowAnalyzer(Dialect const& _dialect):
|
||||
/// @param _functionSideEffects
|
||||
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
|
||||
/// if this is not provided or the function is not found.
|
||||
/// The parameter is mostly used to determine movability of expressions.
|
||||
explicit DataFlowAnalyzer(
|
||||
Dialect const& _dialect,
|
||||
std::map<YulString, SideEffects> _functionSideEffects = {}
|
||||
):
|
||||
m_dialect(_dialect),
|
||||
m_functionSideEffects(std::move(_functionSideEffects)),
|
||||
m_knowledgeBase(_dialect, m_value)
|
||||
{}
|
||||
|
||||
@ -124,6 +134,9 @@ protected:
|
||||
) const;
|
||||
|
||||
Dialect const& m_dialect;
|
||||
/// Side-effects of user-defined functions. Worst-case side-effects are assumed
|
||||
/// if this is not provided or the function is not found.
|
||||
std::map<YulString, SideEffects> m_functionSideEffects;
|
||||
|
||||
/// Current values of variables, always movable.
|
||||
std::map<YulString, Expression const*> m_value;
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <libyul/optimiser/DeadCodeEliminator.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/AsmData.h>
|
||||
|
||||
#include <libevmasm/SemanticInformation.h>
|
||||
@ -32,6 +33,11 @@ using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
|
||||
void DeadCodeEliminator::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
DeadCodeEliminator{_context.dialect}(_ast);
|
||||
}
|
||||
|
||||
void DeadCodeEliminator::operator()(ForLoop& _for)
|
||||
{
|
||||
yulAssert(_for.pre.statements.empty(), "DeadCodeEliminator needs ForLoopInitRewriter as a prerequisite.");
|
||||
|
@ -29,6 +29,7 @@
|
||||
namespace yul
|
||||
{
|
||||
struct Dialect;
|
||||
struct OptimiserStepContext;
|
||||
|
||||
/**
|
||||
* Optimisation stage that removes unreachable code
|
||||
@ -47,13 +48,16 @@ struct Dialect;
|
||||
class DeadCodeEliminator: public ASTModifier
|
||||
{
|
||||
public:
|
||||
DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
static constexpr char const* name{"DeadCodeEliminator"};
|
||||
static void run(OptimiserStepContext&, Block& _ast);
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(ForLoop& _for) override;
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
private:
|
||||
DeadCodeEliminator(Dialect const& _dialect): m_dialect(_dialect) {}
|
||||
|
||||
Dialect const& m_dialect;
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,7 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
void EquivalentFunctionCombiner::run(Block& _ast)
|
||||
void EquivalentFunctionCombiner::run(OptimiserStepContext&, Block& _ast)
|
||||
{
|
||||
EquivalentFunctionCombiner{EquivalentFunctionDetector::run(_ast)}(_ast);
|
||||
}
|
||||
|
@ -21,11 +21,14 @@
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/EquivalentFunctionDetector.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/AsmDataForward.h>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
|
||||
struct OptimiserStepContext;
|
||||
|
||||
/**
|
||||
* Optimiser component that detects syntactically equivalent functions and replaces all calls to any of them by calls
|
||||
* to one particular of them.
|
||||
@ -35,7 +38,8 @@ namespace yul
|
||||
class EquivalentFunctionCombiner: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static void run(Block& _ast);
|
||||
static constexpr char const* name{"EquivalentFunctionCombiner"};
|
||||
static void run(OptimiserStepContext&, Block& _ast);
|
||||
|
||||
using ASTModifier::operator();
|
||||
void operator()(FunctionCall& _funCall) override;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user