Merge pull request #7497 from ethereum/develop

Merge develop into release for 0.5.12
This commit is contained in:
chriseth 2019-10-01 17:59:34 +02:00 committed by GitHub
commit 7709ece95f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
418 changed files with 9152 additions and 2636 deletions

View File

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

View File

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

View 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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +0,0 @@
# Require C++17.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -750,6 +750,10 @@
"bugs": [],
"released": "2019-08-12"
},
"0.5.12": {
"bugs": [],
"released": "2019-10-01"
},
"0.5.2": {
"bugs": [
"SignedArrayStorageCopy",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": "{...}",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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{}] = {};
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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