mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7219 from ethereum/develop
Merge develop into release.
This commit is contained in:
commit
c082d0b4e8
@ -9,9 +9,6 @@ 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/solc-buildpack-deps:archlinux -f Dockerfile.archlinux .
|
||||
docker push solidity/solc-buildpack-deps:archlinux
|
||||
```
|
||||
|
||||
which you can find on Dockerhub after the push at:
|
||||
|
@ -36,8 +36,9 @@ defaults:
|
||||
mkdir -p build
|
||||
cd build
|
||||
protoc --proto_path=../test/tools/ossfuzz yulProto.proto --cpp_out=../test/tools/ossfuzz
|
||||
protoc --proto_path=../test/tools/ossfuzz abiV2Proto.proto --cpp_out=../test/tools/ossfuzz
|
||||
cmake .. -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-Release} $CMAKE_OPTIONS
|
||||
make ossfuzz ossfuzz_proto -j4
|
||||
make ossfuzz ossfuzz_proto ossfuzz_abiv2 -j4
|
||||
|
||||
- run_proofs: &run_proofs
|
||||
name: Correctness proofs for optimization rules
|
||||
@ -69,6 +70,7 @@ defaults:
|
||||
- artifacts_executables_ossfuzz: &artifacts_executables_ossfuzz
|
||||
root: build
|
||||
paths:
|
||||
- test/tools/ossfuzz/abiv2_proto_ossfuzz
|
||||
- test/tools/ossfuzz/const_opt_ossfuzz
|
||||
- test/tools/ossfuzz/solc_noopt_ossfuzz
|
||||
- test/tools/ossfuzz/solc_opt_ossfuzz
|
||||
@ -179,7 +181,23 @@ jobs:
|
||||
pip install --user codespell
|
||||
- run:
|
||||
name: Check spelling
|
||||
command: ~/.local/bin/codespell -S "*.enc,.git" -I ./scripts/codespell_whitelist.txt
|
||||
command: ~/.local/bin/codespell -S "*.enc,.git,Dockerfile*" -I ./scripts/codespell_whitelist.txt
|
||||
|
||||
chk_docs_examples:
|
||||
docker:
|
||||
- image: circleci/node
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: build
|
||||
- run:
|
||||
name: JS deps
|
||||
command: sudo npm install -g solhint
|
||||
- run:
|
||||
name: Test Docs examples
|
||||
command: ./test/docsCodeStyle.sh
|
||||
|
||||
chk_coding_style:
|
||||
docker:
|
||||
@ -231,6 +249,18 @@ jobs:
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu18: &build_ubuntu1804
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:ubuntu1804
|
||||
environment:
|
||||
CMAKE_OPTIONS: -DCMAKE_CXX_FLAGS=-O2
|
||||
CMAKE_BUILD_TYPE: RelWithDebugInfo
|
||||
steps:
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
- persist_to_workspace: *artifacts_executables
|
||||
|
||||
b_ubu_codecov:
|
||||
<<: *build_ubuntu1904
|
||||
environment:
|
||||
@ -252,7 +282,7 @@ jobs:
|
||||
at: build
|
||||
- run:
|
||||
name: "soltest: Syntax Tests"
|
||||
command: build/test/soltest -t 'syntaxTest*' -- --no-ipc --testpath test
|
||||
command: build/test/soltest -t 'syntaxTest*' -- --testpath test
|
||||
- run:
|
||||
name: "Code Coverage: Syntax Tests"
|
||||
command: codecov --flags syntax --gcov-root build
|
||||
@ -303,10 +333,14 @@ jobs:
|
||||
|
||||
b_archlinux:
|
||||
docker:
|
||||
- image: ethereum/solidity-buildpack-deps:archlinux
|
||||
- image: archlinux/base
|
||||
environment:
|
||||
TERM: xterm
|
||||
steps:
|
||||
- run:
|
||||
name: Install build dependencies
|
||||
command: |
|
||||
pacman --noconfirm -Syu --noprogressbar --needed base-devel boost cmake z3 cvc4 git openssh tar
|
||||
- checkout
|
||||
- run: *run_build
|
||||
- store_artifacts: *artifacts_solc
|
||||
@ -444,7 +478,6 @@ jobs:
|
||||
environment:
|
||||
EVM: constantinople
|
||||
OPTIMIZE: 0
|
||||
SOLTEST_IPC: 0
|
||||
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:
|
||||
@ -572,6 +605,7 @@ workflows:
|
||||
# basic checks
|
||||
- chk_spelling: *workflow_trigger_on_tags
|
||||
- chk_coding_style: *workflow_trigger_on_tags
|
||||
- chk_docs_examples: *workflow_trigger_on_tags
|
||||
- chk_buglist: *workflow_trigger_on_tags
|
||||
- chk_proofs: *workflow_trigger_on_tags
|
||||
|
||||
@ -587,6 +621,7 @@ workflows:
|
||||
|
||||
# Ubuntu 18.10 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
|
||||
@ -631,4 +666,3 @@ workflows:
|
||||
# Code Coverage enabled build and tests
|
||||
- b_ubu_codecov: *workflow_trigger_on_tags
|
||||
- t_ubu_codecov: *workflow_ubuntu1904_codecov
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
# vim:syntax=dockerfile
|
||||
#------------------------------------------------------------------------------
|
||||
# Dockerfile for building and testing Solidity Compiler on CI
|
||||
# Target: Arch Linux
|
||||
# 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 archlinux/base
|
||||
|
||||
RUN pacman --noconfirm -Syu --noprogressbar --needed \
|
||||
base-devel boost cmake z3 cvc4 git openssh tar
|
||||
|
@ -21,11 +21,14 @@
|
||||
#
|
||||
# (c) 2016-2019 solidity contributors.
|
||||
#------------------------------------------------------------------------------
|
||||
FROM buildpack-deps:disco
|
||||
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 \
|
||||
@ -34,21 +37,14 @@ RUN set -ex; \
|
||||
libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev \
|
||||
libboost-program-options-dev \
|
||||
libjsoncpp-dev \
|
||||
llvm-8-dev libcvc4-dev libleveldb1d \
|
||||
llvm-8-dev libcvc4-dev libz3-static-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; \
|
||||
pip install codecov; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Aleth for end-to-end tests
|
||||
ARG ALETH_VERSION="1.6.0"
|
||||
ARG ALETH_HASH="7f7004e1563299bc57882e32b32e4a195747dfb6"
|
||||
ARG ALETH_URL="https://github.com/ethereum/aleth/releases/download/v${ALETH_VERSION}/aleth-${ALETH_VERSION}-linux-x86_64.tar.gz"
|
||||
RUN set -ex; \
|
||||
wget -q -O /tmp/aleth.tar.gz "${ALETH_URL}"; \
|
||||
test "$(shasum /tmp/aleth.tar.gz)" = "$ALETH_HASH /tmp/aleth.tar.gz"; \
|
||||
tar -xf /tmp/aleth.tar.gz -C /usr
|
||||
FROM base AS libraries
|
||||
|
||||
# Z3
|
||||
RUN set -ex; \
|
||||
@ -60,21 +56,23 @@ RUN set -ex; \
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/z3
|
||||
|
||||
# OSSFUZZ: LPM package (do not remove build dirs as solidity compiles/links against that dir)
|
||||
# OSSFUZZ: libprotobuf-mutator
|
||||
RUN set -ex; \
|
||||
mkdir /src; \
|
||||
cd /src; \
|
||||
git clone https://github.com/google/libprotobuf-mutator.git; \
|
||||
cd libprotobuf-mutator; \
|
||||
git clone https://github.com/google/libprotobuf-mutator.git \
|
||||
/usr/src/libprotobuf-mutator; \
|
||||
cd /usr/src/libprotobuf-mutator; \
|
||||
git checkout d1fe8a7d8ae18f3d454f055eba5213c291986f21; \
|
||||
mkdir ../LPM; \
|
||||
cd ../LPM; \
|
||||
cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release; \
|
||||
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
|
||||
ninja install/strip; \
|
||||
rm -rf /usr/src/libprotobuf-mutator
|
||||
|
||||
# OSSFUZZ: libfuzzer
|
||||
RUN set -ex; \
|
||||
@ -113,12 +111,16 @@ RUN set -ex; \
|
||||
# EVMONE
|
||||
RUN set -ex; \
|
||||
cd /usr/src; \
|
||||
git clone --branch="v0.1.0" --recurse-submodules https://github.com/chfast/evmone.git; \
|
||||
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=OFF -DCMAKE_INSTALL_PREFIX="/usr" ..; \
|
||||
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
|
||||
|
@ -36,27 +36,17 @@ set -e
|
||||
OPTIMIZE=${OPTIMIZE:-"0"}
|
||||
EVM=${EVM:-"invalid"}
|
||||
WORKDIR=${CIRCLE_WORKING_DIRECTORY:-.}
|
||||
SOLTEST_IPC=${SOLTEST_IPC:-1}
|
||||
REPODIR="$(realpath $(dirname $0)/..)"
|
||||
ALETH_PATH="/usr/bin/aleth"
|
||||
|
||||
source "${REPODIR}/scripts/common.sh"
|
||||
# Test result output directory (CircleCI is reading test results from here)
|
||||
mkdir -p test_results
|
||||
|
||||
ALETH_PID=$(run_aleth)
|
||||
|
||||
function cleanup() {
|
||||
safe_kill $ALETH_PID $ALETH_PATH
|
||||
}
|
||||
trap cleanup INT TERM
|
||||
|
||||
# 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"
|
||||
SOLTEST_ARGS="--evm-version=$EVM --ipcpath "${WORKDIR}/geth.ipc" $flags"
|
||||
test "${SOLTEST_IPC}" = "1" || SOLTEST_ARGS="$SOLTEST_ARGS --no-ipc"
|
||||
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"
|
||||
|
||||
|
@ -10,8 +10,8 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.10")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
set(PROJECT_VERSION "0.5.11")
|
||||
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
|
||||
|
34
Changelog.md
34
Changelog.md
@ -1,3 +1,35 @@
|
||||
### 0.5.11 (2019-08-12)
|
||||
|
||||
|
||||
Language Features:
|
||||
* Inline Assembly: Support direct constants of value type in inline assembly.
|
||||
|
||||
Compiler Features:
|
||||
* ABI: Additional internal type info in the field ``internalType``.
|
||||
* eWasm: Highly experimental eWasm output using ``--ewasm`` in the commandline interface or output selection of ``ewasm.wast`` in standard-json.
|
||||
* Metadata: Update the swarm hash to the current specification, changes ``bzzr0`` to ``bzzr1`` and urls to use ``bzz-raw://``.
|
||||
* Standard JSON Interface: Compile only selected sources and contracts.
|
||||
* 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.
|
||||
* 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.
|
||||
* SMTChecker: Fix internal error when inlining functions that contain tuple expressions.
|
||||
* SMTChecker: Fix pointer knowledge erasing in loops.
|
||||
* SMTChecker: Fix internal error when using compound bitwise assignment operators inside branches.
|
||||
* SMTChecker: Fix internal error when inlining a function that returns a tuple containing an unsupported type inside a branch.
|
||||
* SMTChecker: Fix internal error when inlining functions that use state variables and belong to a different source.
|
||||
* SMTChecker: Fix internal error when reporting counterexamples concerning state variables from different source files.
|
||||
* SMTChecker: Fix SMT sort mismatch when using string literals.
|
||||
* View/Pure Checker: Properly detect state variable access through base class.
|
||||
* Yul Analyzer: Check availability of data objects already in analysis phase.
|
||||
* Yul Optimizer: Fix an issue where memory-accessing code was removed even though ``msize`` was used in the program.
|
||||
|
||||
|
||||
### 0.5.10 (2019-06-25)
|
||||
|
||||
Important Bugfixes:
|
||||
@ -6,7 +38,7 @@ Important Bugfixes:
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch.
|
||||
* Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch or StandardCompiler `settings.parserErrorRecovery` boolean.
|
||||
* Optimizer: Add rule to simplify ``SUB(~0, X)`` to ``NOT(X)``.
|
||||
* Yul Optimizer: Make the optimizer work for all dialects of Yul including eWasm.
|
||||
|
||||
|
10
README.md
10
README.md
@ -17,14 +17,14 @@ Solidity is a statically typed, contract-oriented, high-level language for imple
|
||||
|
||||
Solidity is a statically-typed curly-braces programming language designed for developing smart contracts
|
||||
that run on the Ethereum Virtual Machine. Smart contracts are programs that are executed inside a peer-to-peer
|
||||
network where nobody has special authority over the execution and thus they allow to implement tokens of value,
|
||||
network where nobody has special authority over the execution, and thus they allow to implement tokens of value,
|
||||
ownership, voting and other kinds of logics.
|
||||
|
||||
When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number [to indicate this fast pace of change](https://semver.org/#spec-item-4).
|
||||
|
||||
## Build and Install
|
||||
|
||||
Instructions about how to build and install the Solidity compiler can be found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source)
|
||||
Instructions about how to build and install the Solidity compiler can be found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source).
|
||||
|
||||
|
||||
## Example
|
||||
@ -61,10 +61,10 @@ Please follow the
|
||||
if you want to help.
|
||||
|
||||
## Maintainers
|
||||
[@axic](https://github.com/axic)
|
||||
[@chriseth](https://github.com/chriseth)
|
||||
* [@axic](https://github.com/axic)
|
||||
* [@chriseth](https://github.com/chriseth)
|
||||
|
||||
## License
|
||||
Solidity is licensed under [GNU General Public License v3.0](LICENSE.txt)
|
||||
Solidity is licensed under [GNU General Public License v3.0](LICENSE.txt).
|
||||
|
||||
Some third-party code has its [own licensing terms](cmake/templates/license.h.in).
|
||||
|
@ -43,6 +43,7 @@
|
||||
### Release solc-js
|
||||
- [ ] Increment the version number, create a pull request for that, merge it after tests succeeded.
|
||||
- [ ] Run ``npm publish`` in the updated ``solc-js`` repository.
|
||||
- [ ] Make sure to push the tag ``npm publish`` created with ``git push --tags``.
|
||||
|
||||
### Post-release
|
||||
- [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.
|
||||
|
@ -5,9 +5,6 @@
|
||||
#
|
||||
# http://solidity.readthedocs.org
|
||||
#
|
||||
# TODO - Tests currently disabled, because Tests-over-IPC code is using UNIX
|
||||
# sockets unconditionally at the time of writing.
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
# This file is part of solidity.
|
||||
#
|
||||
@ -71,7 +68,7 @@ build_script:
|
||||
|
||||
test_script:
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
|
||||
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-ipc --no-smt
|
||||
- soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-smt
|
||||
# Skip bytecode compare if private key is not available
|
||||
- cd %APPVEYOR_BUILD_FOLDER%
|
||||
- ps: if ($env:priv_key) {
|
||||
|
@ -97,6 +97,22 @@ scanner/token:
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
evmc:
|
||||
The code in test/evmc is licensed under the Apache License version 2:
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
All other code is licensed under GPL version 3:
|
||||
|
||||
)"};
|
||||
|
@ -141,19 +141,19 @@ on the type of ``X`` being
|
||||
``enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))``
|
||||
|
||||
where ``X = (X(1), ..., X(k))`` and
|
||||
``head`` and ``tail`` are defined for ``Ti`` being a static type as
|
||||
``head`` and ``tail`` are defined for ``Ti`` as follows:
|
||||
|
||||
if ``Ti`` is static:
|
||||
|
||||
``head(X(i)) = enc(X(i))`` and ``tail(X(i)) = ""`` (the empty string)
|
||||
|
||||
and as
|
||||
otherwise, i.e. if ``Ti`` is dynamic:
|
||||
|
||||
``head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))``
|
||||
``head(X(i)) = enc(len( head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1)) ))``
|
||||
``tail(X(i)) = enc(X(i))``
|
||||
|
||||
otherwise, i.e. if ``Ti`` is a dynamic type.
|
||||
|
||||
Note that in the dynamic case, ``head(X(i))`` is well-defined since the lengths of
|
||||
the head parts only depend on the types and not the values. Its value is the offset
|
||||
the head parts only depend on the types and not the values. The value of ``head(X(i))`` is the offset
|
||||
of the beginning of ``tail(X(i))`` relative to the start of ``enc(X)``.
|
||||
|
||||
- ``T[k]`` for any ``T`` and ``k``:
|
||||
|
@ -1,4 +1,15 @@
|
||||
[
|
||||
{
|
||||
"name": "ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
"summary": "Reading from calldata structs that contain dynamically encoded, but statically-sized members can result in incorrect values.",
|
||||
"description": "When a calldata struct contains a dynamically encoded, but statically-sized member, the offsets for all subsequent struct members are calculated incorrectly. All reads from such members will result in invalid values. Only calldata structs are affected, i.e. this occurs in external functions with such structs as argument. Using affected structs in storage or memory or as arguments to public functions on the other hand works correctly.",
|
||||
"introduced": "0.5.6",
|
||||
"fixed": "0.5.11",
|
||||
"severity": "low",
|
||||
"conditions": {
|
||||
"ABIEncoderV2": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SignedArrayStorageCopy",
|
||||
"summary": "Assigning an array of signed integers to a storage array of different type can lead to data corruption in that array.",
|
||||
|
@ -741,9 +741,15 @@
|
||||
"released": "2018-12-03"
|
||||
},
|
||||
"0.5.10": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers"
|
||||
],
|
||||
"released": "2019-06-25"
|
||||
},
|
||||
"0.5.11": {
|
||||
"bugs": [],
|
||||
"released": "2019-08-12"
|
||||
},
|
||||
"0.5.2": {
|
||||
"bugs": [
|
||||
"SignedArrayStorageCopy",
|
||||
@ -792,6 +798,7 @@
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
"SignedArrayStorageCopy",
|
||||
"ABIEncoderV2StorageArrayWithMultiSlotElement",
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
@ -804,6 +811,7 @@
|
||||
},
|
||||
"0.5.7": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
"SignedArrayStorageCopy",
|
||||
"ABIEncoderV2StorageArrayWithMultiSlotElement",
|
||||
"DynamicConstructorArgumentsClippedABIV2",
|
||||
@ -814,6 +822,7 @@
|
||||
},
|
||||
"0.5.8": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
"SignedArrayStorageCopy",
|
||||
"ABIEncoderV2StorageArrayWithMultiSlotElement",
|
||||
"DynamicConstructorArgumentsClippedABIV2"
|
||||
@ -822,6 +831,7 @@
|
||||
},
|
||||
"0.5.9": {
|
||||
"bugs": [
|
||||
"ABIEncoderV2CalldataStructsWithStaticallySizedAndDynamicallyEncodedMembers",
|
||||
"SignedArrayStorageCopy",
|
||||
"ABIEncoderV2StorageArrayWithMultiSlotElement"
|
||||
],
|
||||
|
@ -297,6 +297,43 @@ The reason for this is that ``C`` requests ``X`` to override ``A``
|
||||
requests to override ``X``, which is a contradiction that
|
||||
cannot be resolved.
|
||||
|
||||
One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract's constructor. For example:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Base1 {
|
||||
constructor() public {}
|
||||
}
|
||||
|
||||
contract Base2 {
|
||||
constructor() public {}
|
||||
}
|
||||
|
||||
// Constructors are executed in the following order:
|
||||
// 1 - Base1
|
||||
// 2 - Base2
|
||||
// 3 - Derived1
|
||||
contract Derived1 is Base1, Base2 {
|
||||
constructor() public Base1() Base2() {}
|
||||
}
|
||||
|
||||
// Constructors are executed in the following order:
|
||||
// 1 - Base2
|
||||
// 2 - Base1
|
||||
// 3 - Derived2
|
||||
contract Derived2 is Base2, Base1 {
|
||||
constructor() public Base2() Base1() {}
|
||||
}
|
||||
|
||||
// Constructors are still executed in the following order:
|
||||
// 1 - Base2
|
||||
// 2 - Base1
|
||||
// 3 - Derived3
|
||||
contract Derived3 is Base2, Base1 {
|
||||
constructor() public Base1() Base2() {}
|
||||
}
|
||||
|
||||
|
||||
Inheriting Different Kinds of Members of the Same Name
|
||||
|
@ -21,6 +21,17 @@ In particular, we need help in the following areas:
|
||||
|
||||
Please note that this project is released with a `Contributor Code of Conduct <https://raw.githubusercontent.com/ethereum/solidity/develop/CODE_OF_CONDUCT.md>`_. By participating in this project - in the issues, pull requests, or Gitter channels - you agree to abide by its terms.
|
||||
|
||||
Team Calls
|
||||
==========
|
||||
|
||||
If you have issues or pull requests to discuss, or are interested in hearing what
|
||||
the team and contributors are working on, you can join our public team calls:
|
||||
|
||||
- Monday at 12pm CET
|
||||
- Wednesday at 3pm CET
|
||||
|
||||
Both calls take place on `Google Hangouts <https://hangouts.google.com/hangouts/_/ethereum.org/solidity-weekly>`_.
|
||||
|
||||
How to Report Issues
|
||||
====================
|
||||
|
||||
@ -70,15 +81,23 @@ Thank you for your help!
|
||||
Running the compiler tests
|
||||
==========================
|
||||
|
||||
The ``./scripts/tests.sh`` script executes most Solidity tests and
|
||||
runs ``aleth`` automatically if it is in the path. The script does not download it,
|
||||
so you need to install it first. Please read on for the details.
|
||||
The ``./scripts/tests.sh`` script executes most Solidity tests automatically,
|
||||
but for quicker feedback, you might want to run specific tests.
|
||||
|
||||
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``.
|
||||
Some of them require the ``aleth`` client in testing mode, others require ``libz3``.
|
||||
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.
|
||||
|
||||
To run a basic set of tests that require neither ``aleth`` nor ``libz3``, run
|
||||
``./scripts/soltest.sh --no-ipc --no-smt``.
|
||||
Some tests require the ``libevmone.so`` 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
|
||||
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>`_
|
||||
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:
|
||||
``./scripts/soltest.sh --no-smt``.
|
||||
|
||||
``./build/test/soltest --help`` has extensive help on all of the options available.
|
||||
See especially:
|
||||
@ -89,27 +108,19 @@ See especially:
|
||||
|
||||
.. note ::
|
||||
|
||||
Those working in a Windows environment wanting to run the above basic sets without aleth or libz3 in Git Bash, you would have to do: ``./build/test/Release/soltest.exe -- --no-ipc --no-smt``.
|
||||
If you're running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-ipc --no-smt``.
|
||||
|
||||
The option ``--no-smt`` disables the tests that require ``libz3`` and
|
||||
``--no-ipc`` disables those that require ``aleth``.
|
||||
|
||||
If you want to run the ipc tests (that test the semantics of the generated code),
|
||||
you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0/aleth-1.6.0-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``.
|
||||
|
||||
To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``.
|
||||
Those working in a Windows environment wanting to run the above basic sets without libz3 in Git Bash, you would have to do: ``./build/test/Release/soltest.exe -- --no-smt``.
|
||||
If you are running this in plain Command Prompt, use ``.\build\test\Release\soltest.exe -- --no-smt``.
|
||||
|
||||
To run a subset of tests, you can use filters:
|
||||
``./scripts/soltest.sh -t TestSuite/TestName --ipcpath /tmp/testeth/geth.ipc``,
|
||||
``./scripts/soltest.sh -t TestSuite/TestName,
|
||||
where ``TestName`` can be a wildcard ``*``.
|
||||
|
||||
For example, here's an example test you might run;
|
||||
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-ipc --no-smt``.
|
||||
For example, here is an example test you might run;
|
||||
``./scripts/soltest.sh -t "yulOptimizerTests/disambiguator/*" --no-smt``.
|
||||
This will test all the tests for the disambiguator.
|
||||
|
||||
To get a list of all tests, use
|
||||
``./build/test/soltest --list_content=HRF -- --ipcpath /tmp/irrelevant``.
|
||||
``./build/test/soltest --list_content=HRF``.
|
||||
|
||||
If you want to debug using GDB, make sure you build differently than the "usual".
|
||||
For example, you could run the following command in your ``build`` folder:
|
||||
@ -126,11 +137,6 @@ in addition to those found in ``soltest``.
|
||||
|
||||
The CI runs additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target.
|
||||
|
||||
.. note ::
|
||||
|
||||
Some versions of ``aleth`` can not be used for testing. We suggest using
|
||||
the same version that the Solidity continuous integration tests use.
|
||||
Currently the CI uses version ``1.6.0`` of ``aleth``.
|
||||
|
||||
Writing and running syntax tests
|
||||
--------------------------------
|
||||
|
@ -271,19 +271,19 @@ because only a reference and not a copy is passed.
|
||||
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
contract C {
|
||||
uint[20] x;
|
||||
|
||||
function f() public {
|
||||
function f() public {
|
||||
g(x);
|
||||
h(x);
|
||||
}
|
||||
|
||||
function g(uint[20] memory y) internal pure {
|
||||
function g(uint[20] memory y) internal pure {
|
||||
y[2] = 3;
|
||||
}
|
||||
|
||||
function h(uint[20] storage y) internal {
|
||||
function h(uint[20] storage y) internal {
|
||||
y[3] = 4;
|
||||
}
|
||||
}
|
||||
@ -354,7 +354,7 @@ In any case, you will get a warning about the outer variable being shadowed.
|
||||
for the entire function, regardless where it was declared. The following example shows a code snippet that used
|
||||
to compile but leads to an error starting from version 0.5.0.
|
||||
|
||||
::
|
||||
::
|
||||
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
// This will not compile
|
||||
|
@ -65,12 +65,13 @@ Community volunteers help translate this documentation into several languages.
|
||||
They have varying degrees of completeness and up-to-dateness. The English
|
||||
version stands as a reference.
|
||||
|
||||
* `French <http://solidity-fr.readthedocs.io>`_ (in progress)
|
||||
* `Japanese <https://solidity-jp.readthedocs.io>`_
|
||||
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
|
||||
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
|
||||
* `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress)
|
||||
* `Spanish <https://solidity-es.readthedocs.io>`_
|
||||
* `Turkish <https://github.com/denizozzgur/Solidity_TR/blob/master/README.md>`_ (partial)
|
||||
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
|
||||
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
|
||||
* `French <http://solidity-fr.readthedocs.io>`_ (in progress)
|
||||
|
||||
Contents
|
||||
========
|
||||
|
@ -208,6 +208,15 @@ The following are dependencies for all builds of Solidity:
|
||||
|
||||
Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention.
|
||||
|
||||
Minimum compiler versions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following C++ compilers and their minimum versions can build the Solidity codebase:
|
||||
|
||||
- `GCC <https://gcc.gnu.org>`_, version 5+
|
||||
- `Clang <https://clang.llvm.org/>`_, version 3.4+
|
||||
- `MSVC <https://docs.microsoft.com/en-us/cpp/?view=vs-2019>`_, version 2017+
|
||||
|
||||
Prerequisites - macOS
|
||||
---------------------
|
||||
|
||||
|
@ -186,6 +186,6 @@ This automatically verifies the metadata since its hash is part of the bytecode.
|
||||
Excess data corresponds to the constructor input data, which should be decoded
|
||||
according to the interface and presented to the user.
|
||||
|
||||
In the repository [source-verify](https://github.com/ethereum/source-verify)
|
||||
([npm package](https://www.npmjs.com/package/source-verify)) you can see
|
||||
In the repository `source-verify <https://github.com/ethereum/source-verify>`_
|
||||
(`npm package <https://www.npmjs.com/package/source-verify>`_) you can see
|
||||
example code that shows how to use this feature.
|
||||
|
@ -12,7 +12,7 @@ This documentation is segmented into developer-focused messages and end-user-fac
|
||||
messages. These messages may be shown to the end user (the human) at the
|
||||
time that they will interact with the contract (i.e. sign a transaction).
|
||||
|
||||
It is recommended that Solidity contracts are fully annontated using NatSpec for
|
||||
It is recommended that Solidity contracts are fully annotated using NatSpec for
|
||||
all public interfaces (everything in the ABI).
|
||||
|
||||
NatSpec includes the formatting for comments that the smart contract author will
|
||||
|
@ -196,7 +196,7 @@ Operators:
|
||||
.. warning::
|
||||
If you convert a type that uses a larger byte size to an ``address``, for example ``bytes32``, then the ``address`` is truncated.
|
||||
To reduce conversion ambiguity version 0.4.24 and higher of the compiler force you make the truncation explicit in the conversion.
|
||||
Take for example the address ``0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC``.
|
||||
Take for example the 32-byte value ``0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC``.
|
||||
|
||||
You can use ``address(uint160(bytes20(b)))``, which results in ``0x111122223333444455556666777788889999aAaa``,
|
||||
or you can use ``address(uint160(uint256(b)))``, which results in ``0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc``.
|
||||
@ -386,7 +386,7 @@ Hexadecimal literals that pass the address checksum test, for example
|
||||
``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address payable`` type.
|
||||
Hexadecimal literals that are between 39 and 41 digits
|
||||
long and do not pass the checksum test produce
|
||||
a warning and are treated as regular rational number literals.
|
||||
an error. You can prepend (for integer types) or append (for bytesNN types) zeros to remove the error.
|
||||
|
||||
.. note::
|
||||
The mixed-case address checksum format is defined in `EIP-55 <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>`_.
|
||||
|
@ -193,15 +193,18 @@ Input Description
|
||||
// disabled by default
|
||||
"enabled": true,
|
||||
// Optimize for how many times you intend to run the code.
|
||||
// Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage.
|
||||
// Lower values will optimize more for initial deployment cost, higher
|
||||
// values will optimize more for high-frequency usage.
|
||||
"runs": 200,
|
||||
// Switch optimizer components on or off in detail.
|
||||
// The "enabled" switch above provides two defaults which can be
|
||||
// tweaked here. If "details" is given, "enabled" can be omitted.
|
||||
"details": {
|
||||
// The peephole optimizer is always on if no details are given, use details to switch it off.
|
||||
// The peephole optimizer is always on if no details are given,
|
||||
// use details to switch it off.
|
||||
"peephole": true,
|
||||
// The unused jumpdest remover is always on if no details are given, use details to switch it off.
|
||||
// The unused jumpdest remover is always on if no details are given,
|
||||
// use details to switch it off.
|
||||
"jumpdestRemover": true,
|
||||
// Sometimes re-orders literals in commutative operations.
|
||||
"orderLiterals": false,
|
||||
@ -224,16 +227,21 @@ Input Description
|
||||
}
|
||||
}
|
||||
},
|
||||
"evmVersion": "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg
|
||||
// Version of the EVM to compile for.
|
||||
// Affects type checking and code generation. Can be homestead,
|
||||
// tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg
|
||||
"evmVersion": "byzantium",
|
||||
// Metadata settings (optional)
|
||||
"metadata": {
|
||||
// Use only literal content and not URLs (false by default)
|
||||
"useLiteralContent": true
|
||||
},
|
||||
// Addresses of the libraries. If not all libraries are given here, it can result in unlinked objects whose output data is different.
|
||||
// Addresses of the libraries. If not all libraries are given here,
|
||||
// it can result in unlinked objects whose output data is different.
|
||||
"libraries": {
|
||||
// The top level key is the the name of the source file where the library is used.
|
||||
// If remappings are used, this source file should match the global path after remappings were applied.
|
||||
// If remappings are used, this source file should match the global path
|
||||
// after remappings were applied.
|
||||
// If this key is an empty string, that refers to a global level.
|
||||
"myFile.sol": {
|
||||
"MyLib": "0x123123..."
|
||||
@ -314,6 +322,15 @@ Output Description
|
||||
"start": 0,
|
||||
"end": 100
|
||||
],
|
||||
// Optional: Further locations (e.g. places of conflicting declarations)
|
||||
"secondarySourceLocations": [
|
||||
{
|
||||
"file": "sourceFile.sol",
|
||||
"start": 64,
|
||||
"end": 92,
|
||||
"message": "Other declaration is here:"
|
||||
}
|
||||
],
|
||||
// Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc.
|
||||
// See below for complete list of types.
|
||||
"type": "TypeError",
|
||||
@ -327,7 +344,8 @@ Output Description
|
||||
"formattedMessage": "sourceFile.sol:100: Invalid keyword"
|
||||
}
|
||||
],
|
||||
// This contains the file-level outputs. In can be limited/filtered by the outputSelection settings.
|
||||
// This contains the file-level outputs.
|
||||
// It can be limited/filtered by the outputSelection settings.
|
||||
"sources": {
|
||||
"sourceFile.sol": {
|
||||
// Identifier of the source (used in source maps)
|
||||
@ -338,7 +356,8 @@ Output Description
|
||||
"legacyAST": {}
|
||||
}
|
||||
},
|
||||
// This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings.
|
||||
// This contains the contract-level outputs.
|
||||
// It can be limited/filtered by the outputSelection settings.
|
||||
"contracts": {
|
||||
"sourceFile.sol": {
|
||||
// If the language used has no contract names, this field should equal to an empty string.
|
||||
@ -371,7 +390,8 @@ Output Description
|
||||
// If given, this is an unlinked object.
|
||||
"linkReferences": {
|
||||
"libraryFile.sol": {
|
||||
// Byte offsets into the bytecode. Linking replaces the 20 bytes located there.
|
||||
// Byte offsets into the bytecode.
|
||||
// Linking replaces the 20 bytes located there.
|
||||
"Library1": [
|
||||
{ "start": 0, "length": 20 },
|
||||
{ "start": 200, "length": 20 }
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
/// Operators need to stay in the global namespace.
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -44,9 +46,9 @@ namespace
|
||||
class StreamWriterBuilder: public Json::StreamWriterBuilder
|
||||
{
|
||||
public:
|
||||
explicit StreamWriterBuilder(map<string, string> const& _settings)
|
||||
explicit StreamWriterBuilder(map<string, Json::Value> const& _settings)
|
||||
{
|
||||
for (auto const& iter :_settings)
|
||||
for (auto const& iter: _settings)
|
||||
this->settings_[iter.first] = iter.second;
|
||||
}
|
||||
};
|
||||
@ -89,14 +91,16 @@ bool parse(Json::CharReaderBuilder& _builder, string const& _input, Json::Value&
|
||||
|
||||
string jsonPrettyPrint(Json::Value const& _input)
|
||||
{
|
||||
static map<string, string> settings{{"indentation", " "}};
|
||||
static map<string, Json::Value> settings{{"indentation", " "}, {"enableYAMLCompatibility", true}};
|
||||
static StreamWriterBuilder writerBuilder(settings);
|
||||
return print(_input, writerBuilder);
|
||||
string result = print(_input, writerBuilder);
|
||||
boost::replace_all(result, " \n", "\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
string jsonCompactPrint(Json::Value const& _input)
|
||||
{
|
||||
static map<string, string> settings{{"indentation", ""}};
|
||||
static map<string, Json::Value> settings{{"indentation", ""}};
|
||||
static StreamWriterBuilder writerBuilder(settings);
|
||||
return print(_input, writerBuilder);
|
||||
}
|
||||
|
@ -96,3 +96,20 @@ string dev::quotedAlternativesList(vector<string> const& suggestions)
|
||||
return joinHumanReadable(quotedSuggestions, ", ", " or ");
|
||||
}
|
||||
|
||||
string dev::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
if (_startSuffix < _endSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_startSuffix++);
|
||||
while (_startSuffix < _endSuffix)
|
||||
result += ", " + _baseName + to_string(_startSuffix++);
|
||||
}
|
||||
else if (_endSuffix < _startSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_endSuffix++);
|
||||
while (_endSuffix < _startSuffix)
|
||||
result = _baseName + to_string(_endSuffix++) + ", " + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ size_t stringDistance(std::string const& _str1, std::string const& _str2);
|
||||
// Return a string having elements of suggestions as quoted, alternative suggestions. e.g. "a", "b" or "c"
|
||||
std::string quotedAlternativesList(std::vector<std::string> const& suggestions);
|
||||
|
||||
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
|
||||
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
|
||||
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
|
||||
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
||||
std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
||||
|
||||
/// Joins collection of strings into one string with separators between, last separator can be different.
|
||||
/// @param _list collection of strings to join
|
||||
/// @param _separator defaults to ", "
|
||||
|
@ -61,9 +61,56 @@ h256 swarmHashIntermediate(string const& _input, size_t _offset, size_t _length)
|
||||
return swarmHashSimple(ref, _length);
|
||||
}
|
||||
|
||||
h256 bmtHash(bytesConstRef _data)
|
||||
{
|
||||
if (_data.size() <= 64)
|
||||
return keccak256(_data);
|
||||
|
||||
size_t midPoint = _data.size() / 2;
|
||||
return keccak256(
|
||||
bmtHash(_data.cropped(0, midPoint)).asBytes() +
|
||||
bmtHash(_data.cropped(midPoint)).asBytes()
|
||||
);
|
||||
}
|
||||
|
||||
h256 dev::swarmHash(string const& _input)
|
||||
h256 chunkHash(bytesConstRef const _data, bool _forceHigherLevel = false)
|
||||
{
|
||||
bytes dataToHash;
|
||||
if (_data.size() < 0x1000)
|
||||
dataToHash = _data.toBytes();
|
||||
else if (_data.size() == 0x1000 && !_forceHigherLevel)
|
||||
dataToHash = _data.toBytes();
|
||||
else
|
||||
{
|
||||
size_t maxRepresentedSize = 0x1000;
|
||||
while (maxRepresentedSize * (0x1000 / 32) < _data.size())
|
||||
maxRepresentedSize *= (0x1000 / 32);
|
||||
// If remaining size is 0x1000, but maxRepresentedSize is not,
|
||||
// we have to still do one level of the chunk hashes.
|
||||
bool forceHigher = maxRepresentedSize > 0x1000;
|
||||
for (size_t i = 0; i < _data.size(); i += maxRepresentedSize)
|
||||
{
|
||||
size_t size = std::min(maxRepresentedSize, _data.size() - i);
|
||||
dataToHash += chunkHash(_data.cropped(i, size), forceHigher).asBytes();
|
||||
}
|
||||
}
|
||||
|
||||
dataToHash.resize(0x1000, 0);
|
||||
return keccak256(toLittleEndian(_data.size()) + bmtHash(&dataToHash).asBytes());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
h256 dev::bzzr0Hash(string const& _input)
|
||||
{
|
||||
return swarmHashIntermediate(_input, 0, _input.size());
|
||||
}
|
||||
|
||||
|
||||
h256 dev::bzzr1Hash(bytes const& _input)
|
||||
{
|
||||
if (_input.empty())
|
||||
return h256{};
|
||||
return chunkHash(&_input);
|
||||
}
|
||||
|
@ -26,7 +26,15 @@
|
||||
namespace dev
|
||||
{
|
||||
|
||||
/// Compute the "swarm hash" of @a _input
|
||||
h256 swarmHash(std::string const& _input);
|
||||
/// Compute the "swarm hash" of @a _input (OLD 0x1000-section version)
|
||||
h256 bzzr0Hash(std::string const& _input);
|
||||
|
||||
/// Compute the "bzz hash" of @a _input (the NEW binary / BMT version)
|
||||
h256 bzzr1Hash(bytes const& _input);
|
||||
|
||||
inline h256 bzzr1Hash(std::string const& _input)
|
||||
{
|
||||
return bzzr1Hash(asBytes(_input));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -85,6 +85,8 @@ public:
|
||||
std::string render() const;
|
||||
|
||||
private:
|
||||
// Prevent implicit cast to bool
|
||||
Whiskers& operator()(std::string _parameter, long long);
|
||||
void checkParameterValid(std::string const& _parameter) const;
|
||||
void checkParameterUnknown(std::string const& _parameter) const;
|
||||
|
||||
|
@ -22,7 +22,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libdevcore/Assertions.h>
|
||||
#include <libdevcore/Common.h> // defines noexcept macro for MSVC
|
||||
#include <libdevcore/Exceptions.h>
|
||||
#include <liblangutil/CharStream.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -31,6 +33,7 @@
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
struct SourceLocationError: virtual dev::Exception {};
|
||||
|
||||
/**
|
||||
* Representation of an interval of source positions.
|
||||
@ -49,6 +52,15 @@ struct SourceLocation
|
||||
|
||||
bool isEmpty() const { return start == -1 && end == -1; }
|
||||
|
||||
std::string text() const
|
||||
{
|
||||
assertThrow(source, SourceLocationError, "Requested text from null source.");
|
||||
assertThrow(!isEmpty(), SourceLocationError, "Requested text from empty source location.");
|
||||
assertThrow(start <= end, SourceLocationError, "Invalid source location.");
|
||||
assertThrow(end <= int(source->source().length()), SourceLocationError, "Invalid source location.");
|
||||
return source->source().substr(start, end - start);
|
||||
}
|
||||
|
||||
/// @returns the smallest SourceLocation that contains both @param _a and @param _b.
|
||||
/// Assumes that @param _a and @param _b refer to the same source (exception: if the source of either one
|
||||
/// is unset, the source of the other will be used for the result, even if that is unset as well).
|
||||
|
@ -73,7 +73,7 @@ void SourceReferenceFormatterHuman::printSourceLocation(SourceReference const& _
|
||||
|
||||
// line 0: source name
|
||||
frameColored() << string(leftpad, ' ') << "--> ";
|
||||
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ": " << '\n';
|
||||
m_stream << _ref.sourceName << ":" << (_ref.position.line + 1) << ":" << (_ref.position.column + 1) << ":" << '\n';
|
||||
|
||||
if (!_ref.multiline)
|
||||
{
|
||||
|
@ -75,10 +75,17 @@ set(sources
|
||||
codegen/ir/IRGenerationContext.h
|
||||
codegen/ir/IRLValue.cpp
|
||||
codegen/ir/IRLValue.h
|
||||
formal/BMC.cpp
|
||||
formal/BMC.h
|
||||
formal/CHC.cpp
|
||||
formal/CHC.h
|
||||
formal/CHCSolverInterface.h
|
||||
formal/EncodingContext.cpp
|
||||
formal/EncodingContext.h
|
||||
formal/SMTChecker.cpp
|
||||
formal/SMTChecker.h
|
||||
formal/ModelChecker.cpp
|
||||
formal/ModelChecker.h
|
||||
formal/SMTEncoder.cpp
|
||||
formal/SMTEncoder.h
|
||||
formal/SMTLib2Interface.cpp
|
||||
formal/SMTLib2Interface.h
|
||||
formal/SMTPortfolio.cpp
|
||||
@ -117,7 +124,7 @@ find_package(Z3 4.6.0)
|
||||
if (${Z3_FOUND})
|
||||
add_definitions(-DHAVE_Z3)
|
||||
message("Z3 SMT solver found. This enables optional SMT checking with Z3.")
|
||||
set(z3_SRCS formal/Z3Interface.cpp formal/Z3Interface.h)
|
||||
set(z3_SRCS formal/Z3Interface.cpp formal/Z3Interface.h formal/Z3CHCInterface.cpp formal/Z3CHCInterface.h)
|
||||
else()
|
||||
set(z3_SRCS)
|
||||
endif()
|
||||
|
@ -345,8 +345,8 @@ void ContractLevelChecker::annotateBaseConstructorArguments(
|
||||
else
|
||||
{
|
||||
mainLocation = &_currentContract.location();
|
||||
ssl.append("First constructor call is here: ", _argumentNode->location());
|
||||
ssl.append("Second constructor call is here: ", previousNode->location());
|
||||
ssl.append("First constructor call is here:", _argumentNode->location());
|
||||
ssl.append("Second constructor call is here:", previousNode->location());
|
||||
}
|
||||
|
||||
m_errorReporter.declarationError(
|
||||
|
@ -631,10 +631,32 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
solAssert(var->type(), "Expected variable type!");
|
||||
if (var->isConstant())
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "Constant variables not supported by inline assembly.");
|
||||
return size_t(-1);
|
||||
if (!var->value())
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "Constant has no value.");
|
||||
return size_t(-1);
|
||||
}
|
||||
else if (!type(*var)->isValueType() || (
|
||||
dynamic_cast<Literal const*>(var->value().get()) == nullptr &&
|
||||
type(*var->value())->category() != Type::Category::RationalNumber
|
||||
))
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "Only direct number constants are supported by inline assembly.");
|
||||
return size_t(-1);
|
||||
}
|
||||
else if (_context == yul::IdentifierContext::LValue)
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "Constant variables cannot be assigned to.");
|
||||
return size_t(-1);
|
||||
}
|
||||
else if (requiresStorage)
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables.");
|
||||
return size_t(-1);
|
||||
}
|
||||
}
|
||||
else if (requiresStorage)
|
||||
|
||||
if (requiresStorage)
|
||||
{
|
||||
if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
@ -647,7 +669,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
|
||||
return size_t(-1);
|
||||
}
|
||||
}
|
||||
else if (!var->isLocalVariable())
|
||||
else if (!var->isConstant() && var->isStateVariable())
|
||||
{
|
||||
m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes.");
|
||||
return size_t(-1);
|
||||
|
@ -389,8 +389,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(
|
||||
_memberAccess.annotation().referencedDeclaration
|
||||
))
|
||||
if (varDecl->isStateVariable() && !varDecl->isConstant())
|
||||
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
|
||||
break;
|
||||
}
|
||||
}
|
||||
reportMutability(mutability, _memberAccess.location());
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,16 @@ TypePointer ImportDirective::type() const
|
||||
return TypeProvider::module(*annotation().sourceUnit);
|
||||
}
|
||||
|
||||
vector<VariableDeclaration const*> ContractDefinition::stateVariablesIncludingInherited() const
|
||||
{
|
||||
vector<VariableDeclaration const*> stateVars;
|
||||
for (auto const& contract: annotation().linearizedBaseContracts)
|
||||
for (auto var: contract->stateVariables())
|
||||
if (*contract == *this || var->isVisibleInDerivedContracts())
|
||||
stateVars.push_back(var);
|
||||
return stateVars;
|
||||
}
|
||||
|
||||
map<FixedHash<4>, FunctionTypePointer> ContractDefinition::interfaceFunctions() const
|
||||
{
|
||||
auto exportedFunctionList = interfaceFunctionList();
|
||||
|
@ -79,13 +79,15 @@ public:
|
||||
static void listAccept(std::vector<T> const& _list, ASTVisitor& _visitor)
|
||||
{
|
||||
for (T const& element: _list)
|
||||
element->accept(_visitor);
|
||||
if (element)
|
||||
element->accept(_visitor);
|
||||
}
|
||||
template <class T>
|
||||
static void listAccept(std::vector<T> const& _list, ASTConstVisitor& _visitor)
|
||||
{
|
||||
for (T const& element: _list)
|
||||
element->accept(_visitor);
|
||||
if (element)
|
||||
element->accept(_visitor);
|
||||
}
|
||||
|
||||
/// @returns a copy of the vector containing only the nodes which derive from T.
|
||||
@ -394,6 +396,7 @@ public:
|
||||
std::vector<StructDefinition const*> definedStructs() const { return filteredNodes<StructDefinition>(m_subNodes); }
|
||||
std::vector<EnumDefinition const*> definedEnums() const { return filteredNodes<EnumDefinition>(m_subNodes); }
|
||||
std::vector<VariableDeclaration const*> stateVariables() const { return filteredNodes<VariableDeclaration>(m_subNodes); }
|
||||
std::vector<VariableDeclaration const*> stateVariablesIncludingInherited() const;
|
||||
std::vector<ModifierDefinition const*> functionModifiers() const { return filteredNodes<ModifierDefinition>(m_subNodes); }
|
||||
std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); }
|
||||
std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); }
|
||||
|
@ -1647,15 +1647,13 @@ bool ArrayType::validForCalldata() const
|
||||
if (auto arrayBaseType = dynamic_cast<ArrayType const*>(baseType()))
|
||||
if (!arrayBaseType->validForCalldata())
|
||||
return false;
|
||||
return unlimitedCalldataEncodedSize(true) <= numeric_limits<unsigned>::max();
|
||||
return isDynamicallySized() || unlimitedStaticCalldataSize(true) <= numeric_limits<unsigned>::max();
|
||||
}
|
||||
|
||||
bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const
|
||||
bigint ArrayType::unlimitedStaticCalldataSize(bool _padded) const
|
||||
{
|
||||
if (isDynamicallySized())
|
||||
return 32;
|
||||
// Array elements are always padded.
|
||||
bigint size = bigint(length()) * (isByteArray() ? 1 : baseType()->calldataEncodedSize(true));
|
||||
solAssert(!isDynamicallySized(), "");
|
||||
bigint size = bigint(length()) * calldataStride();
|
||||
if (_padded)
|
||||
size = ((size + 31) / 32) * 32;
|
||||
return size;
|
||||
@ -1663,7 +1661,20 @@ bigint ArrayType::unlimitedCalldataEncodedSize(bool _padded) const
|
||||
|
||||
unsigned ArrayType::calldataEncodedSize(bool _padded) const
|
||||
{
|
||||
bigint size = unlimitedCalldataEncodedSize(_padded);
|
||||
solAssert(!isDynamicallyEncoded(), "");
|
||||
bigint size = unlimitedStaticCalldataSize(_padded);
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
|
||||
return unsigned(size);
|
||||
}
|
||||
|
||||
unsigned ArrayType::calldataEncodedTailSize() const
|
||||
{
|
||||
solAssert(isDynamicallyEncoded(), "");
|
||||
if (isDynamicallySized())
|
||||
// We do not know the dynamic length itself, but at least the uint256 containing the
|
||||
// length must still be present.
|
||||
return 32;
|
||||
bigint size = unlimitedStaticCalldataSize(false);
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit unsigned.");
|
||||
return unsigned(size);
|
||||
}
|
||||
@ -1832,10 +1843,11 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const
|
||||
return result;
|
||||
}
|
||||
|
||||
u256 ArrayType::memorySize() const
|
||||
u256 ArrayType::memoryDataSize() const
|
||||
{
|
||||
solAssert(!isDynamicallySized(), "");
|
||||
solAssert(m_location == DataLocation::Memory, "");
|
||||
solAssert(!isByteArray(), "");
|
||||
bigint size = bigint(m_length) * m_baseType->memoryHeadSize();
|
||||
solAssert(size <= numeric_limits<unsigned>::max(), "Array size does not fit u256.");
|
||||
return u256(size);
|
||||
@ -1992,20 +2004,33 @@ bool StructType::operator==(Type const& _other) const
|
||||
return ReferenceType::operator==(other) && other.m_struct == m_struct;
|
||||
}
|
||||
|
||||
|
||||
unsigned StructType::calldataEncodedSize(bool) const
|
||||
{
|
||||
solAssert(!isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned size = 0;
|
||||
for (auto const& member: members(nullptr))
|
||||
if (!member.type->canLiveOutsideStorage())
|
||||
return 0;
|
||||
else
|
||||
{
|
||||
// Struct members are always padded.
|
||||
unsigned memberSize = member.type->calldataEncodedSize(true);
|
||||
if (memberSize == 0)
|
||||
return 0;
|
||||
size += memberSize;
|
||||
}
|
||||
{
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
// Struct members are always padded.
|
||||
size += member.type->calldataEncodedSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
unsigned StructType::calldataEncodedTailSize() const
|
||||
{
|
||||
solAssert(isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned size = 0;
|
||||
for (auto const& member: members(nullptr))
|
||||
{
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
// Struct members are always padded.
|
||||
size += member.type->calldataHeadSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@ -2017,12 +2042,8 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const
|
||||
solAssert(member.type->canLiveOutsideStorage(), "");
|
||||
if (member.name == _member)
|
||||
return offset;
|
||||
{
|
||||
// Struct members are always padded.
|
||||
unsigned memberSize = member.type->calldataEncodedSize(true);
|
||||
solAssert(memberSize != 0, "");
|
||||
offset += memberSize;
|
||||
}
|
||||
// Struct members are always padded.
|
||||
offset += member.type->calldataHeadSize();
|
||||
}
|
||||
solAssert(false, "Struct member not found.");
|
||||
}
|
||||
@ -2040,7 +2061,7 @@ bool StructType::isDynamicallyEncoded() const
|
||||
return false;
|
||||
}
|
||||
|
||||
u256 StructType::memorySize() const
|
||||
u256 StructType::memoryDataSize() const
|
||||
{
|
||||
u256 size;
|
||||
for (auto const& t: memoryMemberTypes())
|
||||
@ -2837,6 +2858,8 @@ unsigned FunctionType::sizeOnStack() const
|
||||
case Kind::ArrayPush:
|
||||
case Kind::ArrayPop:
|
||||
case Kind::ByteArrayPush:
|
||||
case Kind::Transfer:
|
||||
case Kind::Send:
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
|
@ -208,16 +208,32 @@ public:
|
||||
virtual bool operator==(Type const& _other) const { return category() == _other.category(); }
|
||||
virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); }
|
||||
|
||||
/// @returns number of bytes used by this type when encoded for CALL. If it is a dynamic type,
|
||||
/// returns the size of the pointer (usually 32). Returns 0 if the type cannot be encoded
|
||||
/// in calldata.
|
||||
/// @returns number of bytes used by this type when encoded for CALL. Cannot be used for
|
||||
/// dynamically encoded types.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata
|
||||
/// (or is dynamically encoded).
|
||||
/// If @a _padded then it is assumed that each element is padded to a multiple of 32 bytes.
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; return 0; }
|
||||
virtual unsigned calldataEncodedSize(bool _padded) const { (void)_padded; solAssert(false, ""); }
|
||||
/// Convenience version of @see calldataEncodedSize(bool)
|
||||
unsigned calldataEncodedSize() const { return calldataEncodedSize(true); }
|
||||
/// @returns the distance between two elements of this type in a calldata array, tuple or struct.
|
||||
/// For statically encoded types this is the same as calldataEncodedSize(true).
|
||||
/// For dynamically encoded types this is the distance between two tail pointers, i.e. 32.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata.
|
||||
unsigned calldataHeadSize() const { return isDynamicallyEncoded() ? 32 : calldataEncodedSize(true); }
|
||||
/// @returns the (minimal) size of the calldata tail for this type. Can only be used for
|
||||
/// dynamically encoded types. For dynamically-sized arrays this is 32 (the size of the length),
|
||||
/// for statically-sized, but dynamically encoded arrays this is 32*length(), for structs
|
||||
/// this is the sum of the calldataHeadSize's of its members.
|
||||
/// Always returns a value greater than zero and throws if the type cannot be encoded in calldata
|
||||
/// (or is not dynamically encoded).
|
||||
virtual unsigned calldataEncodedTailSize() const { solAssert(false, ""); }
|
||||
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
|
||||
/// types, this is the size of the memory pointer.
|
||||
virtual unsigned memoryHeadSize() const { return calldataEncodedSize(); }
|
||||
/// Convenience version of @see calldataEncodedSize(bool)
|
||||
unsigned calldataEncodedSize() const { return calldataEncodedSize(true); }
|
||||
/// @returns the size of this data type in bytes when stored in memory. For memory-reference
|
||||
/// types, this is the size of the actual data area, if it is statically-sized.
|
||||
virtual u256 memoryDataSize() const { return calldataEncodedSize(); }
|
||||
/// @returns true if the type is a dynamic array
|
||||
virtual bool isDynamicallySized() const { return false; }
|
||||
/// @returns true if the type is dynamically encoded in the ABI
|
||||
@ -634,6 +650,10 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
unsigned memoryHeadSize() const override { return 32; }
|
||||
u256 memoryDataSize() const override = 0;
|
||||
|
||||
unsigned calldataEncodedSize(bool) const override = 0;
|
||||
unsigned calldataEncodedTailSize() const override = 0;
|
||||
|
||||
/// @returns a copy of this type with location (recursively) changed to @a _location,
|
||||
/// whereas isPointer is only shallowly changed - the deep copy is always a bound reference.
|
||||
@ -701,7 +721,8 @@ public:
|
||||
BoolResult isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
unsigned calldataEncodedSize(bool) const override;
|
||||
unsigned calldataEncodedTailSize() const override;
|
||||
bool isDynamicallySized() const override { return m_hasDynamicLength; }
|
||||
bool isDynamicallyEncoded() const override;
|
||||
u256 storageSize() const override;
|
||||
@ -724,12 +745,12 @@ public:
|
||||
bool isString() const { return m_arrayKind == ArrayKind::String; }
|
||||
Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; }
|
||||
u256 const& length() const { return m_length; }
|
||||
u256 memorySize() const;
|
||||
u256 memoryDataSize() const override;
|
||||
|
||||
std::unique_ptr<ReferenceType> copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||
|
||||
/// The offset to advance in calldata to move from one array element to the next.
|
||||
unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataEncodedSize(); }
|
||||
unsigned calldataStride() const { return isByteArray() ? 1 : m_baseType->calldataHeadSize(); }
|
||||
/// The offset to advance in memory to move from one array element to the next.
|
||||
unsigned memoryStride() const { return isByteArray() ? 1 : m_baseType->memoryHeadSize(); }
|
||||
/// The offset to advance in storage to move from one array element to the next.
|
||||
@ -741,7 +762,7 @@ private:
|
||||
/// String is interpreted as a subtype of Bytes.
|
||||
enum class ArrayKind { Ordinary, Bytes, String };
|
||||
|
||||
bigint unlimitedCalldataEncodedSize(bool _padded) const;
|
||||
bigint unlimitedStaticCalldataSize(bool _padded) const;
|
||||
|
||||
///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays.
|
||||
ArrayKind m_arrayKind = ArrayKind::Ordinary;
|
||||
@ -829,9 +850,10 @@ public:
|
||||
BoolResult isImplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||
std::string richIdentifier() const override;
|
||||
bool operator==(Type const& _other) const override;
|
||||
unsigned calldataEncodedSize(bool _padded) const override;
|
||||
unsigned calldataEncodedSize(bool) const override;
|
||||
unsigned calldataEncodedTailSize() const override;
|
||||
bool isDynamicallyEncoded() const override;
|
||||
u256 memorySize() const;
|
||||
u256 memoryDataSize() const override;
|
||||
u256 storageSize() const override;
|
||||
bool canLiveOutsideStorage() const override { return true; }
|
||||
std::string toString(bool _short) const override;
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
@ -82,16 +83,16 @@ string ABIFunctions::tupleEncoder(
|
||||
<abiEncode>(<values> add(headStart, <pos>))
|
||||
)")
|
||||
);
|
||||
string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options));
|
||||
encodeElements += elementTempl.render();
|
||||
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
||||
headPos += _targetTypes[i]->calldataHeadSize();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
solAssert(headPos == headSize_, "");
|
||||
string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0);
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
@ -147,7 +148,7 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
pos := add(pos, <calldataEncodedSize>)
|
||||
)")
|
||||
);
|
||||
string values = m_utils.suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack);
|
||||
elementTempl("values", values.empty() ? "" : values + ", ");
|
||||
if (!dynamic)
|
||||
elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false)));
|
||||
@ -155,7 +156,7 @@ string ABIFunctions::tupleEncoderPacked(
|
||||
encodeElements += elementTempl.render();
|
||||
stackPos += sizeOnStack;
|
||||
}
|
||||
string valueParams = m_utils.suffixedVariableNameList("value", stackPos, 0);
|
||||
string valueParams = suffixedVariableNameList("value", stackPos, 0);
|
||||
templ("valueParams", valueParams.empty() ? "" : ", " + valueParams);
|
||||
templ("encodeElements", encodeElements);
|
||||
|
||||
@ -224,7 +225,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
elementTempl("pos", to_string(headPos));
|
||||
elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
|
||||
decodeElements += elementTempl.render();
|
||||
headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
|
||||
headPos += decodingTypes[i]->calldataHeadSize();
|
||||
}
|
||||
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
|
||||
templ("arrow", valueReturnParams.empty() ? "" : "->");
|
||||
@ -367,7 +368,7 @@ string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction(
|
||||
_targetType.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
return createFunction(functionName, [&]() {
|
||||
string values = m_utils.suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
|
||||
string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options));
|
||||
string encoder = abiEncodingFunction(_givenType, _targetType, _options);
|
||||
if (_targetType.isDynamicallyEncoded())
|
||||
return Whiskers(R"(
|
||||
@ -508,7 +509,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
string elementValues = m_utils.suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
|
||||
string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions));
|
||||
Whiskers templ(
|
||||
usesTail ?
|
||||
R"(
|
||||
@ -690,6 +691,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
// Multiple items per slot
|
||||
solAssert(_from.baseType()->storageBytes() <= 16, "");
|
||||
solAssert(!_from.baseType()->isDynamicallyEncoded(), "");
|
||||
solAssert(!_to.baseType()->isDynamicallyEncoded(), "");
|
||||
solAssert(_from.baseType()->isValueType(), "");
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
size_t storageBytes = _from.baseType()->storageBytes();
|
||||
@ -714,7 +716,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
let data := sload(srcPtr)
|
||||
<#items>
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
pos := add(pos, <stride>)
|
||||
</items>
|
||||
srcPtr := add(srcPtr, 1)
|
||||
}
|
||||
@ -725,7 +727,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
<#items>
|
||||
if <inRange> {
|
||||
<encodeToMemoryFun>(<extractFromSlot>(data), pos)
|
||||
pos := add(pos, <elementEncodedSize>)
|
||||
pos := add(pos, <stride>)
|
||||
itemCounter := add(itemCounter, 1)
|
||||
}
|
||||
</items>
|
||||
@ -752,9 +754,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
else
|
||||
templ("useSpill", "0");
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
// We use padded size because array elements are always padded.
|
||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||
templ("elementEncodedSize", elementEncodedSize);
|
||||
templ("stride", toCompactHexWithPrefix(_to.calldataStride()));
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
@ -892,7 +892,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
// Like with arrays, struct members are always padded.
|
||||
subOptions.padded = true;
|
||||
|
||||
string memberValues = m_utils.suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions));
|
||||
string memberValues = suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions));
|
||||
members.back()["memberValues"] = memberValues;
|
||||
|
||||
string encode;
|
||||
@ -913,7 +913,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
);
|
||||
encodeTempl("memberValues", memberValues);
|
||||
encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||
encodingOffset += memberTypeTo->calldataHeadSize();
|
||||
encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions));
|
||||
encode = encodeTempl.render();
|
||||
}
|
||||
@ -1079,8 +1079,8 @@ string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromM
|
||||
solAssert(decodingType, "");
|
||||
solAssert(decodingType->sizeOnStack() == 1, "");
|
||||
solAssert(decodingType->isValueType(), "");
|
||||
solAssert(decodingType->calldataEncodedSize() == 32, "");
|
||||
solAssert(!decodingType->isDynamicallyEncoded(), "");
|
||||
solAssert(decodingType->calldataEncodedSize() == 32, "");
|
||||
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
@ -1134,7 +1134,7 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
let elementPos := <retrieveElementPos>
|
||||
mstore(dst, <decodingFun>(elementPos, end))
|
||||
dst := add(dst, 0x20)
|
||||
src := add(src, <baseEncodedSize>)
|
||||
src := add(src, <stride>)
|
||||
}
|
||||
}
|
||||
)"
|
||||
@ -1144,6 +1144,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
string calldataStride = toCompactHexWithPrefix(_type.calldataStride());
|
||||
templ("stride", calldataStride);
|
||||
if (_type.isDynamicallySized())
|
||||
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
|
||||
else
|
||||
@ -1152,14 +1154,11 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
{
|
||||
templ("staticBoundsCheck", "");
|
||||
templ("retrieveElementPos", "add(offset, " + load + "(src))");
|
||||
templ("baseEncodedSize", "0x20");
|
||||
}
|
||||
else
|
||||
{
|
||||
string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
|
||||
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + baseEncodedSize + ")), end) { revert(0, 0) }");
|
||||
templ("staticBoundsCheck", "if gt(add(src, mul(length, " + calldataStride + ")), end) { revert(0, 0) }");
|
||||
templ("retrieveElementPos", "src");
|
||||
templ("baseEncodedSize", baseEncodedSize);
|
||||
}
|
||||
templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
|
||||
return templ.render();
|
||||
@ -1171,8 +1170,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
if (!_type.isDynamicallySized())
|
||||
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() > 0, "");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
||||
solAssert(_type.calldataStride() > 0, "");
|
||||
solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), "");
|
||||
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
@ -1187,7 +1186,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
length := calldataload(offset)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
arrayPos := add(offset, 0x20)
|
||||
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(length, <stride>)), end) { revert(0, 0) }
|
||||
}
|
||||
)";
|
||||
else
|
||||
@ -1195,13 +1194,13 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
// <readableTypeName>
|
||||
function <functionName>(offset, end) -> arrayPos {
|
||||
arrayPos := offset
|
||||
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(<length>, <stride>)), end) { revert(0, 0) }
|
||||
}
|
||||
)";
|
||||
Whiskers w{templ};
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(true));
|
||||
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
||||
w("stride", toCompactHexWithPrefix(_type.calldataStride()));
|
||||
if (!_type.isDynamicallySized())
|
||||
w("length", toCompactHexWithPrefix(_type.length()));
|
||||
return w.render();
|
||||
@ -1245,7 +1244,6 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
||||
string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(_type.calldataEncodedSize(true) != 0, "");
|
||||
string functionName =
|
||||
"abi_decode_" +
|
||||
_type.identifier();
|
||||
@ -1260,7 +1258,7 @@ string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type)
|
||||
)"};
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(true));
|
||||
w("minimumSize", to_string(_type.calldataEncodedSize(true)));
|
||||
w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true)));
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
@ -1290,8 +1288,8 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
|
||||
solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize()));
|
||||
size_t headPos = 0;
|
||||
vector<map<string, string>> members;
|
||||
for (auto const& member: _type.members(nullptr))
|
||||
@ -1321,7 +1319,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
members.push_back({});
|
||||
members.back()["decode"] = memberTempl.render();
|
||||
members.back()["memberName"] = member.name;
|
||||
headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
|
||||
headPos += decodingType->calldataHeadSize();
|
||||
}
|
||||
templ("members", members);
|
||||
templ("minimumSize", toCompactHexWithPrefix(headPos));
|
||||
@ -1375,8 +1373,8 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.isDynamicallyEncoded())
|
||||
{
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
||||
solAssert(tailSize > 1, "");
|
||||
Whiskers w(R"(
|
||||
function <functionName>(base_ref, ptr) -> <return> {
|
||||
let rel_offset_of_tail := calldataload(ptr)
|
||||
@ -1389,13 +1387,12 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
{
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
solAssert(!!arrayType, "");
|
||||
unsigned int calldataStride = arrayType->calldataStride();
|
||||
w("handleLength", Whiskers(R"(
|
||||
length := calldataload(value)
|
||||
value := add(value, 0x20)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
)")("calldataStride", toCompactHexWithPrefix(calldataStride)).render());
|
||||
)")("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())).render());
|
||||
w("return", "value, length");
|
||||
}
|
||||
else
|
||||
@ -1403,7 +1400,7 @@ string ABIFunctions::calldataAccessFunction(Type const& _type)
|
||||
w("handleLength", "");
|
||||
w("return", "value");
|
||||
}
|
||||
w("neededLength", toCompactHexWithPrefix(baseEncodedSize));
|
||||
w("neededLength", toCompactHexWithPrefix(tailSize));
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
@ -1482,12 +1479,7 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes)
|
||||
{
|
||||
size_t headSize = 0;
|
||||
for (auto const& t: _targetTypes)
|
||||
{
|
||||
if (t->isDynamicallyEncoded())
|
||||
headSize += 0x20;
|
||||
else
|
||||
headSize += t->calldataEncodedSize();
|
||||
}
|
||||
headSize += t->calldataHeadSize();
|
||||
|
||||
return headSize;
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
else if (_sourceType.location() == DataLocation::Memory)
|
||||
_context << sourceBaseType->memoryHeadSize();
|
||||
else
|
||||
_context << sourceBaseType->calldataEncodedSize(true);
|
||||
_context << sourceBaseType->calldataHeadSize();
|
||||
_context
|
||||
<< Instruction::ADD
|
||||
<< swapInstruction(2 + byteOffsetSize);
|
||||
@ -294,20 +294,13 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
"Nested dynamic arrays not implemented here."
|
||||
);
|
||||
CompilerUtils utils(m_context);
|
||||
unsigned baseSize = 1;
|
||||
if (!_sourceType.isByteArray())
|
||||
{
|
||||
// We always pad the elements, regardless of _padToWordBoundaries.
|
||||
baseSize = _sourceType.baseType()->calldataEncodedSize();
|
||||
solAssert(baseSize >= 0x20, "");
|
||||
}
|
||||
|
||||
if (_sourceType.location() == DataLocation::CallData)
|
||||
{
|
||||
if (!_sourceType.isDynamicallySized())
|
||||
m_context << _sourceType.length();
|
||||
if (baseSize > 1)
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
if (!_sourceType.isByteArray())
|
||||
convertLengthToSize(_sourceType);
|
||||
|
||||
string routine = "calldatacopy(target, source, len)\n";
|
||||
if (_padToWordBoundaries)
|
||||
@ -358,15 +351,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD;
|
||||
m_context << Instruction::SWAP1;
|
||||
}
|
||||
// convert length to size
|
||||
if (baseSize > 1)
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
if (!_sourceType.isByteArray())
|
||||
convertLengthToSize(_sourceType);
|
||||
// stack: <target> <source> <size>
|
||||
m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4;
|
||||
// We can resort to copying full 32 bytes only if
|
||||
// - the length is known to be a multiple of 32 or
|
||||
// - we will pad to full 32 bytes later anyway.
|
||||
if (((baseSize % 32) == 0) || _padToWordBoundaries)
|
||||
if (!_sourceType.isByteArray() || _padToWordBoundaries)
|
||||
utils.memoryCopy32();
|
||||
else
|
||||
utils.memoryCopy();
|
||||
@ -374,11 +366,8 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
// stack: <target> <size>
|
||||
|
||||
bool paddingNeeded = false;
|
||||
if (_sourceType.isDynamicallySized())
|
||||
paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0);
|
||||
else
|
||||
paddingNeeded = _padToWordBoundaries && (((_sourceType.length() * baseSize) % 32) != 0);
|
||||
bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArray();
|
||||
|
||||
if (paddingNeeded)
|
||||
{
|
||||
// stack: <target> <size>
|
||||
@ -455,10 +444,10 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
m_context.appendJumpTo(loopEnd);
|
||||
m_context << longByteArray;
|
||||
}
|
||||
// compute memory end offset
|
||||
if (baseSize > 1)
|
||||
else
|
||||
// convert length to memory size
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL;
|
||||
|
||||
m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2;
|
||||
if (_sourceType.isDynamicallySized())
|
||||
{
|
||||
@ -515,7 +504,12 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
// stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset
|
||||
if (haveByteOffset)
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
if (_padToWordBoundaries && baseSize % 32 != 0)
|
||||
if (!_sourceType.isByteArray())
|
||||
{
|
||||
solAssert(_sourceType.calldataStride() % 32 == 0, "");
|
||||
solAssert(_sourceType.memoryStride() % 32 == 0, "");
|
||||
}
|
||||
if (_padToWordBoundaries && _sourceType.isByteArray())
|
||||
{
|
||||
// memory_end_offset - start is the actual length (we want to compute the ceil of).
|
||||
// memory_offset - start is its next multiple of 32, but it might be off by 32.
|
||||
@ -987,9 +981,9 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con
|
||||
if (!_arrayType.isByteArray())
|
||||
{
|
||||
if (_arrayType.location() == DataLocation::Memory)
|
||||
m_context << _arrayType.baseType()->memoryHeadSize();
|
||||
m_context << _arrayType.memoryStride();
|
||||
else
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
m_context << _arrayType.calldataStride();
|
||||
m_context << Instruction::MUL;
|
||||
}
|
||||
else if (_pad)
|
||||
@ -1067,10 +1061,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, b
|
||||
case DataLocation::CallData:
|
||||
if (!_arrayType.isByteArray())
|
||||
{
|
||||
if (_arrayType.baseType()->isDynamicallyEncoded())
|
||||
m_context << u256(0x20);
|
||||
else
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
m_context << _arrayType.calldataStride();
|
||||
m_context << Instruction::MUL;
|
||||
}
|
||||
// stack: <base_ref> <index * size>
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/backends/evm/EVMMetrics.h>
|
||||
#include <libyul/optimiser/Suite.h>
|
||||
#include <libyul/Object.h>
|
||||
#include <libyul/YulString.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
@ -423,23 +424,19 @@ void CompilerContext::appendInlineAssembly(
|
||||
{
|
||||
bool const isCreation = m_runtimeContext != nullptr;
|
||||
yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment);
|
||||
yul::Object obj;
|
||||
obj.code = parserResult;
|
||||
obj.analysisInfo = make_shared<yul::AsmAnalysisInfo>(analysisInfo);
|
||||
yul::OptimiserSuite::run(
|
||||
dialect,
|
||||
&meter,
|
||||
*parserResult,
|
||||
analysisInfo,
|
||||
obj,
|
||||
_optimiserSettings.optimizeStackAllocation,
|
||||
externallyUsedIdentifiers
|
||||
);
|
||||
analysisInfo = yul::AsmAnalysisInfo{};
|
||||
if (!yul::AsmAnalyzer(
|
||||
analysisInfo,
|
||||
errorReporter,
|
||||
boost::none,
|
||||
dialect,
|
||||
identifierAccess.resolve
|
||||
).analyze(*parserResult))
|
||||
reportError("Optimizer introduced error into inline assembly.");
|
||||
analysisInfo = std::move(*obj.analysisInfo);
|
||||
parserResult = std::move(obj.code);
|
||||
|
||||
#ifdef SOL_OUTPUT_ASM
|
||||
cout << "After optimizer: " << endl;
|
||||
cout << yul::AsmPrinter()(*parserResult) << endl;
|
||||
|
@ -98,16 +98,17 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
|
||||
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
solAssert(_type.isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
unsigned int tailSize = _type.calldataEncodedTailSize();
|
||||
solAssert(tailSize > 1, "");
|
||||
|
||||
// returns the absolute offset of the tail in "base_ref"
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||
base_ref := add(base_ref, rel_offset_of_tail)
|
||||
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
})")("neededLength", toCompactHexWithPrefix(tailSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
||||
|
||||
if (!_type.isDynamicallySized())
|
||||
@ -170,7 +171,7 @@ void CompilerUtils::loadFromMemoryDynamic(
|
||||
solAssert(!_fromCalldata, "");
|
||||
solAssert(_padToWordBoundaries, "");
|
||||
if (_keepUpdatedMemoryOffset)
|
||||
m_context << arrayType->memorySize() << Instruction::ADD;
|
||||
m_context << arrayType->memoryDataSize() << Instruction::ADD;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -251,7 +252,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
//@todo this does not yet support nested dynamic arrays
|
||||
size_t encodedSize = 0;
|
||||
for (auto const& t: _typeParameters)
|
||||
encodedSize += t->decodingType()->calldataEncodedSize(true);
|
||||
encodedSize += t->decodingType()->calldataHeadSize();
|
||||
m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
|
||||
|
||||
m_context << Instruction::DUP2 << Instruction::ADD;
|
||||
@ -282,6 +283,15 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
{
|
||||
// compute data pointer
|
||||
m_context << Instruction::DUP1 << Instruction::MLOAD;
|
||||
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset
|
||||
|
||||
fetchFreeMemoryPointer();
|
||||
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset dstmem
|
||||
moveIntoStack(4);
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset
|
||||
m_context << Instruction::DUP5;
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset dstmem
|
||||
|
||||
// Check that the data pointer is valid and that length times
|
||||
// item size is still inside the range.
|
||||
Whiskers templ(R"({
|
||||
@ -294,11 +304,17 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
gt(array_length, 0x100000000),
|
||||
gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
|
||||
) { revert(0, 0) }
|
||||
mstore(dst, array_length)
|
||||
dst := add(dst, 0x20)
|
||||
})");
|
||||
templ("item_size", to_string(arrayType.calldataStride()));
|
||||
m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
|
||||
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k)
|
||||
moveIntoStack(3);
|
||||
m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"});
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset dstdata data_ptr
|
||||
ArrayUtils(m_context).copyArrayToMemory(arrayType, true);
|
||||
// stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset mem_end
|
||||
storeFreeMemoryPointer();
|
||||
m_context << u256(0x20) << Instruction::ADD;
|
||||
}
|
||||
else
|
||||
@ -306,7 +322,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
// Size has already been checked for this one.
|
||||
moveIntoStack(2);
|
||||
m_context << Instruction::DUP3;
|
||||
m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
|
||||
m_context << u256(arrayType.calldataHeadSize()) << Instruction::ADD;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -343,7 +359,7 @@ void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMem
|
||||
// size has already been checked
|
||||
// stack: input_end base_offset data_offset
|
||||
m_context << Instruction::DUP1;
|
||||
m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
|
||||
m_context << u256(calldataType->calldataHeadSize()) << Instruction::ADD;
|
||||
}
|
||||
if (arrayType.location() == DataLocation::Memory)
|
||||
{
|
||||
@ -573,11 +589,6 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Potential optimization:
|
||||
// When we create a new multi-dimensional dynamic array, each element
|
||||
// is initialized to an empty array. It actually does not hurt
|
||||
// to re-use exactly the same empty array for all elements. Currently,
|
||||
// a new one is created each time.
|
||||
auto repeat = m_context.newTag();
|
||||
m_context << repeat;
|
||||
pushZeroValue(*_type.baseType());
|
||||
@ -999,7 +1010,7 @@ void CompilerUtils::convertType(
|
||||
{
|
||||
CompilerUtils utils(_context);
|
||||
// stack: <source ref>
|
||||
utils.allocateMemory(typeOnStack->memorySize());
|
||||
utils.allocateMemory(typeOnStack->memoryDataSize());
|
||||
_context << Instruction::SWAP1 << Instruction::DUP2;
|
||||
// stack: <memory ptr> <source ref> <memory ptr>
|
||||
for (auto const& member: typeOnStack->members(nullptr))
|
||||
@ -1182,7 +1193,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
|
||||
1,
|
||||
[type](CompilerContext& _context) {
|
||||
CompilerUtils utils(_context);
|
||||
utils.allocateMemory(max(32u, type->calldataEncodedSize()));
|
||||
|
||||
utils.allocateMemory(max<u256>(32u, type->memoryDataSize()));
|
||||
_context << Instruction::DUP1;
|
||||
|
||||
if (auto structType = dynamic_cast<StructType const*>(type))
|
||||
@ -1349,6 +1361,8 @@ void CompilerUtils::storeStringData(bytesConstRef _data)
|
||||
|
||||
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords)
|
||||
{
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
|
||||
bool isExternalFunctionType = false;
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
@ -1421,6 +1435,8 @@ unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)
|
||||
"Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")."
|
||||
);
|
||||
|
||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||
|
||||
unsigned numBytes = _type.calldataEncodedSize(_padToWords);
|
||||
|
||||
solAssert(
|
||||
|
@ -629,8 +629,46 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
|
||||
}
|
||||
else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl))
|
||||
{
|
||||
solAssert(!variable->isConstant(), "");
|
||||
if (m_context.isStateVariable(decl))
|
||||
if (variable->isConstant())
|
||||
{
|
||||
u256 value;
|
||||
if (variable->value()->annotation().type->category() == Type::Category::RationalNumber)
|
||||
{
|
||||
value = dynamic_cast<RationalNumberType const&>(*variable->value()->annotation().type).literalValue(nullptr);
|
||||
if (FixedBytesType const* bytesType = dynamic_cast<FixedBytesType const*>(variable->type()))
|
||||
value = value << (256 - 8 * bytesType->numBytes());
|
||||
else
|
||||
solAssert(variable->type()->category() == Type::Category::Integer, "");
|
||||
}
|
||||
else if (Literal const* literal = dynamic_cast<Literal const*>(variable->value().get()))
|
||||
{
|
||||
TypePointer type = literal->annotation().type;
|
||||
|
||||
switch (type->category())
|
||||
{
|
||||
case Type::Category::Bool:
|
||||
case Type::Category::Address:
|
||||
solAssert(*type == *variable->annotation().type, "");
|
||||
value = type->literalValue(literal);
|
||||
break;
|
||||
case Type::Category::StringLiteral:
|
||||
{
|
||||
StringLiteralType const& stringLiteral = dynamic_cast<StringLiteralType const&>(*type);
|
||||
solAssert(variable->type()->category() == Type::Category::FixedBytes, "");
|
||||
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(*variable->type()).numBytes();
|
||||
solAssert(stringLiteral.value().size() <= numBytes, "");
|
||||
value = u256(h256(stringLiteral.value(), h256::AlignLeft));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
}
|
||||
else
|
||||
solAssert(false, "Invalid constant in inline assembly.");
|
||||
m_context << value;
|
||||
}
|
||||
else if (m_context.isStateVariable(decl))
|
||||
{
|
||||
auto const& location = m_context.storageLocationOfVariable(*decl);
|
||||
if (ref->second.isSlot)
|
||||
|
@ -317,7 +317,7 @@ bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
||||
|
||||
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
|
||||
utils().allocateMemory(max(u256(32u), arrayType.memorySize()));
|
||||
utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize()));
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
for (auto const& component: _tuple.components())
|
||||
@ -526,7 +526,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
TypeType const& type = dynamic_cast<TypeType const&>(*_functionCall.expression().annotation().type);
|
||||
auto const& structType = dynamic_cast<StructType const&>(*type.actualType());
|
||||
|
||||
utils().allocateMemory(max(u256(32u), structType.memorySize()));
|
||||
utils().allocateMemory(max(u256(32u), structType.memoryDataSize()));
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
for (unsigned i = 0; i < arguments.size(); ++i)
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
@ -556,7 +557,6 @@ std::string YulUtilFunctions::resizeDynamicArrayFunction(ArrayType const& _type)
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
solAssert(_type.isDynamicallySized(), "");
|
||||
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
|
||||
solUnimplementedAssert(_type.baseType()->isValueType(), "...");
|
||||
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "...");
|
||||
solUnimplementedAssert(_type.baseType()->storageSize() == 1, "");
|
||||
|
||||
@ -597,18 +597,62 @@ string YulUtilFunctions::clearStorageRangeFunction(Type const& _type)
|
||||
{
|
||||
string functionName = "clear_storage_range_" + _type.identifier();
|
||||
|
||||
solUnimplementedAssert(_type.isValueType(), "...");
|
||||
solAssert(_type.storageBytes() >= 32, "Expected smaller value for storage bytes");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(start, end) {
|
||||
for {} lt(start, end) { start := add(start, 1) }
|
||||
for {} lt(start, end) { start := add(start, <increment>) }
|
||||
{
|
||||
sstore(start, 0)
|
||||
<setToZero>(start, 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("setToZero", storageSetToZeroFunction(_type))
|
||||
("increment", _type.storageSize().str())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::clearStorageArrayFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.location() == DataLocation::Storage, "");
|
||||
|
||||
if (_type.baseType()->storageBytes() < 32)
|
||||
{
|
||||
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
|
||||
}
|
||||
|
||||
if (_type.baseType()->isValueType())
|
||||
solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
|
||||
|
||||
string functionName = "clear_storage_array_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) {
|
||||
<?dynamic>
|
||||
<resizeArray>(slot, 0)
|
||||
<!dynamic>
|
||||
<clearRange>(slot, add(slot, <lenToSize>(<len>)))
|
||||
</dynamic>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("dynamic", _type.isDynamicallySized())
|
||||
("resizeArray", _type.isDynamicallySized() ? resizeDynamicArrayFunction(_type) : "")
|
||||
(
|
||||
"clearRange",
|
||||
clearStorageRangeFunction(
|
||||
(_type.baseType()->storageBytes() < 32) ?
|
||||
*TypeProvider::uint256() :
|
||||
*_type.baseType()
|
||||
)
|
||||
)
|
||||
("lenToSize", arrayConvertLengthToSize(_type))
|
||||
("len", _type.length().str())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
@ -651,11 +695,11 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
|
||||
<?byteArray>
|
||||
size := length
|
||||
<!byteArray>
|
||||
size := <mul>(length, <elementSize>)
|
||||
size := <mul>(length, <stride>)
|
||||
</byteArray>
|
||||
})")
|
||||
("functionName", functionName)
|
||||
("elementSize", _type.location() == DataLocation::Memory ? baseType.memoryHeadSize() : baseType.calldataEncodedSize())
|
||||
("stride", to_string(_type.location() == DataLocation::Memory ? _type.memoryStride() : _type.calldataStride()))
|
||||
("byteArray", _type.isByteArray())
|
||||
("mul", overflowCheckedIntMulFunction(*TypeProvider::uint256()))
|
||||
.render();
|
||||
@ -754,6 +798,36 @@ string YulUtilFunctions::storageArrayIndexAccessFunction(ArrayType const& _type)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::memoryArrayIndexAccessFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "memory_array_index_access_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(baseRef, index) -> addr {
|
||||
if iszero(lt(index, <arrayLen>(baseRef))) {
|
||||
invalid()
|
||||
}
|
||||
|
||||
let offset := mul(index, <stride>)
|
||||
<?dynamicallySized>
|
||||
offset := add(offset, 32)
|
||||
</dynamicallySized>
|
||||
addr := add(baseRef, offset)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("arrayLen", arrayLengthFunction(_type))
|
||||
("stride", to_string(_type.memoryStride()))
|
||||
("dynamicallySized", _type.isDynamicallySized())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::calldataArrayIndexAccessFunction(ArrayType const& /*_type*/)
|
||||
{
|
||||
solUnimplemented("Calldata arrays not yet implemented!");
|
||||
}
|
||||
|
||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
@ -781,10 +855,7 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
u256 size =
|
||||
_type.baseType()->isDynamicallyEncoded() ?
|
||||
32 :
|
||||
_type.baseType()->calldataEncodedSize();
|
||||
u256 size = _type.calldataStride();
|
||||
solAssert(size >= 32 && size % 32 == 0, "");
|
||||
templ("advance", toCompactHexWithPrefix(size));
|
||||
break;
|
||||
@ -881,7 +952,17 @@ string YulUtilFunctions::readFromStorageDynamic(Type const& _type, bool _splitFu
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset)
|
||||
string YulUtilFunctions::readFromMemory(Type const& _type)
|
||||
{
|
||||
return readFromMemoryOrCalldata(_type, false);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromCalldata(Type const& _type)
|
||||
{
|
||||
return readFromMemoryOrCalldata(_type, true);
|
||||
}
|
||||
|
||||
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const& _offset)
|
||||
{
|
||||
string const functionName =
|
||||
"update_storage_value_" +
|
||||
@ -922,6 +1003,64 @@ string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::op
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
|
||||
{
|
||||
string const functionName =
|
||||
string("write_to_memory_") +
|
||||
_type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
solAssert(!dynamic_cast<StringLiteralType const*>(&_type), "");
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(
|
||||
ref->location() == DataLocation::Memory,
|
||||
"Can only update types with location memory."
|
||||
);
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, value) {
|
||||
mstore(memPtr, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (
|
||||
_type.category() == Type::Category::Function &&
|
||||
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
|
||||
)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, addr, selector) {
|
||||
mstore(memPtr, <combine>(addr, selector))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("combine", combineExternalFunctionIdFunction())
|
||||
.render();
|
||||
}
|
||||
else if (_type.isValueType())
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr, value) {
|
||||
mstore(memPtr, <cleanup>(value))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("cleanup", cleanupFunction(_type))
|
||||
.render();
|
||||
}
|
||||
else // Should never happen
|
||||
{
|
||||
solAssert(
|
||||
false,
|
||||
"Memory store of type " + _type.toString(true) + " not allowed."
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::extractFromStorageValueDynamic(Type const& _type, bool _splitFunctionTypes)
|
||||
{
|
||||
solUnimplementedAssert(!_splitFunctionTypes, "");
|
||||
@ -1038,6 +1177,28 @@ string YulUtilFunctions::allocationFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
|
||||
{
|
||||
solUnimplementedAssert(!_type.isByteArray(), "");
|
||||
|
||||
string functionName = "allocate_memory_array_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(length) -> memPtr {
|
||||
memPtr := <alloc>(<allocSize>(length))
|
||||
<?dynamic>
|
||||
mstore(memPtr, length)
|
||||
</dynamic>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("alloc", allocationFunction())
|
||||
("allocSize", arrayAllocationSizeFunction(_type))
|
||||
("dynamic", _type.isDynamicallySized())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
{
|
||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||
@ -1146,8 +1307,25 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
{
|
||||
bool equal = _from == _to;
|
||||
|
||||
if (!equal)
|
||||
{
|
||||
ArrayType const& from = dynamic_cast<decltype(from)>(_from);
|
||||
ArrayType const& to = dynamic_cast<decltype(to)>(_to);
|
||||
|
||||
if (*from.mobileType() == *to.mobileType())
|
||||
equal = true;
|
||||
}
|
||||
|
||||
if (equal)
|
||||
body = "converted := value";
|
||||
else
|
||||
solUnimplementedAssert(false, "Array conversion not implemented.");
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::Struct:
|
||||
solUnimplementedAssert(false, "Struct conversion not implemented.");
|
||||
break;
|
||||
@ -1399,24 +1577,6 @@ string YulUtilFunctions::forwardingRevertFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
|
||||
{
|
||||
string result;
|
||||
if (_startSuffix < _endSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_startSuffix++);
|
||||
while (_startSuffix < _endSuffix)
|
||||
result += ", " + _baseName + to_string(_startSuffix++);
|
||||
}
|
||||
else if (_endSuffix < _startSuffix)
|
||||
{
|
||||
result = _baseName + to_string(_endSuffix++);
|
||||
while (_endSuffix < _startSuffix)
|
||||
result = _baseName + to_string(_endSuffix++) + ", " + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
@ -1514,6 +1674,35 @@ string YulUtilFunctions::zeroValueFunction(Type const& _type)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
|
||||
{
|
||||
string const functionName = "storage_set_to_zero_" + _type.identifier();
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.isValueType())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot, offset) {
|
||||
<store>(slot, offset, <zeroValue>())
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("store", updateStorageValueFunction(_type))
|
||||
("zeroValue", zeroValueFunction(_type))
|
||||
.render();
|
||||
else if (_type.category() == Type::Category::Array)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot, offset) {
|
||||
<clearArray>(slot)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("clearArray", clearStorageArrayFunction(dynamic_cast<ArrayType const&>(_type)))
|
||||
.render();
|
||||
else
|
||||
solUnimplemented("setToZero for type " + _type.identifier() + " not yet implemented!");
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to)
|
||||
{
|
||||
string functionName =
|
||||
@ -1579,3 +1768,60 @@ string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata)
|
||||
{
|
||||
string functionName =
|
||||
string("read_from_") +
|
||||
(_fromCalldata ? "calldata" : "memory") +
|
||||
_type.identifier();
|
||||
|
||||
// TODO use ABI functions for handling calldata
|
||||
if (_fromCalldata)
|
||||
solAssert(!_type.isDynamicallyEncoded(), "");
|
||||
|
||||
return m_functionCollector->createFunction(functionName, [&] {
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(&_type))
|
||||
{
|
||||
solAssert(refType->sizeOnStack() == 1, "");
|
||||
solAssert(!_fromCalldata, "");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> value {
|
||||
value := mload(memPtr)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
if (auto const* funType = dynamic_cast<FunctionType const*>(&_type))
|
||||
if (funType->kind() == FunctionType::Kind::External)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> addr, selector {
|
||||
let combined := <load>(memPtr)
|
||||
addr, selector := <splitFunction>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromCalldata ? "calldataload" : "mload")
|
||||
("splitFunction", splitExternalFunctionIdFunction())
|
||||
.render();
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> value {
|
||||
value := <load>(memPtr)
|
||||
<?needsValidation>
|
||||
value := <validate>(value)
|
||||
</needsValidation>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromCalldata ? "calldataload" : "mload")
|
||||
("needsValidation", _fromCalldata)
|
||||
("validate", _fromCalldata ? validatorFunction(_type) : "")
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
@ -122,10 +122,14 @@ public:
|
||||
std::string resizeDynamicArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that will clear the storage area given
|
||||
/// by the start and end (exclusive) parameters (slots). Only works for value types.
|
||||
/// by the start and end (exclusive) parameters (slots).
|
||||
/// signature: (start, end)
|
||||
std::string clearStorageRangeFunction(Type const& _type);
|
||||
|
||||
/// @returns the name of a function that will clear the given storage array
|
||||
/// signature: (slot) ->
|
||||
std::string clearStorageArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// Returns the name of a function that will convert a given length to the
|
||||
/// size in memory (number of storage slots or calldata/memory bytes) it
|
||||
/// will require.
|
||||
@ -146,6 +150,17 @@ public:
|
||||
/// signature: (array, index) -> slot, offset
|
||||
std::string storageArrayIndexAccessFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that returns the memory address for the
|
||||
/// given array base ref and index.
|
||||
/// Causes invalid opcode on out of range access.
|
||||
/// signature: (baseRef, index) -> address
|
||||
std::string memoryArrayIndexAccessFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that returns the calldata address for the
|
||||
/// given array base ref and index.
|
||||
/// signature: (baseRef, index) -> address
|
||||
std::string calldataArrayIndexAccessFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays, calldata arrays and storage arrays that every item occupies one or multiple full slots.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
@ -162,6 +177,14 @@ public:
|
||||
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
|
||||
std::string readFromStorageDynamic(Type const& _type, bool _splitFunctionTypes);
|
||||
|
||||
/// @returns a function that reads a value type from memory.
|
||||
/// signature: (addr) -> value
|
||||
std::string readFromMemory(Type const& _type);
|
||||
/// @returns a function that reads a value type from calldata.
|
||||
/// Reverts on invalid input.
|
||||
/// signature: (addr) -> value
|
||||
std::string readFromCalldata(Type const& _type);
|
||||
|
||||
/// @returns a function that extracts a value type from storage slot that has been
|
||||
/// retrieved already.
|
||||
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
|
||||
@ -174,7 +197,13 @@ public:
|
||||
/// the specified slot and offset. If offset is not given, it is expected as
|
||||
/// runtime parameter.
|
||||
/// signature: (slot, [offset,] value)
|
||||
std::string updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset = boost::optional<unsigned>());
|
||||
std::string updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const& _offset = boost::optional<unsigned>());
|
||||
|
||||
/// Returns the name of a function that will write the given value to
|
||||
/// the specified address.
|
||||
/// Performs a cleanup before writing for value types.
|
||||
/// signature: (memPtr, value) ->
|
||||
std::string writeToMemoryFunction(Type const& _type);
|
||||
|
||||
/// Performs cleanup after reading from a potentially compressed storage slot.
|
||||
/// The function does not perform any validation, it just masks or sign-extends
|
||||
@ -197,6 +226,11 @@ public:
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// @returns the name of a function that allocates a memory array.
|
||||
/// For dynamic arrays it adds space for length and stores it.
|
||||
/// signature: (length) -> memPtr
|
||||
std::string allocateMemoryArrayFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of the function that converts a value of type @a _from
|
||||
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
|
||||
/// (i.e. "clean"). Asserts on failure.
|
||||
@ -224,13 +258,6 @@ public:
|
||||
/// as reason string.
|
||||
std::string forwardingRevertFunction();
|
||||
|
||||
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
|
||||
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
|
||||
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
|
||||
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
||||
static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
||||
|
||||
|
||||
std::string incrementCheckedFunction(Type const& _type);
|
||||
std::string decrementCheckedFunction(Type const& _type);
|
||||
|
||||
@ -239,11 +266,18 @@ public:
|
||||
/// @returns the name of a function that returns the zero value for the
|
||||
/// provided type
|
||||
std::string zeroValueFunction(Type const& _type);
|
||||
|
||||
/// @returns the name of a function that will set the given storage item to
|
||||
/// zero
|
||||
/// signature: (slot, offset) ->
|
||||
std::string storageSetToZeroFunction(Type const& _type);
|
||||
private:
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
|
||||
|
||||
std::string readFromMemoryOrCalldata(Type const& _type, bool _fromCalldata);
|
||||
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libsolidity/ast/AST.h>
|
||||
|
||||
#include <libdevcore/Whiskers.h>
|
||||
#include <libdevcore/StringUtils.h>
|
||||
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
@ -64,6 +65,11 @@ string IRGenerationContext::functionName(FunctionDefinition const& _function)
|
||||
return "fun_" + _function.name() + "_" + to_string(_function.id());
|
||||
}
|
||||
|
||||
string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
|
||||
}
|
||||
|
||||
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
|
||||
{
|
||||
// @TODO previously, we had to distinguish creation context and runtime context,
|
||||
@ -98,7 +104,7 @@ string IRGenerationContext::variable(Expression const& _expression)
|
||||
if (size == 1)
|
||||
return var;
|
||||
else
|
||||
return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size);
|
||||
return suffixedVariableNameList(move(var) + "_", 1, 1 + size);
|
||||
}
|
||||
|
||||
string IRGenerationContext::variablePart(Expression const& _expression, size_t _part)
|
||||
@ -128,9 +134,9 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
|
||||
templ("functionName", funName);
|
||||
templ("comma", _in > 0 ? "," : "");
|
||||
YulUtilFunctions utils(m_evmVersion, m_functions);
|
||||
templ("in", utils.suffixedVariableNameList("in_", 0, _in));
|
||||
templ("in", suffixedVariableNameList("in_", 0, _in));
|
||||
templ("arrow", _out > 0 ? "->" : "");
|
||||
templ("out", utils.suffixedVariableNameList("out_", 0, _out));
|
||||
templ("out", suffixedVariableNameList("out_", 0, _out));
|
||||
vector<map<string, string>> functions;
|
||||
for (auto const& contract: m_inheritanceHierarchy)
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
|
@ -76,6 +76,7 @@ public:
|
||||
}
|
||||
|
||||
std::string functionName(FunctionDefinition const& _function);
|
||||
std::string functionName(VariableDeclaration const& _varDecl);
|
||||
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
|
||||
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
|
||||
|
||||
|
@ -39,6 +39,9 @@
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -149,21 +152,73 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
string functionName = m_context.functionName(_varDecl);
|
||||
|
||||
Type const* type = _varDecl.annotation().type;
|
||||
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
solAssert(_varDecl.isStateVariable(), "");
|
||||
|
||||
solUnimplementedAssert(type->isValueType(), "");
|
||||
|
||||
return m_context.functionCollector()->createFunction(functionName, [&]() {
|
||||
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>() -> rval {
|
||||
rval := <readStorage>(<slot>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
|
||||
("slot", slot_offset.first.str())
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string IRGenerator::constructorCode(ContractDefinition const& _contract)
|
||||
{
|
||||
// TODO initialize state variables in base to derived order.
|
||||
// TODO base constructors
|
||||
// TODO callValueCheck if there is no constructor.
|
||||
if (FunctionDefinition const* constructor = _contract.constructor())
|
||||
// Initialization of state variables in base-to-derived order.
|
||||
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
|
||||
|
||||
using boost::adaptors::reverse;
|
||||
|
||||
ostringstream out;
|
||||
|
||||
FunctionDefinition const* constructor = _contract.constructor();
|
||||
if (constructor && !constructor->isPayable())
|
||||
out << callValueCheck();
|
||||
|
||||
for (ContractDefinition const* contract: reverse(_contract.annotation().linearizedBaseContracts))
|
||||
{
|
||||
string out;
|
||||
if (!constructor->isPayable())
|
||||
out = callValueCheck();
|
||||
solUnimplementedAssert(constructor->parameters().empty(), "");
|
||||
return move(out) + m_context.functionName(*constructor) + "()\n";
|
||||
out <<
|
||||
"\n// Begin state variable initialization for contract \"" <<
|
||||
contract->name() <<
|
||||
"\" (" <<
|
||||
contract->stateVariables().size() <<
|
||||
" variables)\n";
|
||||
|
||||
IRGeneratorForStatements generator{m_context, m_utils};
|
||||
for (VariableDeclaration const* variable: contract->stateVariables())
|
||||
if (!variable->isConstant())
|
||||
generator.initializeStateVar(*variable);
|
||||
out << generator.code();
|
||||
|
||||
out << "// End state variable initialization for contract \"" << contract->name() << "\".\n";
|
||||
}
|
||||
|
||||
return {};
|
||||
if (constructor)
|
||||
{
|
||||
solUnimplementedAssert(constructor->parameters().empty(), "");
|
||||
|
||||
// TODO base constructors
|
||||
|
||||
out << m_context.functionName(*constructor) + "()\n";
|
||||
}
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
string IRGenerator::deployCode(ContractDefinition const& _contract)
|
||||
@ -227,14 +282,21 @@ string IRGenerator::dispatchRoutine(ContractDefinition const& _contract)
|
||||
|
||||
unsigned paramVars = make_shared<TupleType>(type->parameterTypes())->sizeOnStack();
|
||||
unsigned retVars = make_shared<TupleType>(type->returnParameterTypes())->sizeOnStack();
|
||||
templ["assignToParams"] = paramVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("param_", 0, paramVars) + " := ";
|
||||
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + m_utils.suffixedVariableNameList("ret_", 0, retVars) + " := ";
|
||||
templ["assignToParams"] = paramVars == 0 ? "" : "let " + suffixedVariableNameList("param_", 0, paramVars) + " := ";
|
||||
templ["assignToRetParams"] = retVars == 0 ? "" : "let " + suffixedVariableNameList("ret_", 0, retVars) + " := ";
|
||||
|
||||
ABIFunctions abiFunctions(m_evmVersion, m_context.functionCollector());
|
||||
templ["abiDecode"] = abiFunctions.tupleDecoder(type->parameterTypes());
|
||||
templ["params"] = m_utils.suffixedVariableNameList("param_", 0, paramVars);
|
||||
templ["retParams"] = m_utils.suffixedVariableNameList("ret_", retVars, 0);
|
||||
templ["function"] = generateFunction(dynamic_cast<FunctionDefinition const&>(type->declaration()));
|
||||
templ["params"] = suffixedVariableNameList("param_", 0, paramVars);
|
||||
templ["retParams"] = suffixedVariableNameList("ret_", retVars, 0);
|
||||
|
||||
if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&type->declaration()))
|
||||
templ["function"] = generateFunction(*funDef);
|
||||
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&type->declaration()))
|
||||
templ["function"] = generateGetter(*varDecl);
|
||||
else
|
||||
solAssert(false, "Unexpected declaration for function!");
|
||||
|
||||
templ["allocate"] = m_utils.allocationFunction();
|
||||
templ["abiEncode"] = abiFunctions.tupleEncoder(type->returnParameterTypes(), type->returnParameterTypes(), false);
|
||||
templ["comma"] = retVars == 0 ? "" : ", ";
|
||||
|
@ -56,6 +56,8 @@ private:
|
||||
|
||||
/// Generates code for and returns the name of the function.
|
||||
std::string generateFunction(FunctionDefinition const& _function);
|
||||
/// Generates a getter for the given declaration and returns its name
|
||||
std::string generateGetter(VariableDeclaration const& _varDecl);
|
||||
|
||||
std::string constructorCode(ContractDefinition const& _contract);
|
||||
std::string deployCode(ContractDefinition const& _contract);
|
||||
|
@ -133,6 +133,21 @@ string IRGeneratorForStatements::code() const
|
||||
return m_code.str();
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable.");
|
||||
solAssert(!_varDecl.isConstant(), "");
|
||||
if (_varDecl.value())
|
||||
{
|
||||
_varDecl.value()->accept(*this);
|
||||
string value = m_context.newYulVariable();
|
||||
Type const& varType = *_varDecl.type();
|
||||
|
||||
m_code << "let " << value << " := " << expressionAsType(*_varDecl.value(), varType) << "\n";
|
||||
m_code << IRStorageItem{m_context, _varDecl}.storeValue(value, varType);
|
||||
}
|
||||
}
|
||||
|
||||
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
|
||||
{
|
||||
for (auto const& decl: _varDeclStatement.declarations())
|
||||
@ -593,8 +608,42 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
||||
|
||||
break;
|
||||
}
|
||||
// Array creation using new
|
||||
case FunctionType::Kind::ObjectCreation:
|
||||
{
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
|
||||
solAssert(arguments.size() == 1, "");
|
||||
|
||||
defineExpression(_functionCall) <<
|
||||
m_utils.allocateMemoryArrayFunction(arrayType) <<
|
||||
"(" <<
|
||||
expressionAsType(*arguments[0], *TypeProvider::uint256()) <<
|
||||
")\n";
|
||||
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::KECCAK256:
|
||||
{
|
||||
solAssert(arguments.size() == 1, "");
|
||||
|
||||
ArrayType const* arrayType = TypeProvider::bytesMemory();
|
||||
string const& array = m_context.newYulVariable();
|
||||
m_code << "let " << array << " := " << expressionAsType(*arguments[0], *arrayType) << "\n";
|
||||
|
||||
defineExpression(_functionCall) <<
|
||||
"keccak256(" <<
|
||||
m_utils.arrayDataAreaFunction(*arrayType) << "(" <<
|
||||
array <<
|
||||
"), " <<
|
||||
m_utils.arrayLengthFunction(*arrayType) <<
|
||||
"(" <<
|
||||
array <<
|
||||
"))\n";
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
solUnimplemented("");
|
||||
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@ -748,7 +797,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
setLValue(_memberAccess, make_unique<IRStorageArrayLength>(
|
||||
m_context,
|
||||
m_context.utils(),
|
||||
m_context.variable(_memberAccess.expression()),
|
||||
*_memberAccess.annotation().type,
|
||||
type
|
||||
@ -756,11 +805,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
|
||||
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
solUnimplementedAssert(false, "");
|
||||
//m_context << Instruction::MLOAD;
|
||||
defineExpression(_memberAccess) <<
|
||||
"mload(" <<
|
||||
m_context.variable(_memberAccess.expression()) <<
|
||||
")\n";
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
@ -813,7 +863,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
templ("key", ", " + m_context.variable(*_indexAccess.indexExpression()));
|
||||
m_code << templ.render();
|
||||
setLValue(_indexAccess, make_unique<IRStorageItem>(
|
||||
m_context,
|
||||
m_context.utils(),
|
||||
slot,
|
||||
0,
|
||||
*_indexAccess.annotation().type
|
||||
@ -842,7 +892,7 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
.render();
|
||||
|
||||
setLValue(_indexAccess, make_unique<IRStorageItem>(
|
||||
m_context,
|
||||
m_context.utils(),
|
||||
slot,
|
||||
offset,
|
||||
*_indexAccess.annotation().type
|
||||
@ -851,13 +901,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
break;
|
||||
}
|
||||
case DataLocation::Memory:
|
||||
solUnimplementedAssert(false, "");
|
||||
break;
|
||||
case DataLocation::CallData:
|
||||
solUnimplementedAssert(false, "");
|
||||
break;
|
||||
}
|
||||
{
|
||||
string const memAddress =
|
||||
m_utils.memoryArrayIndexAccessFunction(arrayType) +
|
||||
"(" +
|
||||
m_context.variable(_indexAccess.baseExpression()) +
|
||||
", " +
|
||||
expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) +
|
||||
")";
|
||||
|
||||
setLValue(_indexAccess, make_unique<IRMemoryItem>(
|
||||
m_context.utils(),
|
||||
memAddress,
|
||||
false,
|
||||
*arrayType.baseType()
|
||||
));
|
||||
break;
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
solUnimplemented("calldata not yet implemented!");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (baseType.category() == Type::Category::FixedBytes)
|
||||
solUnimplementedAssert(false, "");
|
||||
|
@ -45,6 +45,9 @@ public:
|
||||
|
||||
std::string code() const;
|
||||
|
||||
/// Generates code to initialize the given state variable.
|
||||
void initializeStateVar(VariableDeclaration const& _varDecl);
|
||||
|
||||
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
|
||||
bool visit(Assignment const& _assignment) override;
|
||||
bool visit(TupleExpression const& _tuple) override;
|
||||
|
@ -35,7 +35,7 @@ IRLocalVariable::IRLocalVariable(
|
||||
IRGenerationContext& _context,
|
||||
VariableDeclaration const& _varDecl
|
||||
):
|
||||
IRLValue(_context, _varDecl.annotation().type),
|
||||
IRLValue(_context.utils(), _varDecl.annotation().type),
|
||||
m_variableName(_context.localVariableName(_varDecl))
|
||||
{
|
||||
}
|
||||
@ -48,7 +48,7 @@ string IRLocalVariable::storeValue(string const& _value, Type const& _type) cons
|
||||
|
||||
string IRLocalVariable::setToZero() const
|
||||
{
|
||||
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
|
||||
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
|
||||
}
|
||||
|
||||
IRStorageItem::IRStorageItem(
|
||||
@ -56,31 +56,31 @@ IRStorageItem::IRStorageItem(
|
||||
VariableDeclaration const& _varDecl
|
||||
):
|
||||
IRStorageItem(
|
||||
_context,
|
||||
*_varDecl.annotation().type,
|
||||
_context.storageLocationOfVariable(_varDecl)
|
||||
)
|
||||
_context.utils(),
|
||||
*_varDecl.annotation().type,
|
||||
_context.storageLocationOfVariable(_varDecl)
|
||||
)
|
||||
{ }
|
||||
|
||||
IRStorageItem::IRStorageItem(
|
||||
IRGenerationContext& _context,
|
||||
YulUtilFunctions _utils,
|
||||
Type const& _type,
|
||||
std::pair<u256, unsigned> slot_offset
|
||||
):
|
||||
IRLValue(_context, &_type),
|
||||
IRLValue(std::move(_utils), &_type),
|
||||
m_slot(toCompactHexWithPrefix(slot_offset.first)),
|
||||
m_offset(slot_offset.second)
|
||||
{
|
||||
}
|
||||
|
||||
IRStorageItem::IRStorageItem(
|
||||
IRGenerationContext& _context,
|
||||
YulUtilFunctions _utils,
|
||||
string _slot,
|
||||
boost::variant<string, unsigned> _offset,
|
||||
Type const& _type
|
||||
):
|
||||
IRLValue(_context, &_type),
|
||||
m_slot(move(_slot)),
|
||||
IRLValue(std::move(_utils), &_type),
|
||||
m_slot(std::move(_slot)),
|
||||
m_offset(std::move(_offset))
|
||||
{
|
||||
solAssert(!m_offset.empty(), "");
|
||||
@ -94,7 +94,7 @@ string IRStorageItem::retrieveValue() const
|
||||
solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
|
||||
if (m_offset.type() == typeid(string))
|
||||
return
|
||||
m_context.utils().readFromStorageDynamic(*m_type, false) +
|
||||
m_utils.readFromStorageDynamic(*m_type, false) +
|
||||
"(" +
|
||||
m_slot +
|
||||
", " +
|
||||
@ -102,7 +102,7 @@ string IRStorageItem::retrieveValue() const
|
||||
")";
|
||||
else if (m_offset.type() == typeid(unsigned))
|
||||
return
|
||||
m_context.utils().readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
|
||||
m_utils.readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
|
||||
"(" +
|
||||
m_slot +
|
||||
")";
|
||||
@ -121,7 +121,7 @@ string IRStorageItem::storeValue(string const& _value, Type const& _sourceType)
|
||||
offset = get<unsigned>(m_offset);
|
||||
|
||||
return
|
||||
m_context.utils().updateStorageValueFunction(*m_type, offset) +
|
||||
m_utils.updateStorageValueFunction(*m_type, offset) +
|
||||
"(" +
|
||||
m_slot +
|
||||
(m_offset.type() == typeid(string) ?
|
||||
@ -135,26 +135,40 @@ string IRStorageItem::storeValue(string const& _value, Type const& _sourceType)
|
||||
|
||||
string IRStorageItem::setToZero() const
|
||||
{
|
||||
solUnimplementedAssert(m_type->isValueType(), "");
|
||||
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
|
||||
return
|
||||
m_utils.storageSetToZeroFunction(*m_type) +
|
||||
"(" +
|
||||
m_slot +
|
||||
", " +
|
||||
(
|
||||
m_offset.type() == typeid(unsigned) ?
|
||||
to_string(get<unsigned>(m_offset)) :
|
||||
get<string>(m_offset)
|
||||
) +
|
||||
")\n";
|
||||
}
|
||||
|
||||
IRStorageArrayLength::IRStorageArrayLength(IRGenerationContext& _context, string _slot, Type const& _type, ArrayType const& _arrayType):
|
||||
IRLValue(_context, &_type), m_arrayType(_arrayType), m_slot(move(_slot))
|
||||
IRStorageArrayLength::IRStorageArrayLength(
|
||||
YulUtilFunctions _utils,
|
||||
string _slot,
|
||||
Type const& _type,
|
||||
ArrayType const& _arrayType
|
||||
):
|
||||
IRLValue(std::move(_utils), &_type), m_arrayType(_arrayType), m_slot(move(_slot))
|
||||
{
|
||||
solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!");
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::retrieveValue() const
|
||||
{
|
||||
return m_context.utils().arrayLengthFunction(m_arrayType) + "(" + m_slot + ")\n";
|
||||
return m_utils.arrayLengthFunction(m_arrayType) + "(" + m_slot + ")";
|
||||
}
|
||||
|
||||
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
|
||||
{
|
||||
solAssert(_type == *m_type, "Different type, but might not be an error.");
|
||||
|
||||
return m_context.utils().resizeDynamicArrayFunction(m_arrayType) +
|
||||
return m_utils.resizeDynamicArrayFunction(m_arrayType) +
|
||||
"(" +
|
||||
m_slot +
|
||||
", " +
|
||||
@ -166,3 +180,82 @@ string IRStorageArrayLength::setToZero() const
|
||||
{
|
||||
return storeValue("0", *TypeProvider::uint256());
|
||||
}
|
||||
|
||||
IRMemoryItem::IRMemoryItem(
|
||||
YulUtilFunctions _utils,
|
||||
std::string _address,
|
||||
bool _byteArrayElement,
|
||||
Type const& _type
|
||||
):
|
||||
IRLValue(std::move(_utils), &_type),
|
||||
m_address(move(_address)),
|
||||
m_byteArrayElement(_byteArrayElement)
|
||||
{ }
|
||||
|
||||
string IRMemoryItem::retrieveValue() const
|
||||
{
|
||||
if (m_byteArrayElement)
|
||||
return m_utils.cleanupFunction(*m_type) +
|
||||
"(mload(" +
|
||||
m_address +
|
||||
"))";
|
||||
|
||||
if (m_type->isValueType())
|
||||
return m_utils.readFromMemory(*m_type) +
|
||||
"(" +
|
||||
m_address +
|
||||
")";
|
||||
else
|
||||
return "mload(" + m_address + ")";
|
||||
}
|
||||
|
||||
string IRMemoryItem::storeValue(string const& _value, Type const& _type) const
|
||||
{
|
||||
if (!m_type->isValueType())
|
||||
{
|
||||
solUnimplementedAssert(_type == *m_type, "Conversion not implemented for assignment to memory.");
|
||||
|
||||
solAssert(m_type->sizeOnStack() == 1, "");
|
||||
solAssert(dynamic_cast<ReferenceType const*>(m_type), "");
|
||||
|
||||
return "mstore(" + m_address + ", " + _value + ")\n";
|
||||
}
|
||||
|
||||
solAssert(_type.isValueType(), "");
|
||||
|
||||
string prepared = _value;
|
||||
|
||||
// Exists to see if this case ever happens
|
||||
solAssert(_type == *m_type, "");
|
||||
|
||||
if (_type != *m_type)
|
||||
prepared =
|
||||
m_utils.conversionFunction(_type, *m_type) +
|
||||
"(" +
|
||||
_value +
|
||||
")";
|
||||
else
|
||||
prepared =
|
||||
m_utils.cleanupFunction(*m_type) +
|
||||
"(" +
|
||||
_value +
|
||||
")";
|
||||
|
||||
if (m_byteArrayElement)
|
||||
{
|
||||
solAssert(*m_type == *TypeProvider::byte(), "");
|
||||
return "mstore8(" + m_address + ", byte(0, " + prepared + "))\n";
|
||||
}
|
||||
else
|
||||
return m_utils.writeToMemoryFunction(*m_type) +
|
||||
"(" +
|
||||
m_address +
|
||||
", " +
|
||||
prepared +
|
||||
")\n";
|
||||
}
|
||||
|
||||
string IRMemoryItem::setToZero() const
|
||||
{
|
||||
return storeValue(m_utils.zeroValueFunction(*m_type) + "()", *m_type);
|
||||
}
|
||||
|
@ -20,6 +20,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <string>
|
||||
@ -42,8 +44,8 @@ class ArrayType;
|
||||
class IRLValue
|
||||
{
|
||||
protected:
|
||||
IRLValue(IRGenerationContext& _context, Type const* _type = nullptr):
|
||||
m_context(_context),
|
||||
explicit IRLValue(YulUtilFunctions _utils, Type const* _type = nullptr):
|
||||
m_utils(std::move(_utils)),
|
||||
m_type(_type)
|
||||
{}
|
||||
|
||||
@ -58,7 +60,7 @@ public:
|
||||
/// Returns code that will reset the stored value to zero
|
||||
virtual std::string setToZero() const = 0;
|
||||
protected:
|
||||
IRGenerationContext& m_context;
|
||||
YulUtilFunctions mutable m_utils;
|
||||
Type const* m_type;
|
||||
};
|
||||
|
||||
@ -85,7 +87,7 @@ public:
|
||||
VariableDeclaration const& _varDecl
|
||||
);
|
||||
IRStorageItem(
|
||||
IRGenerationContext& _context,
|
||||
YulUtilFunctions _utils,
|
||||
std::string _slot,
|
||||
boost::variant<std::string, unsigned> _offset,
|
||||
Type const& _type
|
||||
@ -96,7 +98,7 @@ public:
|
||||
std::string setToZero() const override;
|
||||
private:
|
||||
IRStorageItem(
|
||||
IRGenerationContext& _context,
|
||||
YulUtilFunctions _utils,
|
||||
Type const& _type,
|
||||
std::pair<u256, unsigned> slot_offset
|
||||
);
|
||||
@ -116,7 +118,12 @@ private:
|
||||
class IRStorageArrayLength: public IRLValue
|
||||
{
|
||||
public:
|
||||
IRStorageArrayLength(IRGenerationContext& _context, std::string _slot, Type const& _type, ArrayType const& _arrayType);
|
||||
IRStorageArrayLength(
|
||||
YulUtilFunctions _utils,
|
||||
std::string _slot,
|
||||
Type const& _type,
|
||||
ArrayType const& _arrayType
|
||||
);
|
||||
|
||||
std::string retrieveValue() const override;
|
||||
std::string storeValue(std::string const& _value, Type const& _type) const override;
|
||||
@ -127,5 +134,23 @@ private:
|
||||
std::string const m_slot;
|
||||
};
|
||||
|
||||
class IRMemoryItem: public IRLValue
|
||||
{
|
||||
public:
|
||||
IRMemoryItem(
|
||||
YulUtilFunctions _utils,
|
||||
std::string _address,
|
||||
bool _byteArrayElement,
|
||||
Type const& _type
|
||||
);
|
||||
std::string retrieveValue() const override;
|
||||
std::string storeValue(std::string const& _value, Type const& _type) const override;
|
||||
|
||||
std::string setToZero() const override;
|
||||
private:
|
||||
std::string const m_address;
|
||||
bool m_byteArrayElement;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
10
libsolidity/codegen/ir/README.md
Normal file
10
libsolidity/codegen/ir/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# The Solidity to Yul Code Generator
|
||||
|
||||
This directory contains the new experimental code generator that
|
||||
compiles Solidity to an intermediate representation in Yul
|
||||
with EVM dialect.
|
||||
|
||||
The main semantic differences to the legacy code generator are the following:
|
||||
|
||||
- Arithmetic operations cause a failing assertion if the result is not in range.
|
||||
- Resizing a storage array to a length larger than 2**64 causes a failing assertion.
|
884
libsolidity/formal/BMC.cpp
Normal file
884
libsolidity/formal/BMC.cpp
Normal file
@ -0,0 +1,884 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/formal/BMC.h>
|
||||
|
||||
#include <libsolidity/formal/SMTPortfolio.h>
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
|
||||
BMC::BMC(smt::EncodingContext& _context, ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses):
|
||||
SMTEncoder(_context),
|
||||
m_outerErrorReporter(_errorReporter),
|
||||
m_interface(make_shared<smt::SMTPortfolio>(_smtlib2Responses))
|
||||
{
|
||||
#if defined (HAVE_Z3) || defined (HAVE_CVC4)
|
||||
if (!_smtlib2Responses.empty())
|
||||
m_errorReporter.warning(
|
||||
"SMT-LIB2 query responses were given in the auxiliary input, "
|
||||
"but this Solidity binary uses an SMT solver (Z3/CVC4) directly."
|
||||
"These responses will be ignored."
|
||||
"Consider disabling Z3/CVC4 at compilation time in order to use SMT-LIB2 responses."
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BMC::analyze(SourceUnit const& _source, set<Expression const*> _safeAssertions)
|
||||
{
|
||||
solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), "");
|
||||
|
||||
m_safeAssertions += move(_safeAssertions);
|
||||
m_context.setSolver(m_interface);
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(true);
|
||||
m_variableUsage.setFunctionInlining(true);
|
||||
|
||||
_source.accept(*this);
|
||||
|
||||
solAssert(m_interface->solvers() > 0, "");
|
||||
// If this check is true, Z3 and CVC4 are not available
|
||||
// and the query answers were not provided, since SMTPortfolio
|
||||
// guarantees that SmtLib2Interface is the first solver.
|
||||
if (!m_interface->unhandledQueries().empty() && m_interface->solvers() == 1)
|
||||
{
|
||||
if (!m_noSolverWarning)
|
||||
{
|
||||
m_noSolverWarning = true;
|
||||
m_outerErrorReporter.warning(
|
||||
SourceLocation(),
|
||||
"BMC analysis was not possible since no integrated SMT solver (Z3 or CVC4) was found."
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
m_outerErrorReporter.append(m_errorReporter.errors());
|
||||
|
||||
m_errorReporter.clear();
|
||||
}
|
||||
|
||||
bool BMC::shouldInlineFunctionCall(FunctionCall const& _funCall)
|
||||
{
|
||||
FunctionDefinition const* funDef = functionCallToDefinition(_funCall);
|
||||
if (!funDef || !funDef->isImplemented())
|
||||
return false;
|
||||
|
||||
FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
if (funType.kind() == FunctionType::Kind::External)
|
||||
{
|
||||
auto memberAccess = dynamic_cast<MemberAccess const*>(&_funCall.expression());
|
||||
if (!memberAccess)
|
||||
return false;
|
||||
|
||||
auto identifier = dynamic_cast<Identifier const*>(&memberAccess->expression());
|
||||
if (!(
|
||||
identifier &&
|
||||
identifier->name() == "this" &&
|
||||
identifier->annotation().referencedDeclaration &&
|
||||
dynamic_cast<MagicVariableDeclaration const*>(identifier->annotation().referencedDeclaration)
|
||||
))
|
||||
return false;
|
||||
}
|
||||
else if (funType.kind() != FunctionType::Kind::Internal)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// AST visitors.
|
||||
|
||||
bool BMC::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
SMTEncoder::visit(_contract);
|
||||
|
||||
/// Check targets created by state variable initialization.
|
||||
smt::Expression constraints = m_context.assertions();
|
||||
checkVerificationTargets(constraints);
|
||||
m_verificationTargets.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BMC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
SMTEncoder::endVisit(_contract);
|
||||
}
|
||||
|
||||
bool BMC::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
auto contract = dynamic_cast<ContractDefinition const*>(_function.scope());
|
||||
solAssert(contract, "");
|
||||
solAssert(m_currentContract, "");
|
||||
auto const& hierarchy = m_currentContract->annotation().linearizedBaseContracts;
|
||||
if (find(hierarchy.begin(), hierarchy.end(), contract) == hierarchy.end())
|
||||
initializeStateVariables(*contract);
|
||||
|
||||
if (m_callStack.empty())
|
||||
reset();
|
||||
|
||||
/// Already visits the children.
|
||||
SMTEncoder::visit(_function);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BMC::endVisit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (isRootFunction())
|
||||
{
|
||||
smt::Expression constraints = m_context.assertions();
|
||||
checkVerificationTargets(constraints);
|
||||
m_verificationTargets.clear();
|
||||
}
|
||||
|
||||
SMTEncoder::endVisit(_function);
|
||||
}
|
||||
|
||||
bool BMC::visit(IfStatement const& _node)
|
||||
{
|
||||
// This check needs to be done in its own context otherwise
|
||||
// constraints from the If body might influence it.
|
||||
m_context.pushSolver();
|
||||
_node.condition().accept(*this);
|
||||
|
||||
// We ignore called functions here because they have
|
||||
// specific input values.
|
||||
if (isRootFunction())
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::ConstantCondition,
|
||||
expr(_node.condition()),
|
||||
&_node.condition()
|
||||
);
|
||||
m_context.popSolver();
|
||||
|
||||
SMTEncoder::visit(_node);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here we consider the execution of two branches:
|
||||
// Branch 1 assumes the loop condition to be true and executes the loop once,
|
||||
// after resetting touched variables.
|
||||
// Branch 2 assumes the loop condition to be false and skips the loop after
|
||||
// visiting the condition (it might contain side-effects, they need to be considered)
|
||||
// and does not erase knowledge.
|
||||
// If the loop is a do-while, condition side-effects are lost since the body,
|
||||
// executed once before the condition, might reassign variables.
|
||||
// Variables touched by the loop are merged with Branch 2.
|
||||
bool BMC::visit(WhileStatement const& _node)
|
||||
{
|
||||
auto indicesBeforeLoop = copyVariableIndices();
|
||||
auto touchedVars = touchedVariables(_node);
|
||||
m_context.resetVariables(touchedVars);
|
||||
decltype(indicesBeforeLoop) indicesAfterLoop;
|
||||
if (_node.isDoWhile())
|
||||
{
|
||||
indicesAfterLoop = visitBranch(&_node.body());
|
||||
// TODO the assertions generated in the body should still be active in the condition
|
||||
_node.condition().accept(*this);
|
||||
if (isRootFunction())
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::ConstantCondition,
|
||||
expr(_node.condition()),
|
||||
&_node.condition()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_node.condition().accept(*this);
|
||||
if (isRootFunction())
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::ConstantCondition,
|
||||
expr(_node.condition()),
|
||||
&_node.condition()
|
||||
);
|
||||
|
||||
indicesAfterLoop = visitBranch(&_node.body(), expr(_node.condition()));
|
||||
}
|
||||
|
||||
// We reset the execution to before the loop
|
||||
// and visit the condition in case it's not a do-while.
|
||||
// A do-while's body might have non-precise information
|
||||
// in its first run about variables that are touched.
|
||||
resetVariableIndices(indicesBeforeLoop);
|
||||
if (!_node.isDoWhile())
|
||||
_node.condition().accept(*this);
|
||||
|
||||
mergeVariables(touchedVars, expr(_node.condition()), indicesAfterLoop, copyVariableIndices());
|
||||
|
||||
m_loopExecutionHappened = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here we consider the execution of two branches similar to WhileStatement.
|
||||
bool BMC::visit(ForStatement const& _node)
|
||||
{
|
||||
if (_node.initializationExpression())
|
||||
_node.initializationExpression()->accept(*this);
|
||||
|
||||
auto indicesBeforeLoop = copyVariableIndices();
|
||||
|
||||
// Do not reset the init expression part.
|
||||
auto touchedVars = touchedVariables(_node.body());
|
||||
if (_node.condition())
|
||||
touchedVars += touchedVariables(*_node.condition());
|
||||
if (_node.loopExpression())
|
||||
touchedVars += touchedVariables(*_node.loopExpression());
|
||||
|
||||
m_context.resetVariables(touchedVars);
|
||||
|
||||
if (_node.condition())
|
||||
{
|
||||
_node.condition()->accept(*this);
|
||||
if (isRootFunction())
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::ConstantCondition,
|
||||
expr(*_node.condition()),
|
||||
_node.condition()
|
||||
);
|
||||
}
|
||||
|
||||
m_context.pushSolver();
|
||||
if (_node.condition())
|
||||
m_context.addAssertion(expr(*_node.condition()));
|
||||
_node.body().accept(*this);
|
||||
if (_node.loopExpression())
|
||||
_node.loopExpression()->accept(*this);
|
||||
m_context.popSolver();
|
||||
|
||||
auto indicesAfterLoop = copyVariableIndices();
|
||||
// We reset the execution to before the loop
|
||||
// and visit the condition.
|
||||
resetVariableIndices(indicesBeforeLoop);
|
||||
if (_node.condition())
|
||||
_node.condition()->accept(*this);
|
||||
|
||||
auto forCondition = _node.condition() ? expr(*_node.condition()) : smt::Expression(true);
|
||||
mergeVariables(touchedVars, forCondition, indicesAfterLoop, copyVariableIndices());
|
||||
|
||||
m_loopExecutionHappened = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BMC::endVisit(UnaryOperation const& _op)
|
||||
{
|
||||
SMTEncoder::endVisit(_op);
|
||||
|
||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
||||
return;
|
||||
|
||||
switch (_op.getOperator())
|
||||
{
|
||||
case Token::Inc: // ++ (pre- or postfix)
|
||||
case Token::Dec: // -- (pre- or postfix)
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::UnderOverflow,
|
||||
expr(_op),
|
||||
&_op
|
||||
);
|
||||
break;
|
||||
case Token::Sub: // -
|
||||
if (_op.annotation().type->category() == Type::Category::Integer)
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::UnderOverflow,
|
||||
expr(_op),
|
||||
&_op
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void BMC::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
|
||||
if (_funCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||
{
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
return;
|
||||
}
|
||||
|
||||
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::Require:
|
||||
visitRequire(_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:
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
internalOrExternalFunctionCall(_funCall);
|
||||
break;
|
||||
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);
|
||||
abstractFunctionCall(_funCall);
|
||||
break;
|
||||
case FunctionType::Kind::Send:
|
||||
case FunctionType::Kind::Transfer:
|
||||
{
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
auto value = _funCall.arguments().front();
|
||||
solAssert(value, "");
|
||||
smt::Expression thisBalance = m_context.balance();
|
||||
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::Balance,
|
||||
thisBalance < expr(*value),
|
||||
&_funCall
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SMTEncoder::endVisit(_funCall);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor helpers.
|
||||
|
||||
void BMC::visitAssert(FunctionCall const& _funCall)
|
||||
{
|
||||
auto const& args = _funCall.arguments();
|
||||
solAssert(args.size() == 1, "");
|
||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::Assert,
|
||||
expr(*args.front()),
|
||||
&_funCall
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::visitRequire(FunctionCall const& _funCall)
|
||||
{
|
||||
auto const& args = _funCall.arguments();
|
||||
solAssert(args.size() >= 1, "");
|
||||
solAssert(args.front()->annotation().type->category() == Type::Category::Bool, "");
|
||||
if (isRootFunction())
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::ConstantCondition,
|
||||
expr(*args.front()),
|
||||
args.front().get()
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::inlineFunctionCall(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(shouldInlineFunctionCall(_funCall), "");
|
||||
FunctionDefinition const* funDef = functionCallToDefinition(_funCall);
|
||||
solAssert(funDef, "");
|
||||
|
||||
if (visitedFunction(funDef))
|
||||
{
|
||||
auto const& returnParams = funDef->returnParameters();
|
||||
for (auto param: returnParams)
|
||||
{
|
||||
m_context.newValue(*param);
|
||||
m_context.setUnknownValue(*param);
|
||||
}
|
||||
|
||||
m_errorReporter.warning(
|
||||
_funCall.location(),
|
||||
"Assertion checker does not support recursive function calls.",
|
||||
SecondarySourceLocation().append("Starting from function:", funDef->location())
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<smt::Expression> funArgs;
|
||||
Expression const* calledExpr = &_funCall.expression();
|
||||
auto const& funType = dynamic_cast<FunctionType const*>(calledExpr->annotation().type);
|
||||
solAssert(funType, "");
|
||||
|
||||
if (funType->bound())
|
||||
{
|
||||
auto const& boundFunction = dynamic_cast<MemberAccess const*>(calledExpr);
|
||||
solAssert(boundFunction, "");
|
||||
funArgs.push_back(expr(boundFunction->expression()));
|
||||
}
|
||||
|
||||
for (auto arg: _funCall.arguments())
|
||||
funArgs.push_back(expr(*arg));
|
||||
initializeFunctionCallParameters(*funDef, funArgs);
|
||||
|
||||
// The reason why we need to pushCallStack here instead of visit(FunctionDefinition)
|
||||
// is that there we don't have `_funCall`.
|
||||
pushCallStack({funDef, &_funCall});
|
||||
// If an internal function is called to initialize
|
||||
// a state variable.
|
||||
if (m_callStack.empty())
|
||||
initFunction(*funDef);
|
||||
funDef->accept(*this);
|
||||
}
|
||||
|
||||
createReturnedExpressions(_funCall);
|
||||
}
|
||||
|
||||
void BMC::abstractFunctionCall(FunctionCall const& _funCall)
|
||||
{
|
||||
vector<smt::Expression> smtArguments;
|
||||
for (auto const& arg: _funCall.arguments())
|
||||
smtArguments.push_back(expr(*arg));
|
||||
defineExpr(_funCall, (*m_context.expression(_funCall.expression()))(smtArguments));
|
||||
m_uninterpretedTerms.insert(&_funCall);
|
||||
setSymbolicUnknownValue(expr(_funCall), _funCall.annotation().type, m_context);
|
||||
}
|
||||
|
||||
void BMC::internalOrExternalFunctionCall(FunctionCall const& _funCall)
|
||||
{
|
||||
auto const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type);
|
||||
if (shouldInlineFunctionCall(_funCall))
|
||||
inlineFunctionCall(_funCall);
|
||||
else if (funType.kind() == FunctionType::Kind::Internal)
|
||||
m_errorReporter.warning(
|
||||
_funCall.location(),
|
||||
"Assertion checker does not yet implement this type of function call."
|
||||
);
|
||||
else
|
||||
{
|
||||
m_externalFunctionCallHappened = true;
|
||||
resetStateVariables();
|
||||
resetStorageReferences();
|
||||
}
|
||||
}
|
||||
|
||||
pair<smt::Expression, smt::Expression> BMC::arithmeticOperation(
|
||||
Token _op,
|
||||
smt::Expression const& _left,
|
||||
smt::Expression const& _right,
|
||||
TypePointer const& _commonType,
|
||||
Expression const& _expression
|
||||
)
|
||||
{
|
||||
if (_op == Token::Div || _op == Token::Mod)
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::DivByZero,
|
||||
_right,
|
||||
&_expression
|
||||
);
|
||||
|
||||
auto values = SMTEncoder::arithmeticOperation(_op, _left, _right, _commonType, _expression);
|
||||
|
||||
addVerificationTarget(
|
||||
VerificationTarget::Type::UnderOverflow,
|
||||
values.second,
|
||||
&_expression
|
||||
);
|
||||
return values;
|
||||
}
|
||||
|
||||
void BMC::resetStorageReferences()
|
||||
{
|
||||
m_context.resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); });
|
||||
}
|
||||
|
||||
void BMC::reset()
|
||||
{
|
||||
m_externalFunctionCallHappened = false;
|
||||
m_loopExecutionHappened = false;
|
||||
}
|
||||
|
||||
pair<vector<smt::Expression>, vector<string>> BMC::modelExpressions()
|
||||
{
|
||||
vector<smt::Expression> expressionsToEvaluate;
|
||||
vector<string> expressionNames;
|
||||
for (auto const& var: m_context.variables())
|
||||
if (var.first->type()->isValueType())
|
||||
{
|
||||
expressionsToEvaluate.emplace_back(currentValue(*var.first));
|
||||
expressionNames.push_back(var.first->name());
|
||||
}
|
||||
for (auto const& var: m_context.globalSymbols())
|
||||
{
|
||||
auto const& type = var.second->type();
|
||||
if (
|
||||
type->isValueType() &&
|
||||
smt::smtKind(type->category()) != smt::Kind::Function
|
||||
)
|
||||
{
|
||||
expressionsToEvaluate.emplace_back(var.second->currentValue());
|
||||
expressionNames.push_back(var.first);
|
||||
}
|
||||
}
|
||||
for (auto const& uf: m_uninterpretedTerms)
|
||||
if (uf->annotation().type->isValueType())
|
||||
{
|
||||
expressionsToEvaluate.emplace_back(expr(*uf));
|
||||
expressionNames.push_back(uf->location().text());
|
||||
}
|
||||
|
||||
return {expressionsToEvaluate, expressionNames};
|
||||
}
|
||||
|
||||
/// Verification targets.
|
||||
|
||||
void BMC::checkVerificationTargets(smt::Expression const& _constraints)
|
||||
{
|
||||
for (auto& target: m_verificationTargets)
|
||||
checkVerificationTarget(target, _constraints);
|
||||
}
|
||||
|
||||
void BMC::checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints)
|
||||
{
|
||||
switch (_target.type)
|
||||
{
|
||||
case VerificationTarget::Type::ConstantCondition:
|
||||
checkConstantCondition(_target);
|
||||
break;
|
||||
case VerificationTarget::Type::Underflow:
|
||||
checkUnderflow(_target, _constraints);
|
||||
break;
|
||||
case VerificationTarget::Type::Overflow:
|
||||
checkOverflow(_target, _constraints);
|
||||
break;
|
||||
case VerificationTarget::Type::UnderOverflow:
|
||||
checkUnderflow(_target, _constraints);
|
||||
checkOverflow(_target, _constraints);
|
||||
break;
|
||||
case VerificationTarget::Type::DivByZero:
|
||||
checkDivByZero(_target);
|
||||
break;
|
||||
case VerificationTarget::Type::Balance:
|
||||
checkBalance(_target);
|
||||
break;
|
||||
case VerificationTarget::Type::Assert:
|
||||
checkAssert(_target);
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "");
|
||||
}
|
||||
}
|
||||
|
||||
void BMC::checkConstantCondition(VerificationTarget& _target)
|
||||
{
|
||||
checkBooleanNotConstant(
|
||||
*_target.expression,
|
||||
_target.constraints,
|
||||
_target.value,
|
||||
_target.callStack,
|
||||
"Condition is always $VALUE."
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints)
|
||||
{
|
||||
solAssert(
|
||||
_target.type == VerificationTarget::Type::Underflow ||
|
||||
_target.type == VerificationTarget::Type::UnderOverflow,
|
||||
""
|
||||
);
|
||||
auto intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type);
|
||||
solAssert(intType, "");
|
||||
checkCondition(
|
||||
_target.constraints && _constraints && _target.value < smt::minValue(*intType),
|
||||
_target.callStack,
|
||||
_target.modelExpressions,
|
||||
_target.expression->location(),
|
||||
"Underflow (resulting value less than " + formatNumberReadable(intType->minValue()) + ")",
|
||||
"<result>",
|
||||
&_target.value
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints)
|
||||
{
|
||||
solAssert(
|
||||
_target.type == VerificationTarget::Type::Overflow ||
|
||||
_target.type == VerificationTarget::Type::UnderOverflow,
|
||||
""
|
||||
);
|
||||
auto intType = dynamic_cast<IntegerType const*>(_target.expression->annotation().type);
|
||||
solAssert(intType, "");
|
||||
checkCondition(
|
||||
_target.constraints && _constraints && _target.value > smt::maxValue(*intType),
|
||||
_target.callStack,
|
||||
_target.modelExpressions,
|
||||
_target.expression->location(),
|
||||
"Overflow (resulting value larger than " + formatNumberReadable(intType->maxValue()) + ")",
|
||||
"<result>",
|
||||
&_target.value
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::checkDivByZero(VerificationTarget& _target)
|
||||
{
|
||||
solAssert(_target.type == VerificationTarget::Type::DivByZero, "");
|
||||
checkCondition(
|
||||
_target.constraints && (_target.value == 0),
|
||||
_target.callStack,
|
||||
_target.modelExpressions,
|
||||
_target.expression->location(),
|
||||
"Division by zero",
|
||||
"<result>",
|
||||
&_target.value
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::checkBalance(VerificationTarget& _target)
|
||||
{
|
||||
solAssert(_target.type == VerificationTarget::Type::Balance, "");
|
||||
checkCondition(
|
||||
_target.constraints && _target.value,
|
||||
_target.callStack,
|
||||
_target.modelExpressions,
|
||||
_target.expression->location(),
|
||||
"Insufficient funds",
|
||||
"address(this).balance"
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::checkAssert(VerificationTarget& _target)
|
||||
{
|
||||
solAssert(_target.type == VerificationTarget::Type::Assert, "");
|
||||
if (!m_safeAssertions.count(_target.expression))
|
||||
checkCondition(
|
||||
_target.constraints && !_target.value,
|
||||
_target.callStack,
|
||||
_target.modelExpressions,
|
||||
_target.expression->location(),
|
||||
"Assertion violation"
|
||||
);
|
||||
}
|
||||
|
||||
void BMC::addVerificationTarget(
|
||||
VerificationTarget::Type _type,
|
||||
smt::Expression const& _value,
|
||||
Expression const* _expression
|
||||
)
|
||||
{
|
||||
VerificationTarget target{
|
||||
_type,
|
||||
_value,
|
||||
currentPathConditions() && m_context.assertions(),
|
||||
_expression,
|
||||
m_callStack,
|
||||
modelExpressions()
|
||||
};
|
||||
if (_type == VerificationTarget::Type::ConstantCondition)
|
||||
checkVerificationTarget(target);
|
||||
else
|
||||
m_verificationTargets.emplace_back(move(target));
|
||||
}
|
||||
|
||||
/// Solving.
|
||||
|
||||
void BMC::checkCondition(
|
||||
smt::Expression _condition,
|
||||
vector<SMTEncoder::CallStackEntry> const& callStack,
|
||||
pair<vector<smt::Expression>, vector<string>> const& _modelExpressions,
|
||||
SourceLocation const& _location,
|
||||
string const& _description,
|
||||
string const& _additionalValueName,
|
||||
smt::Expression const* _additionalValue
|
||||
)
|
||||
{
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(_condition);
|
||||
|
||||
vector<smt::Expression> expressionsToEvaluate;
|
||||
vector<string> expressionNames;
|
||||
tie(expressionsToEvaluate, expressionNames) = _modelExpressions;
|
||||
if (callStack.size())
|
||||
if (_additionalValue)
|
||||
{
|
||||
expressionsToEvaluate.emplace_back(*_additionalValue);
|
||||
expressionNames.push_back(_additionalValueName);
|
||||
}
|
||||
smt::CheckResult result;
|
||||
vector<string> values;
|
||||
tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate);
|
||||
|
||||
string extraComment = SMTEncoder::extraComment();
|
||||
if (m_loopExecutionHappened)
|
||||
extraComment +=
|
||||
"\nNote that some information is erased after the execution of loops.\n"
|
||||
"You can re-introduce information using require().";
|
||||
if (m_externalFunctionCallHappened)
|
||||
extraComment+=
|
||||
"\nNote that external function calls are not inlined,"
|
||||
" even if the source code of the function is available."
|
||||
" This is due to the possibility that the actual called contract"
|
||||
" has the same ABI but implements the function differently.";
|
||||
|
||||
SecondarySourceLocation secondaryLocation{};
|
||||
secondaryLocation.append(extraComment, SourceLocation{});
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case smt::CheckResult::SATISFIABLE:
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << _description << " happens here";
|
||||
if (callStack.size())
|
||||
{
|
||||
std::ostringstream modelMessage;
|
||||
modelMessage << " for:\n";
|
||||
solAssert(values.size() == expressionNames.size(), "");
|
||||
map<string, string> sortedModel;
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
if (expressionsToEvaluate.at(i).name != values.at(i))
|
||||
sortedModel[expressionNames.at(i)] = values.at(i);
|
||||
|
||||
for (auto const& eval: sortedModel)
|
||||
modelMessage << " " << eval.first << " = " << eval.second << "\n";
|
||||
m_errorReporter.warning(
|
||||
_location,
|
||||
message.str(),
|
||||
SecondarySourceLocation().append(modelMessage.str(), SourceLocation{})
|
||||
.append(SMTEncoder::callStackMessage(callStack))
|
||||
.append(move(secondaryLocation))
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
message << ".";
|
||||
m_errorReporter.warning(_location, message.str(), secondaryLocation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case smt::CheckResult::UNSATISFIABLE:
|
||||
break;
|
||||
case smt::CheckResult::UNKNOWN:
|
||||
m_errorReporter.warning(_location, _description + " might happen here.", secondaryLocation);
|
||||
break;
|
||||
case smt::CheckResult::CONFLICTING:
|
||||
m_errorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
break;
|
||||
case smt::CheckResult::ERROR:
|
||||
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
||||
break;
|
||||
}
|
||||
|
||||
m_interface->pop();
|
||||
}
|
||||
|
||||
void BMC::checkBooleanNotConstant(
|
||||
Expression const& _condition,
|
||||
smt::Expression const& _constraints,
|
||||
smt::Expression const& _value,
|
||||
vector<SMTEncoder::CallStackEntry> const& _callStack,
|
||||
string const& _description
|
||||
)
|
||||
{
|
||||
// Do not check for const-ness if this is a constant.
|
||||
if (dynamic_cast<Literal const*>(&_condition))
|
||||
return;
|
||||
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(_constraints && _value);
|
||||
auto positiveResult = checkSatisfiable();
|
||||
m_interface->pop();
|
||||
|
||||
m_interface->push();
|
||||
m_interface->addAssertion(_constraints && !_value);
|
||||
auto negatedResult = checkSatisfiable();
|
||||
m_interface->pop();
|
||||
|
||||
if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
|
||||
m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
|
||||
else if (positiveResult == smt::CheckResult::CONFLICTING || negatedResult == smt::CheckResult::CONFLICTING)
|
||||
m_errorReporter.warning(_condition.location(), "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE)
|
||||
{
|
||||
// everything fine.
|
||||
}
|
||||
else if (positiveResult == smt::CheckResult::UNKNOWN || negatedResult == smt::CheckResult::UNKNOWN)
|
||||
{
|
||||
// can't do anything.
|
||||
}
|
||||
else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
|
||||
m_errorReporter.warning(_condition.location(), "Condition unreachable.", SMTEncoder::callStackMessage(_callStack));
|
||||
else
|
||||
{
|
||||
string value;
|
||||
if (positiveResult == smt::CheckResult::SATISFIABLE)
|
||||
{
|
||||
solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, "");
|
||||
value = "true";
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, "");
|
||||
solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
|
||||
value = "false";
|
||||
}
|
||||
m_errorReporter.warning(
|
||||
_condition.location(),
|
||||
boost::algorithm::replace_all_copy(_description, "$VALUE", value),
|
||||
SMTEncoder::callStackMessage(_callStack)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pair<smt::CheckResult, vector<string>>
|
||||
BMC::checkSatisfiableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
|
||||
{
|
||||
smt::CheckResult result;
|
||||
vector<string> values;
|
||||
try
|
||||
{
|
||||
tie(result, values) = m_interface->check(_expressionsToEvaluate);
|
||||
}
|
||||
catch (smt::SolverError const& _e)
|
||||
{
|
||||
string description("Error querying SMT solver");
|
||||
if (_e.comment())
|
||||
description += ": " + *_e.comment();
|
||||
m_errorReporter.warning(description);
|
||||
result = smt::CheckResult::ERROR;
|
||||
}
|
||||
|
||||
for (string& value: values)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse and re-format nicely
|
||||
value = formatNumberReadable(bigint(value));
|
||||
}
|
||||
catch (...) { }
|
||||
}
|
||||
|
||||
return make_pair(result, values);
|
||||
}
|
||||
|
||||
smt::CheckResult BMC::checkSatisfiable()
|
||||
{
|
||||
return checkSatisfiableAndGenerateModel({}).first;
|
||||
}
|
||||
|
185
libsolidity/formal/BMC.h
Normal file
185
libsolidity/formal/BMC.h
Normal file
@ -0,0 +1,185 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Class that implements an SMT-based Bounded Model Checker (BMC).
|
||||
* Traverses the AST such that:
|
||||
* - Loops are unrolled
|
||||
* - Internal function calls are inlined
|
||||
* Creates verification targets for:
|
||||
* - Underflow/Overflow
|
||||
* - Constant conditions
|
||||
* - Assertions
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <libsolidity/formal/EncodingContext.h>
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
#include <libsolidity/formal/SolverInterface.h>
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
struct SourceLocation;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class BMC: public SMTEncoder
|
||||
{
|
||||
public:
|
||||
BMC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter, std::map<h256, std::string> const& _smtlib2Responses);
|
||||
|
||||
void analyze(SourceUnit const& _sources, std::set<Expression const*> _safeAssertions);
|
||||
|
||||
/// This is used if the SMT solver is not directly linked into this binary.
|
||||
/// @returns a list of inputs to the SMT solver that were not part of the argument to
|
||||
/// the constructor.
|
||||
std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); }
|
||||
|
||||
/// @returns true if _funCall should be inlined, otherwise false.
|
||||
static bool shouldInlineFunctionCall(FunctionCall const& _funCall);
|
||||
|
||||
std::shared_ptr<smt::SolverInterface> solver() { return m_interface; }
|
||||
|
||||
private:
|
||||
/// AST visitors.
|
||||
/// Only nodes that lead to verification targets being built
|
||||
/// or checked are visited.
|
||||
//@{
|
||||
bool visit(ContractDefinition const& _node) override;
|
||||
void endVisit(ContractDefinition const& _node) override;
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
bool visit(IfStatement const& _node) override;
|
||||
bool visit(WhileStatement const& _node) override;
|
||||
bool visit(ForStatement const& _node) override;
|
||||
void endVisit(UnaryOperation const& _node) override;
|
||||
void endVisit(FunctionCall const& _node) override;
|
||||
//@}
|
||||
|
||||
/// Visitor helpers.
|
||||
//@{
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void visitRequire(FunctionCall const& _funCall);
|
||||
/// Visits the FunctionDefinition of the called function
|
||||
/// if available and inlines the return value.
|
||||
void inlineFunctionCall(FunctionCall const& _funCall);
|
||||
/// Creates an uninterpreted function call.
|
||||
void abstractFunctionCall(FunctionCall const& _funCall);
|
||||
/// Inlines if the function call is internal or external to `this`.
|
||||
/// Erases knowledge about state variables if external.
|
||||
void internalOrExternalFunctionCall(FunctionCall const& _funCall);
|
||||
|
||||
/// Creates underflow/overflow verification targets.
|
||||
std::pair<smt::Expression, smt::Expression> arithmeticOperation(
|
||||
Token _op,
|
||||
smt::Expression const& _left,
|
||||
smt::Expression const& _right,
|
||||
TypePointer const& _commonType,
|
||||
Expression const& _expression
|
||||
) override;
|
||||
|
||||
void resetStorageReferences();
|
||||
void reset();
|
||||
|
||||
std::pair<std::vector<smt::Expression>, std::vector<std::string>> modelExpressions();
|
||||
//@}
|
||||
|
||||
/// Verification targets.
|
||||
//@{
|
||||
struct VerificationTarget
|
||||
{
|
||||
enum class Type { ConstantCondition, Underflow, Overflow, UnderOverflow, DivByZero, Balance, Assert } type;
|
||||
smt::Expression value;
|
||||
smt::Expression constraints;
|
||||
Expression const* expression;
|
||||
std::vector<CallStackEntry> callStack;
|
||||
std::pair<std::vector<smt::Expression>, std::vector<std::string>> modelExpressions;
|
||||
};
|
||||
|
||||
void checkVerificationTargets(smt::Expression const& _constraints);
|
||||
void checkVerificationTarget(VerificationTarget& _target, smt::Expression const& _constraints = smt::Expression(true));
|
||||
void checkConstantCondition(VerificationTarget& _target);
|
||||
void checkUnderflow(VerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkOverflow(VerificationTarget& _target, smt::Expression const& _constraints);
|
||||
void checkDivByZero(VerificationTarget& _target);
|
||||
void checkBalance(VerificationTarget& _target);
|
||||
void checkAssert(VerificationTarget& _target);
|
||||
void addVerificationTarget(
|
||||
VerificationTarget::Type _type,
|
||||
smt::Expression const& _value,
|
||||
Expression const* _expression
|
||||
);
|
||||
//@}
|
||||
|
||||
/// Solver related.
|
||||
//@{
|
||||
/// Check that a condition can be satisfied.
|
||||
void checkCondition(
|
||||
smt::Expression _condition,
|
||||
std::vector<CallStackEntry> const& callStack,
|
||||
std::pair<std::vector<smt::Expression>, std::vector<std::string>> const& _modelExpressions,
|
||||
langutil::SourceLocation const& _location,
|
||||
std::string const& _description,
|
||||
std::string const& _additionalValueName = "",
|
||||
smt::Expression const* _additionalValue = nullptr
|
||||
);
|
||||
/// Checks that a boolean condition is not constant. Do not warn if the expression
|
||||
/// is a literal constant.
|
||||
/// @param _description the warning string, $VALUE will be replaced by the constant value.
|
||||
void checkBooleanNotConstant(
|
||||
Expression const& _condition,
|
||||
smt::Expression const& _constraints,
|
||||
smt::Expression const& _value,
|
||||
std::vector<CallStackEntry> const& _callStack,
|
||||
std::string const& _description
|
||||
);
|
||||
std::pair<smt::CheckResult, std::vector<std::string>>
|
||||
checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
|
||||
|
||||
smt::CheckResult checkSatisfiable();
|
||||
//@}
|
||||
|
||||
/// Flags used for better warning messages.
|
||||
bool m_loopExecutionHappened = false;
|
||||
bool m_externalFunctionCallHappened = false;
|
||||
|
||||
/// ErrorReporter that comes from CompilerStack.
|
||||
langutil::ErrorReporter& m_outerErrorReporter;
|
||||
|
||||
std::vector<VerificationTarget> m_verificationTargets;
|
||||
|
||||
/// Assertions that are known to be safe.
|
||||
std::set<Expression const*> m_safeAssertions;
|
||||
|
||||
std::shared_ptr<smt::SolverInterface> m_interface;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
297
libsolidity/formal/CHC.cpp
Normal file
297
libsolidity/formal/CHC.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/formal/CHC.h>
|
||||
|
||||
#ifdef HAVE_Z3
|
||||
#include <libsolidity/formal/Z3CHCInterface.h>
|
||||
#endif
|
||||
|
||||
#include <libsolidity/formal/SymbolicTypes.h>
|
||||
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
|
||||
CHC::CHC(smt::EncodingContext& _context, ErrorReporter& _errorReporter):
|
||||
SMTEncoder(_context),
|
||||
#ifdef HAVE_Z3
|
||||
m_interface(make_shared<smt::Z3CHCInterface>()),
|
||||
#endif
|
||||
m_outerErrorReporter(_errorReporter)
|
||||
{
|
||||
}
|
||||
|
||||
void CHC::analyze(SourceUnit const& _source)
|
||||
{
|
||||
solAssert(_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker), "");
|
||||
|
||||
#ifdef HAVE_Z3
|
||||
auto z3Interface = dynamic_pointer_cast<smt::Z3CHCInterface>(m_interface);
|
||||
solAssert(z3Interface, "");
|
||||
m_context.setSolver(z3Interface->z3Interface());
|
||||
m_context.clear();
|
||||
m_context.setAssertionAccumulation(false);
|
||||
m_variableUsage.setFunctionInlining(false);
|
||||
|
||||
_source.accept(*this);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CHC::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
if (!shouldVisit(_contract))
|
||||
return false;
|
||||
|
||||
reset();
|
||||
|
||||
if (!SMTEncoder::visit(_contract))
|
||||
return false;
|
||||
|
||||
m_stateVariables = _contract.stateVariablesIncludingInherited();
|
||||
|
||||
for (auto const& var: m_stateVariables)
|
||||
// SMT solvers do not support function types as arguments.
|
||||
if (var->type()->category() == Type::Category::Function)
|
||||
m_stateSorts.push_back(make_shared<smt::Sort>(smt::Kind::Int));
|
||||
else
|
||||
m_stateSorts.push_back(smt::smtSort(*var->type()));
|
||||
|
||||
string interfaceName = "interface_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
m_interfacePredicate = createBlock(interfaceSort(), interfaceName);
|
||||
|
||||
// TODO create static instances for Bool/Int sorts in SolverInterface.
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
auto errorFunctionSort = make_shared<smt::FunctionSort>(
|
||||
vector<smt::SortPointer>(),
|
||||
boolSort
|
||||
);
|
||||
m_errorPredicate = createBlock(errorFunctionSort, "error");
|
||||
|
||||
// If the contract has a constructor it is handled as a function.
|
||||
// Otherwise we zero-initialize all state vars.
|
||||
// TODO take into account state vars init values.
|
||||
if (!_contract.constructor())
|
||||
{
|
||||
string constructorName = "constructor_" + _contract.name() + "_" + to_string(_contract.id());
|
||||
m_constructorPredicate = createBlock(constructorSort(), constructorName);
|
||||
|
||||
for (auto const& var: m_stateVariables)
|
||||
{
|
||||
auto const& symbVar = m_context.variable(*var);
|
||||
symbVar->increaseIndex();
|
||||
m_interface->declareVariable(symbVar->currentName(), *symbVar->sort());
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CHC::endVisit(ContractDefinition const& _contract)
|
||||
{
|
||||
if (!shouldVisit(_contract))
|
||||
return;
|
||||
|
||||
auto errorAppl = (*m_errorPredicate)({});
|
||||
for (auto const& target: m_verificationTargets)
|
||||
if (query(errorAppl, target->location()))
|
||||
m_safeAssertions.insert(target);
|
||||
|
||||
SMTEncoder::endVisit(_contract);
|
||||
}
|
||||
|
||||
bool CHC::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!shouldVisit(_function))
|
||||
return false;
|
||||
|
||||
solAssert(!m_currentFunction, "Inlining internal function calls not yet implemented");
|
||||
m_currentFunction = &_function;
|
||||
|
||||
SMTEncoder::visit(*m_currentFunction);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CHC::endVisit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (!shouldVisit(_function))
|
||||
return;
|
||||
|
||||
solAssert(m_currentFunction == &_function, "Inlining internal function calls not yet implemented");
|
||||
m_currentFunction = nullptr;
|
||||
|
||||
SMTEncoder::endVisit(_function);
|
||||
}
|
||||
|
||||
bool CHC::visit(IfStatement const& _if)
|
||||
{
|
||||
solAssert(m_currentFunction, "");
|
||||
|
||||
SMTEncoder::visit(_if);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CHC::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, "");
|
||||
|
||||
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);
|
||||
|
||||
createReturnedExpressions(_funCall);
|
||||
}
|
||||
|
||||
void CHC::visitAssert(FunctionCall const&)
|
||||
{
|
||||
}
|
||||
|
||||
void CHC::reset()
|
||||
{
|
||||
m_stateSorts.clear();
|
||||
m_stateVariables.clear();
|
||||
m_verificationTargets.clear();
|
||||
m_safeAssertions.clear();
|
||||
}
|
||||
|
||||
bool CHC::shouldVisit(ContractDefinition const& _contract) const
|
||||
{
|
||||
if (
|
||||
_contract.isLibrary() ||
|
||||
_contract.isInterface()
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CHC::shouldVisit(FunctionDefinition const& _function) const
|
||||
{
|
||||
if (
|
||||
_function.isPublic() &&
|
||||
_function.isImplemented()
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
smt::SortPointer CHC::interfaceSort()
|
||||
{
|
||||
auto boolSort = make_shared<smt::Sort>(smt::Kind::Bool);
|
||||
return make_shared<smt::FunctionSort>(
|
||||
m_stateSorts,
|
||||
boolSort
|
||||
);
|
||||
}
|
||||
|
||||
smt::SortPointer CHC::functionSort(FunctionDefinition const& _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()),
|
||||
boolSort
|
||||
);
|
||||
}
|
||||
|
||||
unique_ptr<smt::SymbolicFunctionVariable> CHC::createBlock(smt::SortPointer _sort, string const& _name)
|
||||
{
|
||||
auto block = make_unique<smt::SymbolicFunctionVariable>(
|
||||
_sort,
|
||||
_name,
|
||||
m_context
|
||||
);
|
||||
m_interface->registerRelation(block->currentValue());
|
||||
return block;
|
||||
}
|
||||
|
||||
smt::Expression CHC::constructor()
|
||||
{
|
||||
solAssert(m_currentContract, "");
|
||||
|
||||
if (!m_currentContract->constructor())
|
||||
return (*m_constructorPredicate)({});
|
||||
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_currentContract->constructor()->parameters())
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return (*m_constructorPredicate)(paramExprs);
|
||||
}
|
||||
|
||||
smt::Expression CHC::interface()
|
||||
{
|
||||
vector<smt::Expression> paramExprs;
|
||||
for (auto const& var: m_stateVariables)
|
||||
paramExprs.push_back(m_context.variable(*var)->currentValue());
|
||||
return (*m_interfacePredicate)(paramExprs);
|
||||
}
|
||||
|
||||
smt::Expression CHC::error()
|
||||
{
|
||||
return (*m_errorPredicate)({});
|
||||
}
|
||||
|
||||
bool CHC::query(smt::Expression const& _query, langutil::SourceLocation const& _location)
|
||||
{
|
||||
smt::CheckResult result;
|
||||
vector<string> values;
|
||||
tie(result, values) = m_interface->query(_query);
|
||||
switch (result)
|
||||
{
|
||||
case smt::CheckResult::SATISFIABLE:
|
||||
break;
|
||||
case smt::CheckResult::UNSATISFIABLE:
|
||||
return true;
|
||||
case smt::CheckResult::UNKNOWN:
|
||||
break;
|
||||
case smt::CheckResult::CONFLICTING:
|
||||
m_outerErrorReporter.warning(_location, "At least two SMT solvers provided conflicting answers. Results might not be sound.");
|
||||
break;
|
||||
case smt::CheckResult::ERROR:
|
||||
m_outerErrorReporter.warning(_location, "Error trying to invoke SMT solver.");
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
145
libsolidity/formal/CHC.h
Normal file
145
libsolidity/formal/CHC.h
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Model checker based on Constrained Horn Clauses.
|
||||
*
|
||||
* A Solidity contract's CFG is encoded into a system of Horn clauses where
|
||||
* each block has a predicate and edges are rules.
|
||||
*
|
||||
* The entry block is the constructor which has no in-edges.
|
||||
* The constructor has one out-edge to an artificial block named _Interface_
|
||||
* which has in/out-edges from/to all public functions.
|
||||
*
|
||||
* Loop invariants for Interface -> Interface' are state invariants.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
|
||||
#include <libsolidity/formal/CHCSolverInterface.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class CHC: public SMTEncoder
|
||||
{
|
||||
public:
|
||||
CHC(smt::EncodingContext& _context, langutil::ErrorReporter& _errorReporter);
|
||||
|
||||
void analyze(SourceUnit const& _sources);
|
||||
|
||||
std::set<Expression const*> const& safeAssertions() const { return m_safeAssertions; }
|
||||
|
||||
private:
|
||||
/// Visitor functions.
|
||||
//@{
|
||||
bool visit(ContractDefinition const& _node) override;
|
||||
void endVisit(ContractDefinition const& _node) override;
|
||||
bool visit(FunctionDefinition const& _node) override;
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
bool visit(IfStatement const& _node) override;
|
||||
void endVisit(FunctionCall const& _node) override;
|
||||
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
//@}
|
||||
|
||||
/// Helpers.
|
||||
//@{
|
||||
void reset();
|
||||
bool shouldVisit(ContractDefinition const& _contract) const;
|
||||
bool shouldVisit(FunctionDefinition const& _function) const;
|
||||
//@}
|
||||
|
||||
/// Sort helpers.
|
||||
//@{
|
||||
smt::SortPointer constructorSort();
|
||||
smt::SortPointer interfaceSort();
|
||||
smt::SortPointer functionSort(FunctionDefinition const& _function);
|
||||
//@}
|
||||
|
||||
/// Predicate helpers.
|
||||
//@{
|
||||
/// @returns a new block of given _sort and _name.
|
||||
std::unique_ptr<smt::SymbolicFunctionVariable> createBlock(smt::SortPointer _sort, std::string const& _name);
|
||||
|
||||
/// Constructor predicate over current variables.
|
||||
smt::Expression constructor();
|
||||
/// Interface predicate over current variables.
|
||||
smt::Expression interface();
|
||||
/// Error predicate over current variables.
|
||||
smt::Expression error();
|
||||
//@}
|
||||
|
||||
/// Solver related.
|
||||
//@{
|
||||
/// @returns true if query is unsatisfiable (safe).
|
||||
bool query(smt::Expression const& _query, langutil::SourceLocation const& _location);
|
||||
//@}
|
||||
|
||||
/// Predicates.
|
||||
//@{
|
||||
/// Constructor predicate.
|
||||
/// Default constructor sets state vars to 0.
|
||||
std::unique_ptr<smt::SymbolicVariable> m_constructorPredicate;
|
||||
|
||||
/// Artificial Interface predicate.
|
||||
/// Single entry block for all functions.
|
||||
std::unique_ptr<smt::SymbolicVariable> m_interfacePredicate;
|
||||
|
||||
/// Artificial Error predicate.
|
||||
/// Single error block for all assertions.
|
||||
std::unique_ptr<smt::SymbolicVariable> m_errorPredicate;
|
||||
//@}
|
||||
|
||||
/// Variables.
|
||||
//@{
|
||||
/// State variables sorts.
|
||||
/// Used by all predicates.
|
||||
std::vector<smt::SortPointer> m_stateSorts;
|
||||
/// State variables.
|
||||
/// Used to create all predicates.
|
||||
std::vector<VariableDeclaration const*> m_stateVariables;
|
||||
//@}
|
||||
|
||||
/// Verification targets.
|
||||
//@{
|
||||
std::vector<Expression const*> m_verificationTargets;
|
||||
|
||||
/// Assertions proven safe.
|
||||
std::set<Expression const*> m_safeAssertions;
|
||||
//@}
|
||||
|
||||
/// Control-flow.
|
||||
//@{
|
||||
FunctionDefinition const* m_currentFunction = nullptr;
|
||||
//@}
|
||||
|
||||
/// CHC solver.
|
||||
std::shared_ptr<smt::CHCSolverInterface> m_interface;
|
||||
|
||||
/// ErrorReporter that comes from CompilerStack.
|
||||
langutil::ErrorReporter& m_outerErrorReporter;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
56
libsolidity/formal/CHCSolverInterface.h
Normal file
56
libsolidity/formal/CHCSolverInterface.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for constrained Horn solvers.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/SolverInterface.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
class CHCSolverInterface
|
||||
{
|
||||
public:
|
||||
virtual ~CHCSolverInterface() = default;
|
||||
|
||||
virtual void declareVariable(std::string const& _name, Sort const& _sort) = 0;
|
||||
|
||||
/// Takes a function declaration as a relation.
|
||||
virtual void registerRelation(Expression const& _expr) = 0;
|
||||
|
||||
/// Takes an implication and adds as rule.
|
||||
/// Needs to bound all vars as universally quantified.
|
||||
virtual void addRule(Expression const& _expr, std::string const& _name) = 0;
|
||||
|
||||
/// Takes a function application and checks
|
||||
/// for reachability.
|
||||
virtual std::pair<CheckResult, std::vector<std::string>> query(
|
||||
Expression const& _expr
|
||||
) = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -23,15 +23,14 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
EncodingContext::EncodingContext(std::shared_ptr<SolverInterface> _solver):
|
||||
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *_solver)),
|
||||
m_solver(_solver)
|
||||
EncodingContext::EncodingContext():
|
||||
m_thisAddress(make_unique<SymbolicAddressVariable>("this", *this))
|
||||
{
|
||||
auto sort = make_shared<ArraySort>(
|
||||
make_shared<Sort>(Kind::Int),
|
||||
make_shared<Sort>(Kind::Int)
|
||||
);
|
||||
m_balances = make_unique<SymbolicVariable>(sort, "balances", *m_solver);
|
||||
m_balances = make_unique<SymbolicVariable>(sort, "balances", *this);
|
||||
}
|
||||
|
||||
void EncodingContext::reset()
|
||||
@ -39,11 +38,17 @@ void EncodingContext::reset()
|
||||
resetAllVariables();
|
||||
m_expressions.clear();
|
||||
m_globalContext.clear();
|
||||
m_thisAddress->increaseIndex();
|
||||
m_balances->increaseIndex();
|
||||
m_thisAddress->resetIndex();
|
||||
m_balances->resetIndex();
|
||||
m_assertions.clear();
|
||||
}
|
||||
|
||||
void EncodingContext::clear()
|
||||
{
|
||||
m_variables.clear();
|
||||
reset();
|
||||
}
|
||||
|
||||
/// Variables.
|
||||
|
||||
shared_ptr<SymbolicVariable> EncodingContext::variable(solidity::VariableDeclaration const& _varDecl)
|
||||
@ -56,7 +61,7 @@ bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDe
|
||||
{
|
||||
solAssert(!knownVariable(_varDecl), "");
|
||||
auto const& type = _varDecl.type();
|
||||
auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_solver);
|
||||
auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *this);
|
||||
m_variables.emplace(&_varDecl, result.second);
|
||||
return result.first;
|
||||
}
|
||||
@ -106,7 +111,7 @@ void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl)
|
||||
|
||||
void EncodingContext::setZeroValue(SymbolicVariable& _variable)
|
||||
{
|
||||
setSymbolicZeroValue(_variable, *m_solver);
|
||||
setSymbolicZeroValue(_variable, *this);
|
||||
}
|
||||
|
||||
void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl)
|
||||
@ -117,7 +122,7 @@ void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl
|
||||
|
||||
void EncodingContext::setUnknownValue(SymbolicVariable& _variable)
|
||||
{
|
||||
setSymbolicUnknownValue(_variable, *m_solver);
|
||||
setSymbolicUnknownValue(_variable, *this);
|
||||
}
|
||||
|
||||
/// Expressions
|
||||
@ -144,7 +149,7 @@ bool EncodingContext::createExpression(solidity::Expression const& _e, shared_pt
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_solver);
|
||||
auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *this);
|
||||
m_expressions.emplace(&_e, result.second);
|
||||
return result.first;
|
||||
}
|
||||
@ -166,7 +171,7 @@ shared_ptr<SymbolicVariable> EncodingContext::globalSymbol(string const& _name)
|
||||
bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr)
|
||||
{
|
||||
solAssert(!knownGlobalSymbol(_name), "");
|
||||
auto result = newSymbolicVariable(*_expr.annotation().type, _name, *m_solver);
|
||||
auto result = newSymbolicVariable(*_expr.annotation().type, _name, *this);
|
||||
m_globalContext.emplace(_name, result.second);
|
||||
setUnknownValue(*result.second);
|
||||
return result.first;
|
||||
@ -208,7 +213,7 @@ void EncodingContext::transfer(Expression _from, Expression _to, Expression _val
|
||||
m_balances->valueAtIndex(indexBefore),
|
||||
m_balances->valueAtIndex(indexAfter)
|
||||
);
|
||||
m_solver->addAssertion(m_balances->currentValue() == newBalances);
|
||||
addAssertion(m_balances->currentValue() == newBalances);
|
||||
}
|
||||
|
||||
/// Solver.
|
||||
@ -223,7 +228,10 @@ Expression EncodingContext::assertions()
|
||||
|
||||
void EncodingContext::pushSolver()
|
||||
{
|
||||
m_assertions.push_back(assertions());
|
||||
if (m_accumulateAssertions)
|
||||
m_assertions.push_back(assertions());
|
||||
else
|
||||
m_assertions.push_back(smt::Expression(true));
|
||||
}
|
||||
|
||||
void EncodingContext::popSolver()
|
||||
@ -250,5 +258,5 @@ void EncodingContext::addBalance(Expression _address, Expression _value)
|
||||
balance(_address) + move(_value)
|
||||
);
|
||||
m_balances->increaseIndex();
|
||||
m_solver->addAssertion(newBalances == m_balances->currentValue());
|
||||
addAssertion(newBalances == m_balances->currentValue());
|
||||
}
|
||||
|
@ -36,10 +36,33 @@ namespace smt
|
||||
class EncodingContext
|
||||
{
|
||||
public:
|
||||
EncodingContext(std::shared_ptr<SolverInterface> _solver);
|
||||
EncodingContext();
|
||||
|
||||
/// Resets the entire context.
|
||||
/// Resets the entire context except for symbolic variables which stay
|
||||
/// alive because of state variables and inlined function calls.
|
||||
/// To be used in the beginning of a root function visit.
|
||||
void reset();
|
||||
/// Clears the entire context, erasing everything.
|
||||
/// To be used before a model checking engine starts.
|
||||
void clear();
|
||||
|
||||
/// Sets the current solver used by the current engine for
|
||||
/// SMT variable declaration.
|
||||
void setSolver(std::shared_ptr<SolverInterface> _solver)
|
||||
{
|
||||
solAssert(_solver, "");
|
||||
m_solver = _solver;
|
||||
}
|
||||
|
||||
/// Sets whether the context should conjoin assertions in the assertion stack.
|
||||
void setAssertionAccumulation(bool _acc) { m_accumulateAssertions = _acc; }
|
||||
|
||||
/// Forwards variable creation to the solver.
|
||||
Expression newVariable(std::string _name, SortPointer _sort)
|
||||
{
|
||||
solAssert(m_solver, "");
|
||||
return m_solver->newVariable(move(_name), move(_sort));
|
||||
}
|
||||
|
||||
/// Variables.
|
||||
//@{
|
||||
@ -121,7 +144,11 @@ public:
|
||||
void pushSolver();
|
||||
void popSolver();
|
||||
void addAssertion(Expression const& _e);
|
||||
std::shared_ptr<SolverInterface> solver() { return m_solver; }
|
||||
std::shared_ptr<SolverInterface> solver()
|
||||
{
|
||||
solAssert(m_solver, "");
|
||||
return m_solver;
|
||||
}
|
||||
//@}
|
||||
|
||||
private:
|
||||
@ -154,6 +181,9 @@ private:
|
||||
|
||||
/// Assertion stack.
|
||||
std::vector<Expression> m_assertions;
|
||||
|
||||
/// Whether to conjoin assertions in the assertion stack.
|
||||
bool m_accumulateAssertions = true;
|
||||
//@}
|
||||
};
|
||||
|
||||
|
44
libsolidity/formal/ModelChecker.cpp
Normal file
44
libsolidity/formal/ModelChecker.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/formal/ModelChecker.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
|
||||
ModelChecker::ModelChecker(ErrorReporter& _errorReporter, map<h256, string> const& _smtlib2Responses):
|
||||
m_bmc(m_context, _errorReporter, _smtlib2Responses),
|
||||
m_chc(m_context, _errorReporter),
|
||||
m_context()
|
||||
{
|
||||
}
|
||||
|
||||
void ModelChecker::analyze(SourceUnit const& _source)
|
||||
{
|
||||
if (!_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
|
||||
return;
|
||||
|
||||
m_chc.analyze(_source);
|
||||
m_bmc.analyze(_source, m_chc.safeAssertions());
|
||||
}
|
||||
|
||||
vector<string> ModelChecker::unhandledQueries()
|
||||
{
|
||||
return m_bmc.unhandledQueries();
|
||||
}
|
67
libsolidity/formal/ModelChecker.h
Normal file
67
libsolidity/formal/ModelChecker.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Entry point to the model checking engines.
|
||||
* The goal of this class is to make different
|
||||
* engines share knowledge to boost their proving power.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/BMC.h>
|
||||
#include <libsolidity/formal/CHC.h>
|
||||
#include <libsolidity/formal/EncodingContext.h>
|
||||
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
struct SourceLocation;
|
||||
}
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class ModelChecker
|
||||
{
|
||||
public:
|
||||
ModelChecker(langutil::ErrorReporter& _errorReporter, std::map<h256, std::string> const& _smtlib2Responses);
|
||||
|
||||
void analyze(SourceUnit const& _sources);
|
||||
|
||||
/// This is used if the SMT solver is not directly linked into this binary.
|
||||
/// @returns a list of inputs to the SMT solver that were not part of the argument to
|
||||
/// the constructor.
|
||||
std::vector<std::string> unhandledQueries();
|
||||
|
||||
private:
|
||||
/// Bounded Model Checker engine.
|
||||
BMC m_bmc;
|
||||
|
||||
/// Constrained Horn Clauses engine.
|
||||
CHC m_chc;
|
||||
|
||||
/// Stores the context of the encoding.
|
||||
smt::EncodingContext m_context;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -14,19 +14,22 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Encodes Solidity into SMT expressions without creating
|
||||
* any verification targets.
|
||||
* Also implements the SSA scheme for branches.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <libsolidity/formal/EncodingContext.h>
|
||||
#include <libsolidity/formal/SMTPortfolio.h>
|
||||
#include <libsolidity/formal/SymbolicVariables.h>
|
||||
#include <libsolidity/formal/VariableUsage.h>
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
#include <libsolidity/interface/ReadFile.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -43,25 +46,19 @@ namespace dev
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class SMTChecker: private ASTConstVisitor
|
||||
class SMTEncoder: public ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
SMTChecker(langutil::ErrorReporter& _errorReporter, std::map<h256, std::string> const& _smtlib2Responses);
|
||||
SMTEncoder(smt::EncodingContext& _context);
|
||||
|
||||
void analyze(SourceUnit const& _sources, std::shared_ptr<langutil::Scanner> const& _scanner);
|
||||
|
||||
/// This is used if the SMT solver is not directly linked into this binary.
|
||||
/// @returns a list of inputs to the SMT solver that were not part of the argument to
|
||||
/// the constructor.
|
||||
std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); }
|
||||
|
||||
/// @returns the FunctionDefinition of a called function if possible and should inline,
|
||||
/// otherwise nullptr.
|
||||
static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall);
|
||||
/// @returns the leftmost identifier in a multi-d IndexAccess.
|
||||
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
|
||||
|
||||
private:
|
||||
/// @returns the FunctionDefinition of a FunctionCall
|
||||
/// if possible or nullptr.
|
||||
static FunctionDefinition const* functionCallToDefinition(FunctionCall const& _funCall);
|
||||
|
||||
protected:
|
||||
// TODO: Check that we do not have concurrent reads and writes to a variable,
|
||||
// because the order of expression evaluation is undefined
|
||||
// TODO: or just force a certain order, but people might have a different idea about that.
|
||||
@ -74,8 +71,8 @@ private:
|
||||
void endVisit(FunctionDefinition const& _node) override;
|
||||
bool visit(PlaceholderStatement const& _node) override;
|
||||
bool visit(IfStatement const& _node) override;
|
||||
bool visit(WhileStatement const& _node) override;
|
||||
bool visit(ForStatement const& _node) override;
|
||||
bool visit(WhileStatement const&) override { return false; }
|
||||
bool visit(ForStatement const&) override { return false; }
|
||||
void endVisit(VariableDeclarationStatement const& _node) override;
|
||||
void endVisit(Assignment const& _node) override;
|
||||
void endVisit(TupleExpression const& _node) override;
|
||||
@ -95,31 +92,24 @@ private:
|
||||
/// Symbolic _expr is the rational literal.
|
||||
bool shortcutRationalNumber(Expression const& _expr);
|
||||
void arithmeticOperation(BinaryOperation const& _op);
|
||||
/// @returns _op(_left, _right).
|
||||
/// @returns _op(_left, _right) with and without modular arithmetic.
|
||||
/// Used by the function above, compound assignments and
|
||||
/// unary increment/decrement.
|
||||
smt::Expression arithmeticOperation(
|
||||
virtual std::pair<smt::Expression, smt::Expression> arithmeticOperation(
|
||||
Token _op,
|
||||
smt::Expression const& _left,
|
||||
smt::Expression const& _right,
|
||||
TypePointer const& _commonType,
|
||||
langutil::SourceLocation const& _location
|
||||
Expression const& _expression
|
||||
);
|
||||
void compareOperation(BinaryOperation const& _op);
|
||||
void booleanOperation(BinaryOperation const& _op);
|
||||
|
||||
void initFunction(FunctionDefinition const& _function);
|
||||
void visitAssert(FunctionCall const& _funCall);
|
||||
void visitRequire(FunctionCall const& _funCall);
|
||||
void visitGasLeft(FunctionCall const& _funCall);
|
||||
void visitTypeConversion(FunctionCall const& _funCall);
|
||||
/// Visits the FunctionDefinition of the called function
|
||||
/// if available and inlines the return value.
|
||||
void inlineFunctionCall(FunctionCall const& _funCall);
|
||||
/// Creates an uninterpreted function call.
|
||||
void abstractFunctionCall(FunctionCall const& _funCall);
|
||||
/// Inlines if the function call is internal or external to `this`.
|
||||
/// Erases knowledge about state variables if external.
|
||||
void internalOrExternalFunctionCall(FunctionCall const& _funCall);
|
||||
void visitFunctionIdentifier(Identifier const& _identifier);
|
||||
|
||||
/// Encodes a modifier or function body according to the modifier
|
||||
@ -140,9 +130,9 @@ private:
|
||||
/// of rounding for signed division.
|
||||
smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
|
||||
|
||||
void assignment(VariableDeclaration const& _variable, Expression const& _value, langutil::SourceLocation const& _location);
|
||||
void assignment(VariableDeclaration const& _variable, Expression const& _value);
|
||||
/// Handles assignments to variables of different types.
|
||||
void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, langutil::SourceLocation const& _location);
|
||||
void assignment(VariableDeclaration const& _variable, smt::Expression const& _value);
|
||||
/// Handles assignments between generic expressions.
|
||||
/// Will also be used for assignments of tuple components.
|
||||
void assignment(
|
||||
@ -163,62 +153,12 @@ private:
|
||||
VariableIndices visitBranch(ASTNode const* _statement, smt::Expression const* _condition = nullptr);
|
||||
VariableIndices visitBranch(ASTNode const* _statement, smt::Expression _condition);
|
||||
|
||||
/// Check that a condition can be satisfied.
|
||||
void checkCondition(
|
||||
smt::Expression _condition,
|
||||
langutil::SourceLocation const& _location,
|
||||
std::string const& _description,
|
||||
std::string const& _additionalValueName = "",
|
||||
smt::Expression const* _additionalValue = nullptr
|
||||
);
|
||||
/// Checks that a boolean condition is not constant. Do not warn if the expression
|
||||
/// is a literal constant.
|
||||
/// @param _description the warning string, $VALUE will be replaced by the constant value.
|
||||
void checkBooleanNotConstant(
|
||||
Expression const& _condition,
|
||||
std::string const& _description
|
||||
);
|
||||
|
||||
using CallStackEntry = std::pair<CallableDeclaration const*, ASTNode const*>;
|
||||
|
||||
struct OverflowTarget
|
||||
{
|
||||
enum class Type { Underflow, Overflow, All } type;
|
||||
TypePointer intType;
|
||||
smt::Expression value;
|
||||
smt::Expression path;
|
||||
langutil::SourceLocation const& location;
|
||||
std::vector<CallStackEntry> callStack;
|
||||
|
||||
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<CallStackEntry> _callStack):
|
||||
type(_type),
|
||||
intType(_intType),
|
||||
value(_value),
|
||||
path(_path),
|
||||
location(_location),
|
||||
callStack(move(_callStack))
|
||||
{
|
||||
solAssert(dynamic_cast<IntegerType const*>(intType), "");
|
||||
}
|
||||
};
|
||||
|
||||
/// Checks that the value is in the range given by the type.
|
||||
void checkUnderflow(OverflowTarget& _target);
|
||||
void checkOverflow(OverflowTarget& _target);
|
||||
/// Calls the functions above for all elements in m_overflowTargets accordingly.
|
||||
void checkUnderOverflow();
|
||||
/// Adds an overflow target for lazy check at the end of the function.
|
||||
void addOverflowTarget(OverflowTarget::Type _type, TypePointer _intType, smt::Expression _value, langutil::SourceLocation const& _location);
|
||||
|
||||
std::pair<smt::CheckResult, std::vector<std::string>>
|
||||
checkSatisfiableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
|
||||
|
||||
smt::CheckResult checkSatisfiable();
|
||||
|
||||
void initializeStateVariables(ContractDefinition const& _contract);
|
||||
void initializeLocalVariables(FunctionDefinition const& _function);
|
||||
void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs);
|
||||
void resetStateVariables();
|
||||
void resetStorageReferences();
|
||||
/// @returns the type without storage pointer information if it has it.
|
||||
TypePointer typeWithoutPointer(TypePointer const& _type);
|
||||
|
||||
@ -235,8 +175,10 @@ private:
|
||||
/// @returns an expression denoting the value of the variable declared in @a _decl
|
||||
/// at the given index. Does not ensure that this index exists.
|
||||
smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index);
|
||||
/// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
|
||||
smt::Expression expr(Expression const& _e);
|
||||
/// Returns the expression corresponding to the AST node.
|
||||
/// If _targetType is not null apply conversion.
|
||||
/// Throws if the expression does not exist.
|
||||
smt::Expression expr(Expression const& _e, TypePointer _targetType = nullptr);
|
||||
/// Creates the expression (value can be arbitrary)
|
||||
void createExpr(Expression const& _e);
|
||||
/// Creates the expression and sets its value.
|
||||
@ -248,14 +190,12 @@ private:
|
||||
void popPathCondition();
|
||||
/// Returns the conjunction of all path conditions or True if empty
|
||||
smt::Expression currentPathConditions();
|
||||
/// Returns the current callstack. Used for models.
|
||||
langutil::SecondarySourceLocation currentCallStack();
|
||||
/// @returns a human-readable call stack. Used for models.
|
||||
langutil::SecondarySourceLocation callStackMessage(std::vector<CallStackEntry> const& _callStack);
|
||||
/// Copies and pops the last called node.
|
||||
CallStackEntry popCallStack();
|
||||
/// Adds (_definition, _node) to the callstack.
|
||||
void pushCallStack(CallStackEntry _entry);
|
||||
/// Conjoin the current path conditions with the given parameter and add to the solver
|
||||
void addPathConjoinedExpression(smt::Expression const& _e);
|
||||
/// Add to the solver: the given expression implied by the current path conditions
|
||||
void addPathImpliedExpression(smt::Expression const& _e);
|
||||
|
||||
@ -270,11 +210,15 @@ private:
|
||||
/// @returns the VariableDeclaration referenced by an Identifier or nullptr.
|
||||
VariableDeclaration const* identifierToVariable(Expression const& _expr);
|
||||
|
||||
std::shared_ptr<smt::SolverInterface> m_interface;
|
||||
/// Creates symbolic expressions for the returned values
|
||||
/// and set them as the components of the symbolic tuple.
|
||||
void createReturnedExpressions(FunctionCall const& _funCall);
|
||||
|
||||
/// @returns a note to be added to warnings.
|
||||
std::string extraComment();
|
||||
|
||||
smt::VariableUsage m_variableUsage;
|
||||
bool m_loopExecutionHappened = false;
|
||||
bool m_arrayAssignmentHappened = false;
|
||||
bool m_externalFunctionCallHappened = false;
|
||||
// True if the "No SMT solver available" warning was already created.
|
||||
bool m_noSolverWarning = false;
|
||||
|
||||
@ -283,14 +227,11 @@ private:
|
||||
/// Used to retrieve models.
|
||||
std::set<Expression const*> m_uninterpretedTerms;
|
||||
std::vector<smt::Expression> m_pathConditions;
|
||||
/// ErrorReporter that comes from CompilerStack.
|
||||
langutil::ErrorReporter& m_errorReporterReference;
|
||||
/// Local SMTChecker ErrorReporter.
|
||||
/// Local SMTEncoder ErrorReporter.
|
||||
/// This is necessary to show the "No SMT solver available"
|
||||
/// warning before the others in case it's needed.
|
||||
langutil::ErrorReporter m_errorReporter;
|
||||
langutil::ErrorList m_smtErrors;
|
||||
std::shared_ptr<langutil::Scanner> m_scanner;
|
||||
|
||||
/// Stores the current function/modifier call/invocation path.
|
||||
std::vector<CallStackEntry> m_callStack;
|
||||
@ -300,16 +241,16 @@ private:
|
||||
/// Returns true if _funDef was already visited.
|
||||
bool visitedFunction(FunctionDefinition const* _funDef);
|
||||
|
||||
std::vector<OverflowTarget> m_overflowTargets;
|
||||
|
||||
/// Depth of visit to modifiers.
|
||||
/// When m_modifierDepth == #modifiers the function can be visited
|
||||
/// when placeholder is visited.
|
||||
/// Needs to be a stack because of function calls.
|
||||
std::vector<int> m_modifierDepthStack;
|
||||
|
||||
ContractDefinition const* m_currentContract = nullptr;
|
||||
|
||||
/// Stores the context of the encoding.
|
||||
smt::EncodingContext m_context;
|
||||
smt::EncodingContext& m_context;
|
||||
};
|
||||
|
||||
}
|
@ -64,6 +64,13 @@ SortPointer smtSort(solidity::Type const& _type)
|
||||
solAssert(mapType, "");
|
||||
return make_shared<ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
|
||||
}
|
||||
else if (isStringLiteral(_type.category()))
|
||||
{
|
||||
auto stringLitType = dynamic_cast<solidity::StringLiteralType const*>(&_type);
|
||||
solAssert(stringLitType, "");
|
||||
auto intSort = make_shared<Sort>(Kind::Int);
|
||||
return make_shared<ArraySort>(intSort, intSort);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(isArray(_type.category()), "");
|
||||
@ -118,7 +125,7 @@ bool isSupportedTypeDeclaration(solidity::Type::Category _category)
|
||||
pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
solidity::Type const& _type,
|
||||
std::string const& _uniqueName,
|
||||
SolverInterface& _solver
|
||||
EncodingContext& _context
|
||||
)
|
||||
{
|
||||
bool abstract = false;
|
||||
@ -127,39 +134,44 @@ pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
|
||||
if (!isSupportedTypeDeclaration(_type))
|
||||
{
|
||||
abstract = true;
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), type, _uniqueName, _context);
|
||||
}
|
||||
else if (isBool(_type.category()))
|
||||
var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _context);
|
||||
else if (isFunction(_type.category()))
|
||||
var = make_shared<SymbolicFunctionVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicFunctionVariable>(type, _uniqueName, _context);
|
||||
else if (isInteger(_type.category()))
|
||||
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
else if (isFixedBytes(_type.category()))
|
||||
{
|
||||
auto fixedBytesType = dynamic_cast<solidity::FixedBytesType const*>(type);
|
||||
solAssert(fixedBytesType, "");
|
||||
var = make_shared<SymbolicFixedBytesVariable>(fixedBytesType->numBytes(), _uniqueName, _solver);
|
||||
var = make_shared<SymbolicFixedBytesVariable>(type, fixedBytesType->numBytes(), _uniqueName, _context);
|
||||
}
|
||||
else if (isAddress(_type.category()) || isContract(_type.category()))
|
||||
var = make_shared<SymbolicAddressVariable>(_uniqueName, _solver);
|
||||
var = make_shared<SymbolicAddressVariable>(_uniqueName, _context);
|
||||
else if (isEnum(_type.category()))
|
||||
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _context);
|
||||
else if (isRational(_type.category()))
|
||||
{
|
||||
auto rational = dynamic_cast<solidity::RationalNumberType const*>(&_type);
|
||||
solAssert(rational, "");
|
||||
if (rational->isFractional())
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), type, _uniqueName, _context);
|
||||
else
|
||||
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicIntVariable>(type, type, _uniqueName, _context);
|
||||
}
|
||||
else if (isMapping(_type.category()))
|
||||
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _context);
|
||||
else if (isArray(_type.category()))
|
||||
var = make_shared<SymbolicArrayVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicArrayVariable>(type, type, _uniqueName, _context);
|
||||
else if (isTuple(_type.category()))
|
||||
var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _solver);
|
||||
var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _context);
|
||||
else if (isStringLiteral(_type.category()))
|
||||
{
|
||||
auto stringType = TypeProvider::stringMemory();
|
||||
var = make_shared<SymbolicArrayVariable>(stringType, type, _uniqueName, _context);
|
||||
}
|
||||
else
|
||||
solAssert(false, "");
|
||||
return make_pair(abstract, var);
|
||||
@ -232,7 +244,8 @@ bool isMapping(solidity::Type::Category _category)
|
||||
|
||||
bool isArray(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == solidity::Type::Category::Array;
|
||||
return _category == solidity::Type::Category::Array ||
|
||||
_category == solidity::Type::Category::StringLiteral;
|
||||
}
|
||||
|
||||
bool isTuple(solidity::Type::Category _category)
|
||||
@ -240,6 +253,11 @@ bool isTuple(solidity::Type::Category _category)
|
||||
return _category == solidity::Type::Category::Tuple;
|
||||
}
|
||||
|
||||
bool isStringLiteral(solidity::Type::Category _category)
|
||||
{
|
||||
return _category == solidity::Type::Category::StringLiteral;
|
||||
}
|
||||
|
||||
Expression minValue(solidity::IntegerType const& _type)
|
||||
{
|
||||
return Expression(_type.minValue());
|
||||
@ -250,41 +268,41 @@ Expression maxValue(solidity::IntegerType const& _type)
|
||||
return Expression(_type.maxValue());
|
||||
}
|
||||
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface)
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context)
|
||||
{
|
||||
setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _interface);
|
||||
setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _context);
|
||||
}
|
||||
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isInteger(_type->category()))
|
||||
_interface.addAssertion(_expr == 0);
|
||||
if (isNumber(_type->category()))
|
||||
_context.addAssertion(_expr == 0);
|
||||
else if (isBool(_type->category()))
|
||||
_interface.addAssertion(_expr == Expression(false));
|
||||
_context.addAssertion(_expr == Expression(false));
|
||||
}
|
||||
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface)
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context)
|
||||
{
|
||||
setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _interface);
|
||||
setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _context);
|
||||
}
|
||||
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context)
|
||||
{
|
||||
solAssert(_type, "");
|
||||
if (isEnum(_type->category()))
|
||||
{
|
||||
auto enumType = dynamic_cast<solidity::EnumType const*>(_type);
|
||||
solAssert(enumType, "");
|
||||
_interface.addAssertion(_expr >= 0);
|
||||
_interface.addAssertion(_expr < enumType->numberOfMembers());
|
||||
_context.addAssertion(_expr >= 0);
|
||||
_context.addAssertion(_expr < enumType->numberOfMembers());
|
||||
}
|
||||
else if (isInteger(_type->category()))
|
||||
{
|
||||
auto intType = dynamic_cast<solidity::IntegerType const*>(_type);
|
||||
solAssert(intType, "");
|
||||
_interface.addAssertion(_expr >= minValue(*intType));
|
||||
_interface.addAssertion(_expr <= maxValue(*intType));
|
||||
_context.addAssertion(_expr >= minValue(*intType));
|
||||
_context.addAssertion(_expr <= maxValue(*intType));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/SolverInterface.h>
|
||||
#include <libsolidity/formal/EncodingContext.h>
|
||||
#include <libsolidity/formal/SymbolicVariables.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/Types.h>
|
||||
@ -54,19 +54,20 @@ bool isFunction(solidity::Type::Category _category);
|
||||
bool isMapping(solidity::Type::Category _category);
|
||||
bool isArray(solidity::Type::Category _category);
|
||||
bool isTuple(solidity::Type::Category _category);
|
||||
bool isStringLiteral(solidity::Type::Category _category);
|
||||
|
||||
/// Returns a new symbolic variable, according to _type.
|
||||
/// Also returns whether the type is abstract or not,
|
||||
/// which is true for unsupported types.
|
||||
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, SolverInterface& _solver);
|
||||
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, EncodingContext& _context);
|
||||
|
||||
Expression minValue(solidity::IntegerType const& _type);
|
||||
Expression maxValue(solidity::IntegerType const& _type);
|
||||
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface);
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface);
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
|
||||
void setSymbolicZeroValue(SymbolicVariable const& _variable, EncodingContext& _context);
|
||||
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context);
|
||||
void setSymbolicUnknownValue(SymbolicVariable const& _variable, EncodingContext& _context);
|
||||
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, EncodingContext& _context);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -27,12 +27,14 @@ using namespace dev::solidity::smt;
|
||||
|
||||
SymbolicVariable::SymbolicVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalType,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
m_type(move(_type)),
|
||||
m_type(_type),
|
||||
m_originalType(_originalType),
|
||||
m_uniqueName(move(_uniqueName)),
|
||||
m_interface(_interface),
|
||||
m_context(_context),
|
||||
m_ssa(make_unique<SSAVariable>())
|
||||
{
|
||||
solAssert(m_type, "");
|
||||
@ -43,17 +45,17 @@ SymbolicVariable::SymbolicVariable(
|
||||
SymbolicVariable::SymbolicVariable(
|
||||
SortPointer _sort,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
m_sort(move(_sort)),
|
||||
m_uniqueName(move(_uniqueName)),
|
||||
m_interface(_interface),
|
||||
m_context(_context),
|
||||
m_ssa(make_unique<SSAVariable>())
|
||||
{
|
||||
solAssert(m_sort, "");
|
||||
}
|
||||
|
||||
Expression SymbolicVariable::currentValue() const
|
||||
Expression SymbolicVariable::currentValue(solidity::TypePointer const&) const
|
||||
{
|
||||
return valueAtIndex(m_ssa->index());
|
||||
}
|
||||
@ -65,7 +67,12 @@ string SymbolicVariable::currentName() const
|
||||
|
||||
Expression SymbolicVariable::valueAtIndex(int _index) const
|
||||
{
|
||||
return m_interface.newVariable(uniqueSymbol(_index), m_sort);
|
||||
return m_context.newVariable(uniqueSymbol(_index), m_sort);
|
||||
}
|
||||
|
||||
string SymbolicVariable::nameAtIndex(int _index) const
|
||||
{
|
||||
return uniqueSymbol(_index);
|
||||
}
|
||||
|
||||
string SymbolicVariable::uniqueSymbol(unsigned _index) const
|
||||
@ -73,6 +80,12 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const
|
||||
return m_uniqueName + "_" + to_string(_index);
|
||||
}
|
||||
|
||||
Expression SymbolicVariable::resetIndex()
|
||||
{
|
||||
m_ssa->resetIndex();
|
||||
return currentValue();
|
||||
}
|
||||
|
||||
Expression SymbolicVariable::increaseIndex()
|
||||
{
|
||||
++(*m_ssa);
|
||||
@ -82,54 +95,67 @@ Expression SymbolicVariable::increaseIndex()
|
||||
SymbolicBoolVariable::SymbolicBoolVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(m_type->category() == solidity::Type::Category::Bool, "");
|
||||
}
|
||||
|
||||
SymbolicIntVariable::SymbolicIntVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalType,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _originalType, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isNumber(m_type->category()), "");
|
||||
}
|
||||
|
||||
SymbolicAddressVariable::SymbolicAddressVariable(
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface)
|
||||
SymbolicIntVariable(TypeProvider::uint(160), TypeProvider::uint(160), move(_uniqueName), _context)
|
||||
{
|
||||
}
|
||||
|
||||
SymbolicFixedBytesVariable::SymbolicFixedBytesVariable(
|
||||
solidity::TypePointer _originalType,
|
||||
unsigned _numBytes,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface)
|
||||
SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), _originalType, move(_uniqueName), _context)
|
||||
{
|
||||
}
|
||||
|
||||
SymbolicFunctionVariable::SymbolicFunctionVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface),
|
||||
m_declaration(m_interface.newVariable(currentName(), m_sort))
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context),
|
||||
m_declaration(m_context.newVariable(currentName(), m_sort))
|
||||
{
|
||||
solAssert(m_type->category() == solidity::Type::Category::Function, "");
|
||||
}
|
||||
|
||||
SymbolicFunctionVariable::SymbolicFunctionVariable(
|
||||
SortPointer _sort,
|
||||
string _uniqueName,
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_sort), move(_uniqueName), _context),
|
||||
m_declaration(m_context.newVariable(currentName(), m_sort))
|
||||
{
|
||||
solAssert(m_sort->kind == Kind::Function, "");
|
||||
}
|
||||
|
||||
void SymbolicFunctionVariable::resetDeclaration()
|
||||
{
|
||||
m_declaration = m_interface.newVariable(currentName(), m_sort);
|
||||
m_declaration = m_context.newVariable(currentName(), m_sort);
|
||||
}
|
||||
|
||||
Expression SymbolicFunctionVariable::increaseIndex()
|
||||
@ -147,29 +173,43 @@ Expression SymbolicFunctionVariable::operator()(vector<Expression> _arguments) c
|
||||
SymbolicMappingVariable::SymbolicMappingVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isMapping(m_type->category()), "");
|
||||
}
|
||||
|
||||
SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalType,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _originalType, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isArray(m_type->category()), "");
|
||||
}
|
||||
|
||||
Expression SymbolicArrayVariable::currentValue(solidity::TypePointer const& _targetType) const
|
||||
{
|
||||
if (_targetType)
|
||||
// StringLiterals are encoded as SMT arrays in the generic case,
|
||||
// but they can also be compared/assigned to fixed bytes, in which
|
||||
// case they'd need to be encoded as numbers.
|
||||
if (auto strType = dynamic_cast<StringLiteralType const*>(m_originalType))
|
||||
if (_targetType->category() == solidity::Type::Category::FixedBytes)
|
||||
return smt::Expression(u256(toHex(asBytes(strType->value()), HexPrefix::Add)));
|
||||
|
||||
return SymbolicVariable::currentValue(_targetType);
|
||||
}
|
||||
|
||||
SymbolicEnumVariable::SymbolicEnumVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isEnum(m_type->category()), "");
|
||||
}
|
||||
@ -177,9 +217,9 @@ SymbolicEnumVariable::SymbolicEnumVariable(
|
||||
SymbolicTupleVariable::SymbolicTupleVariable(
|
||||
solidity::TypePointer _type,
|
||||
string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
):
|
||||
SymbolicVariable(move(_type), move(_uniqueName), _interface)
|
||||
SymbolicVariable(_type, _type, move(_uniqueName), _context)
|
||||
{
|
||||
solAssert(isTuple(m_type->category()), "");
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ namespace solidity
|
||||
namespace smt
|
||||
{
|
||||
|
||||
class EncodingContext;
|
||||
class Type;
|
||||
|
||||
/**
|
||||
@ -39,20 +40,23 @@ class SymbolicVariable
|
||||
public:
|
||||
SymbolicVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalType,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
SymbolicVariable(
|
||||
SortPointer _sort,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
virtual ~SymbolicVariable() = default;
|
||||
|
||||
Expression currentValue() const;
|
||||
virtual Expression currentValue(solidity::TypePointer const& _targetType = TypePointer{}) const;
|
||||
std::string currentName() const;
|
||||
virtual Expression valueAtIndex(int _index) const;
|
||||
virtual std::string nameAtIndex(int _index) const;
|
||||
virtual Expression resetIndex();
|
||||
virtual Expression increaseIndex();
|
||||
virtual Expression operator()(std::vector<Expression> /*_arguments*/) const
|
||||
{
|
||||
@ -62,7 +66,9 @@ public:
|
||||
unsigned index() const { return m_ssa->index(); }
|
||||
unsigned& index() { return m_ssa->index(); }
|
||||
|
||||
SortPointer const& sort() const { return m_sort; }
|
||||
solidity::TypePointer const& type() const { return m_type; }
|
||||
solidity::TypePointer const& originalType() const { return m_originalType; }
|
||||
|
||||
protected:
|
||||
std::string uniqueSymbol(unsigned _index) const;
|
||||
@ -71,8 +77,10 @@ protected:
|
||||
SortPointer m_sort;
|
||||
/// Solidity type, used for size and range in number types.
|
||||
solidity::TypePointer m_type;
|
||||
/// Solidity original type, used for type conversion if necessary.
|
||||
solidity::TypePointer m_originalType;
|
||||
std::string m_uniqueName;
|
||||
SolverInterface& m_interface;
|
||||
EncodingContext& m_context;
|
||||
std::unique_ptr<SSAVariable> m_ssa;
|
||||
};
|
||||
|
||||
@ -85,7 +93,7 @@ public:
|
||||
SymbolicBoolVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -97,8 +105,9 @@ class SymbolicIntVariable: public SymbolicVariable
|
||||
public:
|
||||
SymbolicIntVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalType,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -110,7 +119,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable
|
||||
public:
|
||||
SymbolicAddressVariable(
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -121,9 +130,10 @@ class SymbolicFixedBytesVariable: public SymbolicIntVariable
|
||||
{
|
||||
public:
|
||||
SymbolicFixedBytesVariable(
|
||||
solidity::TypePointer _originalType,
|
||||
unsigned _numBytes,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -136,7 +146,12 @@ public:
|
||||
SymbolicFunctionVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
SymbolicFunctionVariable(
|
||||
SortPointer _sort,
|
||||
std::string _uniqueName,
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
Expression increaseIndex();
|
||||
@ -159,7 +174,7 @@ public:
|
||||
SymbolicMappingVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -171,9 +186,12 @@ class SymbolicArrayVariable: public SymbolicVariable
|
||||
public:
|
||||
SymbolicArrayVariable(
|
||||
solidity::TypePointer _type,
|
||||
solidity::TypePointer _originalTtype,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
Expression currentValue(solidity::TypePointer const& _targetType = TypePointer{}) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -185,7 +203,7 @@ public:
|
||||
SymbolicEnumVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
};
|
||||
|
||||
@ -198,7 +216,7 @@ public:
|
||||
SymbolicTupleVariable(
|
||||
solidity::TypePointer _type,
|
||||
std::string _uniqueName,
|
||||
SolverInterface& _interface
|
||||
EncodingContext& _context
|
||||
);
|
||||
|
||||
std::vector<std::shared_ptr<SymbolicVariable>> const& components()
|
||||
|
@ -17,7 +17,8 @@
|
||||
|
||||
#include <libsolidity/formal/VariableUsage.h>
|
||||
|
||||
#include <libsolidity/formal/SMTChecker.h>
|
||||
#include <libsolidity/formal/BMC.h>
|
||||
#include <libsolidity/formal/SMTEncoder.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@ -48,7 +49,7 @@ void VariableUsage::endVisit(IndexAccess const& _indexAccess)
|
||||
{
|
||||
/// identifier.annotation().lValueRequested == false, that's why we
|
||||
/// need to check that before.
|
||||
auto identifier = dynamic_cast<Identifier const*>(SMTChecker::leftmostBase(_indexAccess));
|
||||
auto identifier = dynamic_cast<Identifier const*>(SMTEncoder::leftmostBase(_indexAccess));
|
||||
if (identifier)
|
||||
checkIdentifier(*identifier);
|
||||
}
|
||||
@ -56,9 +57,13 @@ void VariableUsage::endVisit(IndexAccess const& _indexAccess)
|
||||
|
||||
void VariableUsage::endVisit(FunctionCall const& _funCall)
|
||||
{
|
||||
if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall))
|
||||
if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end())
|
||||
funDef->accept(*this);
|
||||
if (m_inlineFunctionCalls)
|
||||
if (auto const& funDef = SMTEncoder::functionCallToDefinition(_funCall))
|
||||
{
|
||||
solAssert(funDef, "");
|
||||
if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end())
|
||||
funDef->accept(*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool VariableUsage::visit(FunctionDefinition const& _function)
|
||||
|
@ -38,6 +38,9 @@ public:
|
||||
/// @param _outerCallstack the current callstack in the callers context.
|
||||
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<CallableDeclaration const*> const& _outerCallstack);
|
||||
|
||||
/// Sets whether to inline function calls.
|
||||
void setFunctionInlining(bool _inlineFunction) { m_inlineFunctionCalls = _inlineFunction; }
|
||||
|
||||
private:
|
||||
void endVisit(Identifier const& _node) override;
|
||||
void endVisit(IndexAccess const& _node) override;
|
||||
@ -53,6 +56,8 @@ private:
|
||||
std::set<VariableDeclaration const*> m_touchedVariables;
|
||||
std::vector<CallableDeclaration const*> m_callStack;
|
||||
CallableDeclaration const* m_lastCall = nullptr;
|
||||
|
||||
bool m_inlineFunctionCalls = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
97
libsolidity/formal/Z3CHCInterface.cpp
Normal file
97
libsolidity/formal/Z3CHCInterface.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libsolidity/formal/Z3CHCInterface.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libdevcore/CommonIO.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity::smt;
|
||||
|
||||
Z3CHCInterface::Z3CHCInterface():
|
||||
m_z3Interface(make_shared<Z3Interface>()),
|
||||
m_context(m_z3Interface->context()),
|
||||
m_solver(*m_context)
|
||||
{
|
||||
// This needs to be set globally.
|
||||
z3::set_param("rewriter.pull_cheap_ite", true);
|
||||
// This needs to be set in the context.
|
||||
m_context->set("timeout", queryTimeout);
|
||||
}
|
||||
|
||||
void Z3CHCInterface::declareVariable(string const& _name, Sort const& _sort)
|
||||
{
|
||||
m_z3Interface->declareVariable(_name, _sort);
|
||||
}
|
||||
|
||||
void Z3CHCInterface::registerRelation(Expression const& _expr)
|
||||
{
|
||||
m_solver.register_relation(m_z3Interface->functions().at(_expr.name));
|
||||
}
|
||||
|
||||
void Z3CHCInterface::addRule(Expression const& _expr, string const& _name)
|
||||
{
|
||||
z3::expr rule = m_z3Interface->toZ3Expr(_expr);
|
||||
if (m_z3Interface->constants().empty())
|
||||
m_solver.add_rule(rule, m_context->str_symbol(_name.c_str()));
|
||||
else
|
||||
{
|
||||
z3::expr_vector variables(*m_context);
|
||||
for (auto const& var: m_z3Interface->constants())
|
||||
variables.push_back(var.second);
|
||||
z3::expr boundRule = z3::forall(variables, rule);
|
||||
m_solver.add_rule(boundRule, m_context->str_symbol(_name.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
pair<CheckResult, vector<string>> Z3CHCInterface::query(Expression const& _expr)
|
||||
{
|
||||
CheckResult result;
|
||||
vector<string> values;
|
||||
try
|
||||
{
|
||||
z3::expr z3Expr = m_z3Interface->toZ3Expr(_expr);
|
||||
switch (m_solver.query(z3Expr))
|
||||
{
|
||||
case z3::check_result::sat:
|
||||
{
|
||||
result = CheckResult::SATISFIABLE;
|
||||
// TODO retrieve model.
|
||||
break;
|
||||
}
|
||||
case z3::check_result::unsat:
|
||||
{
|
||||
result = CheckResult::UNSATISFIABLE;
|
||||
// TODO retrieve invariants.
|
||||
break;
|
||||
}
|
||||
case z3::check_result::unknown:
|
||||
result = CheckResult::UNKNOWN;
|
||||
break;
|
||||
}
|
||||
// TODO retrieve model / invariants
|
||||
}
|
||||
catch (z3::exception const& _e)
|
||||
{
|
||||
result = CheckResult::ERROR;
|
||||
values.clear();
|
||||
}
|
||||
|
||||
return make_pair(result, values);
|
||||
}
|
64
libsolidity/formal/Z3CHCInterface.h
Normal file
64
libsolidity/formal/Z3CHCInterface.h
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Z3 specific Horn solver interface.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/formal/CHCSolverInterface.h>
|
||||
#include <libsolidity/formal/Z3Interface.h>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
namespace smt
|
||||
{
|
||||
|
||||
class Z3CHCInterface: public CHCSolverInterface
|
||||
{
|
||||
public:
|
||||
Z3CHCInterface();
|
||||
|
||||
/// Forwards variable declaration to Z3Interface.
|
||||
void declareVariable(std::string const& _name, Sort const& _sort) override;
|
||||
|
||||
void registerRelation(Expression const& _expr) override;
|
||||
|
||||
void addRule(Expression const& _expr, std::string const& _name) override;
|
||||
|
||||
std::pair<CheckResult, std::vector<std::string>> query(Expression const& _expr) override;
|
||||
|
||||
std::shared_ptr<Z3Interface> z3Interface() { return m_z3Interface; }
|
||||
|
||||
private:
|
||||
// Used to handle variables.
|
||||
std::shared_ptr<Z3Interface> m_z3Interface;
|
||||
|
||||
z3::context* m_context;
|
||||
// Horn solver.
|
||||
z3::fixedpoint m_solver;
|
||||
|
||||
// SMT query timeout in milliseconds.
|
||||
static int const queryTimeout = 10000;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -43,17 +43,24 @@ public:
|
||||
void addAssertion(Expression const& _expr) override;
|
||||
std::pair<CheckResult, std::vector<std::string>> check(std::vector<Expression> const& _expressionsToEvaluate) override;
|
||||
|
||||
z3::expr toZ3Expr(Expression const& _expr);
|
||||
|
||||
std::map<std::string, z3::expr> constants() const { return m_constants; }
|
||||
std::map<std::string, z3::func_decl> functions() const { return m_functions; }
|
||||
|
||||
z3::context* context() { return &m_context; }
|
||||
|
||||
private:
|
||||
void declareFunction(std::string const& _name, Sort const& _sort);
|
||||
|
||||
z3::expr toZ3Expr(Expression const& _expr);
|
||||
z3::sort z3Sort(smt::Sort const& _sort);
|
||||
z3::sort_vector z3Sort(std::vector<smt::SortPointer> const& _sorts);
|
||||
|
||||
z3::context m_context;
|
||||
z3::solver m_solver;
|
||||
std::map<std::string, z3::expr> m_constants;
|
||||
std::map<std::string, z3::func_decl> m_functions;
|
||||
|
||||
z3::context m_context;
|
||||
z3::solver m_solver;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -44,14 +44,13 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
|
||||
for (auto it: _contractDef.interfaceFunctions())
|
||||
{
|
||||
if (
|
||||
_contractDef.isLibrary() &&
|
||||
(it.second->stateMutability() > StateMutability::View ||
|
||||
anyDataStoredInStorage(it.second->parameterTypes() + it.second->returnParameterTypes()))
|
||||
)
|
||||
if (_contractDef.isLibrary() && (
|
||||
it.second->stateMutability() > StateMutability::View ||
|
||||
anyDataStoredInStorage(it.second->parameterTypes() + it.second->returnParameterTypes())
|
||||
))
|
||||
continue;
|
||||
|
||||
auto externalFunctionType = it.second->interfaceFunctionType();
|
||||
FunctionType const* externalFunctionType = it.second->interfaceFunctionType();
|
||||
solAssert(!!externalFunctionType, "");
|
||||
Json::Value method;
|
||||
method["type"] = "function";
|
||||
@ -63,18 +62,21 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
method["inputs"] = formatTypeList(
|
||||
externalFunctionType->parameterNames(),
|
||||
externalFunctionType->parameterTypes(),
|
||||
it.second->parameterTypes(),
|
||||
_contractDef.isLibrary()
|
||||
);
|
||||
method["outputs"] = formatTypeList(
|
||||
externalFunctionType->returnParameterNames(),
|
||||
externalFunctionType->returnParameterTypes(),
|
||||
it.second->returnParameterTypes(),
|
||||
_contractDef.isLibrary()
|
||||
);
|
||||
abi.append(method);
|
||||
abi.append(std::move(method));
|
||||
}
|
||||
if (_contractDef.constructor())
|
||||
{
|
||||
auto externalFunctionType = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
|
||||
FunctionType constrType(*_contractDef.constructor(), false);
|
||||
FunctionType const* externalFunctionType = constrType.interfaceFunctionType();
|
||||
solAssert(!!externalFunctionType, "");
|
||||
Json::Value method;
|
||||
method["type"] = "constructor";
|
||||
@ -83,19 +85,20 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
method["inputs"] = formatTypeList(
|
||||
externalFunctionType->parameterNames(),
|
||||
externalFunctionType->parameterTypes(),
|
||||
constrType.parameterTypes(),
|
||||
_contractDef.isLibrary()
|
||||
);
|
||||
abi.append(method);
|
||||
abi.append(std::move(method));
|
||||
}
|
||||
if (_contractDef.fallbackFunction())
|
||||
{
|
||||
auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType();
|
||||
FunctionType const* externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType();
|
||||
solAssert(!!externalFunctionType, "");
|
||||
Json::Value method;
|
||||
method["type"] = "fallback";
|
||||
method["payable"] = externalFunctionType->isPayable();
|
||||
method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability());
|
||||
abi.append(method);
|
||||
abi.append(std::move(method));
|
||||
}
|
||||
for (auto const& it: _contractDef.interfaceEvents())
|
||||
{
|
||||
@ -106,15 +109,15 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
Json::Value params(Json::arrayValue);
|
||||
for (auto const& p: it->parameters())
|
||||
{
|
||||
auto type = p->annotation().type->interfaceType(false);
|
||||
solAssert(type.get(), "");
|
||||
Type const* type = p->annotation().type->interfaceType(false);
|
||||
solAssert(type, "");
|
||||
Json::Value input;
|
||||
auto param = formatType(p->name(), *type.get(), false);
|
||||
auto param = formatType(p->name(), *type, *p->annotation().type, false);
|
||||
param["indexed"] = p->isIndexed();
|
||||
params.append(param);
|
||||
params.append(std::move(param));
|
||||
}
|
||||
event["inputs"] = params;
|
||||
abi.append(event);
|
||||
event["inputs"] = std::move(params);
|
||||
abi.append(std::move(event));
|
||||
}
|
||||
|
||||
return abi;
|
||||
@ -122,31 +125,39 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
|
||||
Json::Value ABI::formatTypeList(
|
||||
vector<string> const& _names,
|
||||
vector<TypePointer> const& _types,
|
||||
vector<TypePointer> const& _encodingTypes,
|
||||
vector<TypePointer> const& _solidityTypes,
|
||||
bool _forLibrary
|
||||
)
|
||||
{
|
||||
Json::Value params(Json::arrayValue);
|
||||
solAssert(_names.size() == _types.size(), "Names and types vector size does not match");
|
||||
solAssert(_names.size() == _encodingTypes.size(), "Names and types vector size does not match");
|
||||
solAssert(_names.size() == _solidityTypes.size(), "");
|
||||
for (unsigned i = 0; i < _names.size(); ++i)
|
||||
{
|
||||
solAssert(_types[i], "");
|
||||
params.append(formatType(_names[i], *_types[i], _forLibrary));
|
||||
solAssert(_encodingTypes[i], "");
|
||||
params.append(formatType(_names[i], *_encodingTypes[i], *_solidityTypes[i], _forLibrary));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLibrary)
|
||||
Json::Value ABI::formatType(
|
||||
string const& _name,
|
||||
Type const& _encodingType,
|
||||
Type const& _solidityType,
|
||||
bool _forLibrary
|
||||
)
|
||||
{
|
||||
Json::Value ret;
|
||||
ret["name"] = _name;
|
||||
string suffix = (_forLibrary && _type.dataStoredIn(DataLocation::Storage)) ? " storage" : "";
|
||||
if (_type.isValueType() || (_forLibrary && _type.dataStoredIn(DataLocation::Storage)))
|
||||
ret["type"] = _type.canonicalName() + suffix;
|
||||
else if (ArrayType const* arrayType = dynamic_cast<ArrayType const*>(&_type))
|
||||
ret["internalType"] = _solidityType.toString(true);
|
||||
string suffix = (_forLibrary && _encodingType.dataStoredIn(DataLocation::Storage)) ? " storage" : "";
|
||||
if (_encodingType.isValueType() || (_forLibrary && _encodingType.dataStoredIn(DataLocation::Storage)))
|
||||
ret["type"] = _encodingType.canonicalName() + suffix;
|
||||
else if (ArrayType const* arrayType = dynamic_cast<ArrayType const*>(&_encodingType))
|
||||
{
|
||||
if (arrayType->isByteArray())
|
||||
ret["type"] = _type.canonicalName() + suffix;
|
||||
ret["type"] = _encodingType.canonicalName() + suffix;
|
||||
else
|
||||
{
|
||||
string suffix;
|
||||
@ -155,7 +166,12 @@ Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLib
|
||||
else
|
||||
suffix = string("[") + arrayType->length().str() + "]";
|
||||
solAssert(arrayType->baseType(), "");
|
||||
Json::Value subtype = formatType("", *arrayType->baseType(), _forLibrary);
|
||||
Json::Value subtype = formatType(
|
||||
"",
|
||||
*arrayType->baseType(),
|
||||
*dynamic_cast<ArrayType const&>(_solidityType).baseType(),
|
||||
_forLibrary
|
||||
);
|
||||
if (subtype.isMember("components"))
|
||||
{
|
||||
ret["type"] = subtype["type"].asString() + suffix;
|
||||
@ -165,16 +181,16 @@ Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLib
|
||||
ret["type"] = subtype["type"].asString() + suffix;
|
||||
}
|
||||
}
|
||||
else if (StructType const* structType = dynamic_cast<StructType const*>(&_type))
|
||||
else if (StructType const* structType = dynamic_cast<StructType const*>(&_encodingType))
|
||||
{
|
||||
ret["type"] = "tuple";
|
||||
ret["components"] = Json::arrayValue;
|
||||
for (auto const& member: structType->members(nullptr))
|
||||
{
|
||||
solAssert(member.type, "");
|
||||
auto t = member.type->interfaceType(_forLibrary);
|
||||
solAssert(t.get(), "");
|
||||
ret["components"].append(formatType(member.name, *t.get(), _forLibrary));
|
||||
Type const* t = member.type->interfaceType(_forLibrary);
|
||||
solAssert(t, "");
|
||||
ret["components"].append(formatType(member.name, *t, *member.type, _forLibrary));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -45,15 +45,25 @@ private:
|
||||
/// @returns a json value suitable for a list of types in function input or output
|
||||
/// parameters or other places. If @a _forLibrary is true, complex types are referenced
|
||||
/// by name, otherwise they are anonymously expanded.
|
||||
/// @a _solidityTypes is the list of original Solidity types where @a _encodingTypes is the list of
|
||||
/// ABI types used for the actual encoding.
|
||||
static Json::Value formatTypeList(
|
||||
std::vector<std::string> const& _names,
|
||||
std::vector<TypePointer> const& _types,
|
||||
std::vector<TypePointer> const& _encodingTypes,
|
||||
std::vector<TypePointer> const& _solidityTypes,
|
||||
bool _forLibrary
|
||||
);
|
||||
/// @returns a Json object with "name", "type" and potentially "components" keys, according
|
||||
/// to the ABI specification.
|
||||
/// @returns a Json object with "name", "type", "internalType" and potentially
|
||||
/// "components" keys, according to the ABI specification.
|
||||
/// If it is possible to express the type as a single string, it is allowed to return a single string.
|
||||
static Json::Value formatType(std::string const& _name, Type const& _type, bool _forLibrary);
|
||||
/// @a _solidityType is the original Solidity type and @a _encodingTypes is the
|
||||
/// ABI type used for the actual encoding.
|
||||
static Json::Value formatType(
|
||||
std::string const& _name,
|
||||
Type const& _encodingType,
|
||||
Type const& _solidityType,
|
||||
bool _forLibrary
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
#include <libsolidity/codegen/Compiler.h>
|
||||
#include <libsolidity/formal/SMTChecker.h>
|
||||
#include <libsolidity/formal/ModelChecker.h>
|
||||
#include <libsolidity/interface/ABI.h>
|
||||
#include <libsolidity/interface/Natspec.h>
|
||||
#include <libsolidity/interface/GasEstimator.h>
|
||||
@ -49,6 +49,12 @@
|
||||
#include <libsolidity/codegen/ir/IRGenerator.h>
|
||||
|
||||
#include <libyul/YulString.h>
|
||||
#include <libyul/AsmPrinter.h>
|
||||
#include <libyul/backends/wasm/EVMToEWasmTranslator.h>
|
||||
#include <libyul/backends/wasm/EWasmObjectCompiler.h>
|
||||
#include <libyul/backends/wasm/WasmDialect.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/SemVerHandler.h>
|
||||
@ -73,6 +79,7 @@ static int g_compilerStackCounts = 0;
|
||||
CompilerStack::CompilerStack(ReadCallback::Callback const& _readFile):
|
||||
m_readFile{_readFile},
|
||||
m_generateIR{false},
|
||||
m_generateEWasm{false},
|
||||
m_errorList{},
|
||||
m_errorReporter{m_errorList}
|
||||
{
|
||||
@ -110,7 +117,7 @@ boost::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string c
|
||||
|
||||
void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set remappings before parsing."));
|
||||
for (auto const& remapping: _remappings)
|
||||
solAssert(!remapping.prefix.empty(), "");
|
||||
@ -119,14 +126,14 @@ void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
|
||||
|
||||
void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set EVM version before parsing."));
|
||||
m_evmVersion = _version;
|
||||
}
|
||||
|
||||
void CompilerStack::setLibraries(std::map<std::string, h160> const& _libraries)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set libraries before parsing."));
|
||||
m_libraries = _libraries;
|
||||
}
|
||||
@ -140,21 +147,21 @@ void CompilerStack::setOptimiserSettings(bool _optimize, unsigned _runs)
|
||||
|
||||
void CompilerStack::setOptimiserSettings(OptimiserSettings _settings)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set optimiser settings before parsing."));
|
||||
m_optimiserSettings = std::move(_settings);
|
||||
}
|
||||
|
||||
void CompilerStack::useMetadataLiteralSources(bool _metadataLiteralSources)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must set use literal sources before parsing."));
|
||||
m_metadataLiteralSources = _metadataLiteralSources;
|
||||
}
|
||||
|
||||
void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _response)
|
||||
{
|
||||
if (m_stackState >= ParsingSuccessful)
|
||||
if (m_stackState >= ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must add SMTLib2 responses before parsing."));
|
||||
m_smtlib2Responses[_hash] = _response;
|
||||
}
|
||||
@ -162,6 +169,7 @@ void CompilerStack::addSMTLib2Response(h256 const& _hash, string const& _respons
|
||||
void CompilerStack::reset(bool _keepSettings)
|
||||
{
|
||||
m_stackState = Empty;
|
||||
m_hasError = false;
|
||||
m_sources.clear();
|
||||
m_smtlib2Responses.clear();
|
||||
m_unhandledSMTLib2Queries.clear();
|
||||
@ -171,6 +179,7 @@ void CompilerStack::reset(bool _keepSettings)
|
||||
m_libraries.clear();
|
||||
m_evmVersion = langutil::EVMVersion();
|
||||
m_generateIR = false;
|
||||
m_generateEWasm = false;
|
||||
m_optimiserSettings = OptimiserSettings::minimal();
|
||||
m_metadataLiteralSources = false;
|
||||
}
|
||||
@ -232,24 +241,23 @@ bool CompilerStack::parse()
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Error::containsOnlyWarnings(m_errorReporter.errors()))
|
||||
{
|
||||
m_stackState = ParsingSuccessful;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
m_stackState = ParsingPerformed;
|
||||
if (!Error::containsOnlyWarnings(m_errorReporter.errors()))
|
||||
m_hasError = true;
|
||||
return !m_hasError;
|
||||
}
|
||||
|
||||
bool CompilerStack::analyze()
|
||||
{
|
||||
if (m_stackState != ParsingSuccessful || m_stackState >= AnalysisSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was successful."));
|
||||
if (m_stackState != ParsingPerformed || m_stackState >= AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Must call analyze only after parsing was performed."));
|
||||
resolveImports();
|
||||
|
||||
bool noErrors = true;
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (!syntaxChecker.checkSyntax(*source->ast))
|
||||
@ -363,48 +371,68 @@ bool CompilerStack::analyze()
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
SMTChecker smtChecker(m_errorReporter, m_smtlib2Responses);
|
||||
ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
smtChecker.analyze(*source->ast, source->scanner);
|
||||
m_unhandledSMTLib2Queries += smtChecker.unhandledQueries();
|
||||
modelChecker.analyze(*source->ast);
|
||||
m_unhandledSMTLib2Queries += modelChecker.unhandledQueries();
|
||||
}
|
||||
}
|
||||
catch(FatalError const&)
|
||||
catch (FatalError const&)
|
||||
{
|
||||
if (m_errorReporter.errors().empty())
|
||||
throw; // Something is weird here, rather throw again.
|
||||
noErrors = false;
|
||||
}
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
m_stackState = AnalysisSuccessful;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
m_stackState = AnalysisPerformed;
|
||||
if (!noErrors)
|
||||
m_hasError = true;
|
||||
|
||||
return !m_hasError;
|
||||
}
|
||||
|
||||
bool CompilerStack::parseAndAnalyze()
|
||||
{
|
||||
return parse() && analyze();
|
||||
bool success = parse();
|
||||
if (success || m_parserErrorRecovery)
|
||||
success = analyze();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CompilerStack::isRequestedSource(string const& _sourceName) const
|
||||
{
|
||||
return
|
||||
m_requestedContractNames.empty() ||
|
||||
m_requestedContractNames.count("") ||
|
||||
m_requestedContractNames.count(_sourceName);
|
||||
}
|
||||
|
||||
bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) const
|
||||
{
|
||||
return
|
||||
m_requestedContractNames.empty() ||
|
||||
m_requestedContractNames.count(_contract.fullyQualifiedName()) ||
|
||||
m_requestedContractNames.count(_contract.name()) ||
|
||||
m_requestedContractNames.count(":" + _contract.name());
|
||||
/// In case nothing was specified in outputSelection.
|
||||
if (m_requestedContractNames.empty())
|
||||
return true;
|
||||
|
||||
for (auto const& key: vector<string>{"", _contract.sourceUnitName()})
|
||||
{
|
||||
auto const& it = m_requestedContractNames.find(key);
|
||||
if (it != m_requestedContractNames.end())
|
||||
if (it->second.count(_contract.name()) || it->second.count(""))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompilerStack::compile()
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
if (!parseAndAnalyze())
|
||||
return false;
|
||||
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors."));
|
||||
|
||||
// Only compile contracts individually which have been requested.
|
||||
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
|
||||
for (Source const* source: m_sourceOrder)
|
||||
@ -413,8 +441,10 @@ bool CompilerStack::compile()
|
||||
if (isRequestedContract(*contract))
|
||||
{
|
||||
compileContract(*contract, otherCompilers);
|
||||
if (m_generateIR)
|
||||
if (m_generateIR || m_generateEWasm)
|
||||
generateIR(*contract);
|
||||
if (m_generateEWasm)
|
||||
generateEWasm(*contract);
|
||||
}
|
||||
m_stackState = CompilationSuccessful;
|
||||
this->link();
|
||||
@ -433,7 +463,7 @@ void CompilerStack::link()
|
||||
|
||||
vector<string> CompilerStack::contractNames() const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
vector<string> contractNames;
|
||||
for (auto const& contract: m_contracts)
|
||||
@ -443,7 +473,7 @@ vector<string> CompilerStack::contractNames() const
|
||||
|
||||
string const CompilerStack::lastContractName() const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
// try to find some user-supplied contract
|
||||
string contractName;
|
||||
@ -502,7 +532,7 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
|
||||
|
||||
std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found."));
|
||||
|
||||
// Look up the contract (by its fully-qualified name)
|
||||
@ -540,6 +570,14 @@ string const& CompilerStack::yulIROptimized(string const& _contractName) const
|
||||
return contract(_contractName).yulIROptimized;
|
||||
}
|
||||
|
||||
string const& CompilerStack::eWasm(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Compilation was not successful."));
|
||||
|
||||
return contract(_contractName).eWasm;
|
||||
}
|
||||
|
||||
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState != CompilationSuccessful)
|
||||
@ -601,7 +639,7 @@ map<string, unsigned> CompilerStack::sourceIndices() const
|
||||
|
||||
Json::Value const& CompilerStack::contractABI(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return contractABI(contract(_contractName));
|
||||
@ -609,7 +647,7 @@ Json::Value const& CompilerStack::contractABI(string const& _contractName) const
|
||||
|
||||
Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
@ -623,7 +661,7 @@ Json::Value const& CompilerStack::contractABI(Contract const& _contract) const
|
||||
|
||||
Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return natspecUser(contract(_contractName));
|
||||
@ -631,7 +669,7 @@ Json::Value const& CompilerStack::natspecUser(string const& _contractName) const
|
||||
|
||||
Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
@ -645,7 +683,7 @@ Json::Value const& CompilerStack::natspecUser(Contract const& _contract) const
|
||||
|
||||
Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return natspecDev(contract(_contractName));
|
||||
@ -653,7 +691,7 @@ Json::Value const& CompilerStack::natspecDev(string const& _contractName) const
|
||||
|
||||
Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
@ -667,7 +705,7 @@ Json::Value const& CompilerStack::natspecDev(Contract const& _contract) const
|
||||
|
||||
Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
Json::Value methodIdentifiers(Json::objectValue);
|
||||
@ -678,7 +716,7 @@ Json::Value CompilerStack::methodIdentifiers(string const& _contractName) const
|
||||
|
||||
string const& CompilerStack::metadata(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return metadata(contract(_contractName));
|
||||
@ -686,7 +724,7 @@ string const& CompilerStack::metadata(string const& _contractName) const
|
||||
|
||||
string const& CompilerStack::metadata(Contract const& _contract) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
solAssert(_contract.contract, "");
|
||||
@ -708,7 +746,9 @@ Scanner const& CompilerStack::scanner(string const& _sourceName) const
|
||||
|
||||
SourceUnit const& CompilerStack::ast(string const& _sourceName) const
|
||||
{
|
||||
if (m_stackState < ParsingSuccessful)
|
||||
if (m_stackState < ParsingPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing not yet performed."));
|
||||
if (!source(_sourceName).ast && !m_parserErrorRecovery)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful."));
|
||||
|
||||
return *source(_sourceName).ast;
|
||||
@ -716,7 +756,7 @@ SourceUnit const& CompilerStack::ast(string const& _sourceName) const
|
||||
|
||||
ContractDefinition const& CompilerStack::contractDefinition(string const& _contractName) const
|
||||
{
|
||||
if (m_stackState < AnalysisSuccessful)
|
||||
if (m_stackState < AnalysisPerformed)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Analysis was not successful."));
|
||||
|
||||
return *contract(_contractName).contract;
|
||||
@ -766,7 +806,7 @@ h256 const& CompilerStack::Source::keccak256() const
|
||||
h256 const& CompilerStack::Source::swarmHash() const
|
||||
{
|
||||
if (swarmHashCached == h256{})
|
||||
swarmHashCached = dev::swarmHash(scanner->source());
|
||||
swarmHashCached = dev::bzzr1Hash(scanner->source());
|
||||
return swarmHashCached;
|
||||
}
|
||||
|
||||
@ -780,7 +820,7 @@ string const& CompilerStack::Source::ipfsUrl() const
|
||||
|
||||
StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath)
|
||||
{
|
||||
solAssert(m_stackState < ParsingSuccessful, "");
|
||||
solAssert(m_stackState < ParsingPerformed, "");
|
||||
StringMap newSources;
|
||||
for (auto const& node: _ast.nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
@ -816,7 +856,7 @@ StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string
|
||||
|
||||
string CompilerStack::applyRemapping(string const& _path, string const& _context)
|
||||
{
|
||||
solAssert(m_stackState < ParsingSuccessful, "");
|
||||
solAssert(m_stackState < ParsingPerformed, "");
|
||||
// Try to find the longest prefix match in all remappings that are active in the current context.
|
||||
auto isPrefixOf = [](string const& _a, string const& _b)
|
||||
{
|
||||
@ -858,7 +898,7 @@ string CompilerStack::applyRemapping(string const& _path, string const& _context
|
||||
|
||||
void CompilerStack::resolveImports()
|
||||
{
|
||||
solAssert(m_stackState == ParsingSuccessful, "");
|
||||
solAssert(m_stackState == ParsingPerformed, "");
|
||||
|
||||
// topological sorting (depth first search) of the import graph, cutting potential cycles
|
||||
vector<Source const*> sourceOrder;
|
||||
@ -869,20 +909,22 @@ void CompilerStack::resolveImports()
|
||||
if (sourcesSeen.count(_source))
|
||||
return;
|
||||
sourcesSeen.insert(_source);
|
||||
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
{
|
||||
string const& path = import->annotation().absolutePath;
|
||||
solAssert(!path.empty(), "");
|
||||
solAssert(m_sources.count(path), "");
|
||||
import->annotation().sourceUnit = m_sources[path].ast.get();
|
||||
toposort(&m_sources[path]);
|
||||
}
|
||||
if (_source->ast)
|
||||
for (ASTPointer<ASTNode> const& node: _source->ast->nodes())
|
||||
if (ImportDirective const* import = dynamic_cast<ImportDirective*>(node.get()))
|
||||
{
|
||||
string const& path = import->annotation().absolutePath;
|
||||
solAssert(!path.empty(), "");
|
||||
solAssert(m_sources.count(path), "");
|
||||
import->annotation().sourceUnit = m_sources[path].ast.get();
|
||||
toposort(&m_sources[path]);
|
||||
}
|
||||
sourceOrder.push_back(_source);
|
||||
};
|
||||
|
||||
for (auto const& sourcePair: m_sources)
|
||||
toposort(&sourcePair.second);
|
||||
if (isRequestedSource(sourcePair.first))
|
||||
toposort(&sourcePair.second);
|
||||
|
||||
swap(m_sourceOrder, sourceOrder);
|
||||
}
|
||||
@ -903,7 +945,9 @@ void CompilerStack::compileContract(
|
||||
map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers
|
||||
)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisSuccessful, "");
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called compile with errors."));
|
||||
|
||||
if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed())
|
||||
return;
|
||||
@ -955,7 +999,9 @@ void CompilerStack::compileContract(
|
||||
|
||||
void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisSuccessful, "");
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateIR with errors."));
|
||||
|
||||
if (!_contract.canBeDeployed())
|
||||
return;
|
||||
@ -971,9 +1017,42 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
|
||||
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
|
||||
}
|
||||
|
||||
void CompilerStack::generateEWasm(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
if (m_hasError)
|
||||
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Called generateEWasm with errors."));
|
||||
|
||||
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
|
||||
solAssert(!compiledContract.yulIROptimized.empty(), "");
|
||||
if (!compiledContract.eWasm.empty())
|
||||
return;
|
||||
|
||||
// Re-parse the Yul IR in EVM dialect
|
||||
yul::AssemblyStack evmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
|
||||
evmStack.parseAndAnalyze("", compiledContract.yulIROptimized);
|
||||
|
||||
// Turn into eWasm dialect
|
||||
yul::Object ewasmObject = yul::EVMToEWasmTranslator(
|
||||
yul::EVMDialect::strictAssemblyForEVMObjects(m_evmVersion)
|
||||
).run(*evmStack.parserResult());
|
||||
|
||||
// Re-inject into an assembly stack for the eWasm dialect
|
||||
yul::AssemblyStack ewasmStack(m_evmVersion, yul::AssemblyStack::Language::EWasm, m_optimiserSettings);
|
||||
// TODO this is a hack for now - provide as structured AST!
|
||||
ewasmStack.parseAndAnalyze("", "{}");
|
||||
*ewasmStack.parserResult() = move(ewasmObject);
|
||||
ewasmStack.optimize();
|
||||
|
||||
//cout << yul::AsmPrinter{}(*ewasmStack.parserResult()->code) << endl;
|
||||
|
||||
// Turn into eWasm text representation.
|
||||
compiledContract.eWasm = ewasmStack.assemble(yul::AssemblyStack::Machine::eWasm).assembly;
|
||||
}
|
||||
|
||||
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const
|
||||
{
|
||||
solAssert(m_stackState >= AnalysisSuccessful, "");
|
||||
solAssert(m_stackState >= AnalysisPerformed, "");
|
||||
|
||||
auto it = m_contracts.find(_contractName);
|
||||
if (it != m_contracts.end())
|
||||
@ -1037,7 +1116,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
|
||||
else
|
||||
{
|
||||
meta["sources"][s.first]["urls"] = Json::arrayValue;
|
||||
meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes()));
|
||||
meta["sources"][s.first]["urls"].append("bzz-raw://" + toHex(s.second.swarmHash().asBytes()));
|
||||
meta["sources"][s.first]["urls"].append(s.second.ipfsUrl());
|
||||
}
|
||||
}
|
||||
@ -1184,7 +1263,7 @@ private:
|
||||
bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimentalMode)
|
||||
{
|
||||
MetadataCBOREncoder encoder;
|
||||
encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
|
||||
encoder.pushBytes("bzzr1", dev::bzzr1Hash(_metadata).asBytes());
|
||||
if (_experimentalMode)
|
||||
encoder.pushBool("experimental", true);
|
||||
if (m_release)
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -77,6 +78,8 @@ class DeclarationContainer;
|
||||
* Easy to use and self-contained Solidity compiler with as few header dependencies as possible.
|
||||
* It holds state and can be used to either step through the compilation stages (and abort e.g.
|
||||
* before compilation to bytecode) or run the whole compilation in one call.
|
||||
* If error recovery is active, it is possible to progress through the stages even when
|
||||
* there are errors. In any case, producing code is only possible without errors.
|
||||
*/
|
||||
class CompilerStack: boost::noncopyable
|
||||
{
|
||||
@ -84,8 +87,8 @@ public:
|
||||
enum State {
|
||||
Empty,
|
||||
SourcesSet,
|
||||
ParsingSuccessful,
|
||||
AnalysisSuccessful,
|
||||
ParsingPerformed,
|
||||
AnalysisPerformed,
|
||||
CompilationSuccessful
|
||||
};
|
||||
|
||||
@ -109,6 +112,10 @@ public:
|
||||
/// @returns the current state.
|
||||
State state() const { return m_stackState; }
|
||||
|
||||
bool hasError() const { return m_hasError; }
|
||||
|
||||
bool compilationSuccessful() const { return m_stackState >= CompilationSuccessful; }
|
||||
|
||||
/// Resets the compiler to an empty state. Unless @a _keepSettings is set to true,
|
||||
/// all settings are reset as well.
|
||||
void reset(bool _keepSettings = false);
|
||||
@ -145,15 +152,21 @@ public:
|
||||
/// Must be set before parsing.
|
||||
void setEVMVersion(langutil::EVMVersion _version = langutil::EVMVersion{});
|
||||
|
||||
/// Sets the list of requested contract names. If empty, no filtering is performed and every contract
|
||||
/// found in the supplied sources is compiled. Names are cleared iff @a _contractNames is missing.
|
||||
void setRequestedContractNames(std::set<std::string> const& _contractNames = std::set<std::string>{}) {
|
||||
/// Sets the requested contract names by source.
|
||||
/// If empty, no filtering is performed and every contract
|
||||
/// found in the supplied sources is compiled.
|
||||
/// Names are cleared iff @a _contractNames is missing.
|
||||
void setRequestedContractNames(std::map<std::string, std::set<std::string>> const& _contractNames = std::map<std::string, std::set<std::string>>{})
|
||||
{
|
||||
m_requestedContractNames = _contractNames;
|
||||
}
|
||||
|
||||
/// Enable experimental generation of Yul IR code.
|
||||
void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; }
|
||||
|
||||
/// Enable experimental generation of eWasm code. If enabled, IR is also generated.
|
||||
void enableEWasmGeneration(bool _enable = true) { m_generateEWasm = _enable; }
|
||||
|
||||
/// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata.
|
||||
/// Must be set before parsing.
|
||||
void useMetadataLiteralSources(bool _metadataLiteralSources);
|
||||
@ -219,6 +232,9 @@ public:
|
||||
/// @returns the optimized IR representation of a contract.
|
||||
std::string const& yulIROptimized(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the eWasm (text) representation of a contract.
|
||||
std::string const& eWasm(std::string const& _contractName) const;
|
||||
|
||||
/// @returns the assembled object for a contract.
|
||||
eth::LinkerObject const& object(std::string const& _contractName) const;
|
||||
|
||||
@ -296,6 +312,7 @@ private:
|
||||
eth::LinkerObject runtimeObject; ///< Runtime object.
|
||||
std::string yulIR; ///< Experimental Yul IR code.
|
||||
std::string yulIROptimized; ///< Optimized experimental Yul IR code.
|
||||
std::string eWasm; ///< Experimental eWasm code (text representation).
|
||||
mutable std::unique_ptr<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
|
||||
mutable std::unique_ptr<Json::Value const> abi;
|
||||
mutable std::unique_ptr<Json::Value const> userDocumentation;
|
||||
@ -311,6 +328,9 @@ private:
|
||||
std::string applyRemapping(std::string const& _path, std::string const& _context);
|
||||
void resolveImports();
|
||||
|
||||
/// @returns true if the source is requested to be compiled.
|
||||
bool isRequestedSource(std::string const& _sourceName) const;
|
||||
|
||||
/// @returns true if the contract is requested to be compiled.
|
||||
bool isRequestedContract(ContractDefinition const& _contract) const;
|
||||
|
||||
@ -326,6 +346,9 @@ private:
|
||||
/// The IR is stored but otherwise unused.
|
||||
void generateIR(ContractDefinition const& _contract);
|
||||
|
||||
/// Generate eWasm text representation for a single contract.
|
||||
void generateEWasm(ContractDefinition const& _contract);
|
||||
|
||||
/// Links all the known library addresses in the available objects. Any unknown
|
||||
/// library will still be kept as an unlinked placeholder in the objects.
|
||||
void link();
|
||||
@ -377,8 +400,9 @@ private:
|
||||
ReadCallback::Callback m_readFile;
|
||||
OptimiserSettings m_optimiserSettings;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::set<std::string> m_requestedContractNames;
|
||||
std::map<std::string, std::set<std::string>> m_requestedContractNames;
|
||||
bool m_generateIR;
|
||||
bool m_generateEWasm;
|
||||
std::map<std::string, h160> m_libraries;
|
||||
/// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum
|
||||
/// "context:prefix=target"
|
||||
@ -396,6 +420,9 @@ private:
|
||||
bool m_metadataLiteralSources = false;
|
||||
bool m_parserErrorRecovery = false;
|
||||
State m_stackState = Empty;
|
||||
/// Whether or not there has been an error during processing.
|
||||
/// If this is true, the stack will refuse to generate code.
|
||||
bool m_hasError = false;
|
||||
bool m_release = VersionIsRelease;
|
||||
};
|
||||
|
||||
|
@ -40,7 +40,8 @@ using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
using namespace yul;
|
||||
|
||||
namespace {
|
||||
namespace
|
||||
{
|
||||
|
||||
Json::Value formatError(
|
||||
bool _warning,
|
||||
@ -48,7 +49,8 @@ Json::Value formatError(
|
||||
string const& _component,
|
||||
string const& _message,
|
||||
string const& _formattedMessage = "",
|
||||
Json::Value const& _sourceLocation = Json::Value()
|
||||
Json::Value const& _sourceLocation = Json::Value(),
|
||||
Json::Value const& _secondarySourceLocation = Json::Value()
|
||||
)
|
||||
{
|
||||
Json::Value error = Json::objectValue;
|
||||
@ -59,6 +61,8 @@ Json::Value formatError(
|
||||
error["formattedMessage"] = (_formattedMessage.length() > 0) ? _formattedMessage : _message;
|
||||
if (_sourceLocation.isObject())
|
||||
error["sourceLocation"] = _sourceLocation;
|
||||
if (_secondarySourceLocation.isArray())
|
||||
error["secondarySourceLocations"] = _secondarySourceLocation;
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -70,6 +74,34 @@ Json::Value formatFatalError(string const& _type, string const& _message)
|
||||
return output;
|
||||
}
|
||||
|
||||
Json::Value formatSourceLocation(SourceLocation const* location)
|
||||
{
|
||||
Json::Value sourceLocation;
|
||||
if (location && location->source && !location->source->name().empty())
|
||||
{
|
||||
sourceLocation["file"] = location->source->name();
|
||||
sourceLocation["start"] = location->start;
|
||||
sourceLocation["end"] = location->end;
|
||||
}
|
||||
|
||||
return sourceLocation;
|
||||
}
|
||||
|
||||
Json::Value formatSecondarySourceLocation(SecondarySourceLocation const* _secondaryLocation)
|
||||
{
|
||||
if (!_secondaryLocation)
|
||||
return {};
|
||||
|
||||
Json::Value secondarySourceLocation = Json::arrayValue;
|
||||
for (auto const& location: _secondaryLocation->infos)
|
||||
{
|
||||
Json::Value msg = formatSourceLocation(&location.second);
|
||||
msg["message"] = location.first;
|
||||
secondarySourceLocation.append(msg);
|
||||
}
|
||||
return secondarySourceLocation;
|
||||
}
|
||||
|
||||
Json::Value formatErrorWithException(
|
||||
Exception const& _exception,
|
||||
bool const& _warning,
|
||||
@ -81,39 +113,35 @@ Json::Value formatErrorWithException(
|
||||
string message;
|
||||
string formattedMessage = SourceReferenceFormatter::formatExceptionInformation(_exception, _type);
|
||||
|
||||
// NOTE: the below is partially a copy from SourceReferenceFormatter
|
||||
SourceLocation const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
|
||||
|
||||
if (string const* description = boost::get_error_info<errinfo_comment>(_exception))
|
||||
message = ((_message.length() > 0) ? (_message + ":") : "") + *description;
|
||||
else
|
||||
message = _message;
|
||||
|
||||
Json::Value sourceLocation;
|
||||
if (location && location->source && location->source->name() != "")
|
||||
{
|
||||
sourceLocation["file"] = location->source->name();
|
||||
sourceLocation["start"] = location->start;
|
||||
sourceLocation["end"] = location->end;
|
||||
}
|
||||
|
||||
return formatError(_warning, _type, _component, message, formattedMessage, sourceLocation);
|
||||
return formatError(
|
||||
_warning,
|
||||
_type,
|
||||
_component,
|
||||
message,
|
||||
formattedMessage,
|
||||
formatSourceLocation(boost::get_error_info<errinfo_sourceLocation>(_exception)),
|
||||
formatSecondarySourceLocation(boost::get_error_info<errinfo_secondarySourceLocation>(_exception))
|
||||
);
|
||||
}
|
||||
|
||||
set<string> requestedContractNames(Json::Value const& _outputSelection)
|
||||
map<string, set<string>> requestedContractNames(Json::Value const& _outputSelection)
|
||||
{
|
||||
set<string> names;
|
||||
map<string, set<string>> contracts;
|
||||
for (auto const& sourceName: _outputSelection.getMemberNames())
|
||||
{
|
||||
string key = (sourceName == "*") ? "" : sourceName;
|
||||
for (auto const& contractName: _outputSelection[sourceName].getMemberNames())
|
||||
{
|
||||
/// Consider the "all sources" shortcuts as requesting everything.
|
||||
if (contractName == "*" || contractName == "")
|
||||
return set<string>();
|
||||
names.insert((sourceName == "*" ? "" : sourceName) + ":" + contractName);
|
||||
string value = (contractName == "*") ? "" : contractName;
|
||||
contracts[key].insert(value);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
return contracts;
|
||||
}
|
||||
|
||||
/// Returns true iff @a _hash (hex with 0x prefix) is the Keccak256 hash of the binary data in @a _content.
|
||||
@ -129,16 +157,17 @@ bool hashMatchesContent(string const& _hash, string const& _content)
|
||||
}
|
||||
}
|
||||
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesIR)
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact, bool _wildcardMatchesExperimental)
|
||||
{
|
||||
static set<string> experimental{"ir", "irOptimized", "wast", "ewasm", "ewasm.wast"};
|
||||
for (auto const& artifact: _outputSelection)
|
||||
/// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
|
||||
if (artifact == _artifact)
|
||||
return true;
|
||||
else if (artifact == "*")
|
||||
{
|
||||
// "ir" and "irOptimized" can only be matched by "*" if activated.
|
||||
if ((_artifact != "ir" && _artifact != "irOptimized") || _wildcardMatchesIR)
|
||||
// "ir", "irOptimized", "wast" and "ewasm.wast" can only be matched by "*" if activated.
|
||||
if (experimental.count(_artifact) == 0 || _wildcardMatchesExperimental)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -157,7 +186,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _art
|
||||
///
|
||||
/// @TODO optimise this. Perhaps flatten the structure upfront.
|
||||
///
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesIR)
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact, bool _wildcardMatchesExperimental)
|
||||
{
|
||||
if (!_outputSelection.isObject())
|
||||
return false;
|
||||
@ -174,7 +203,7 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil
|
||||
if (
|
||||
_outputSelection[file].isMember(contract) &&
|
||||
_outputSelection[file][contract].isArray() &&
|
||||
isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesIR)
|
||||
isArtifactRequested(_outputSelection[file][contract], _artifact, _wildcardMatchesExperimental)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
@ -182,10 +211,10 @@ bool isArtifactRequested(Json::Value const& _outputSelection, string const& _fil
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts, bool _wildcardMatchesIR)
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts, bool _wildcardMatchesExperimental)
|
||||
{
|
||||
for (auto const& artifact: _artifacts)
|
||||
if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesIR))
|
||||
if (isArtifactRequested(_outputSelection, _file, _contract, artifact, _wildcardMatchesExperimental))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@ -200,6 +229,7 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
|
||||
static vector<string> const outputsThatRequireBinaries{
|
||||
"*",
|
||||
"ir", "irOptimized",
|
||||
"wast", "wasm", "ewasm.wast", "ewasm.wasm",
|
||||
"evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes",
|
||||
"evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences",
|
||||
"evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap",
|
||||
@ -215,10 +245,29 @@ bool isBinaryRequested(Json::Value const& _outputSelection)
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns true if any eWasm code was requested. Note that as an exception, '*' does not
|
||||
/// yet match "ewasm.wast" or "ewasm"
|
||||
bool isEWasmRequested(Json::Value const& _outputSelection)
|
||||
{
|
||||
if (!_outputSelection.isObject())
|
||||
return false;
|
||||
|
||||
for (auto const& fileRequests: _outputSelection)
|
||||
for (auto const& requests: fileRequests)
|
||||
for (auto const& request: requests)
|
||||
if (request == "ewasm" || request == "ewasm.wast")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not
|
||||
/// yet match "ir" or "irOptimized"
|
||||
bool isIRRequested(Json::Value const& _outputSelection)
|
||||
{
|
||||
if (isEWasmRequested(_outputSelection))
|
||||
return true;
|
||||
|
||||
if (!_outputSelection.isObject())
|
||||
return false;
|
||||
|
||||
@ -231,7 +280,6 @@ bool isIRRequested(Json::Value const& _outputSelection)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
|
||||
{
|
||||
Json::Value ret(Json::objectValue);
|
||||
@ -689,9 +737,9 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
|
||||
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
|
||||
|
||||
bool const irRequested = isIRRequested(_inputsAndSettings.outputSelection);
|
||||
compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection));
|
||||
|
||||
compilerStack.enableIRGeneration(irRequested);
|
||||
compilerStack.enableEWasmGeneration(isEWasmRequested(_inputsAndSettings.outputSelection));
|
||||
|
||||
Json::Value errors = std::move(_inputsAndSettings.errors);
|
||||
|
||||
@ -796,11 +844,14 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
));
|
||||
}
|
||||
|
||||
bool const analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful;
|
||||
bool analysisPerformed = compilerStack.state() >= CompilerStack::State::AnalysisPerformed;
|
||||
bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful;
|
||||
|
||||
if (compilerStack.hasError() && !_inputsAndSettings.parserErrorRecovery)
|
||||
analysisPerformed = false;
|
||||
|
||||
/// Inconsistent state - stop here to receive error reports from users
|
||||
if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty())
|
||||
if (((binariesRequested && !compilationSuccess) || !analysisPerformed) && errors.empty())
|
||||
return formatFatalError("InternalCompilerError", "No error reported, but compilation failed.");
|
||||
|
||||
Json::Value output = Json::objectValue;
|
||||
@ -812,23 +863,23 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
for (string const& query: compilerStack.unhandledSMTLib2Queries())
|
||||
output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query;
|
||||
|
||||
bool const wildcardMatchesIR = false;
|
||||
bool const wildcardMatchesExperimental = false;
|
||||
|
||||
output["sources"] = Json::objectValue;
|
||||
unsigned sourceIndex = 0;
|
||||
for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector<string>())
|
||||
for (string const& sourceName: analysisPerformed ? compilerStack.sourceNames() : vector<string>())
|
||||
{
|
||||
Json::Value sourceResult = Json::objectValue;
|
||||
sourceResult["id"] = sourceIndex++;
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast", wildcardMatchesExperimental))
|
||||
sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST", wildcardMatchesExperimental))
|
||||
sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
|
||||
output["sources"][sourceName] = sourceResult;
|
||||
}
|
||||
|
||||
Json::Value contractsOutput = Json::objectValue;
|
||||
for (string const& contractName: analysisSuccess ? compilerStack.contractNames() : vector<string>())
|
||||
for (string const& contractName: analysisPerformed ? compilerStack.contractNames() : vector<string>())
|
||||
{
|
||||
size_t colon = contractName.rfind(':');
|
||||
solAssert(colon != string::npos, "");
|
||||
@ -837,30 +888,34 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
|
||||
// ABI, documentation and metadata
|
||||
Json::Value contractData(Json::objectValue);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi", wildcardMatchesExperimental))
|
||||
contractData["abi"] = compilerStack.contractABI(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata", wildcardMatchesExperimental))
|
||||
contractData["metadata"] = compilerStack.metadata(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc", wildcardMatchesExperimental))
|
||||
contractData["userdoc"] = compilerStack.natspecUser(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc", wildcardMatchesExperimental))
|
||||
contractData["devdoc"] = compilerStack.natspecDev(contractName);
|
||||
|
||||
// IR
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesIR))
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ir", wildcardMatchesExperimental))
|
||||
contractData["ir"] = compilerStack.yulIR(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesIR))
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "irOptimized", wildcardMatchesExperimental))
|
||||
contractData["irOptimized"] = compilerStack.yulIROptimized(contractName);
|
||||
|
||||
// eWasm
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "ewasm.wast", wildcardMatchesExperimental))
|
||||
contractData["ewasm"]["wast"] = compilerStack.eWasm(contractName);
|
||||
|
||||
// EVM
|
||||
Json::Value evmData(Json::objectValue);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesIR))
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly", wildcardMatchesExperimental))
|
||||
evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesIR))
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly", wildcardMatchesExperimental))
|
||||
evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers", wildcardMatchesExperimental))
|
||||
evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesIR))
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental))
|
||||
evmData["gasEstimates"] = compilerStack.gasEstimates(contractName);
|
||||
|
||||
if (compilationSuccess && isArtifactRequested(
|
||||
@ -868,7 +923,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
file,
|
||||
name,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
||||
wildcardMatchesIR
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
evmData["bytecode"] = collectEVMObject(
|
||||
compilerStack.object(contractName),
|
||||
@ -880,7 +935,7 @@ Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSetting
|
||||
file,
|
||||
name,
|
||||
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" },
|
||||
wildcardMatchesIR
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
evmData["deployedBytecode"] = collectEVMObject(
|
||||
compilerStack.runtimeObject(contractName),
|
||||
@ -954,8 +1009,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
|
||||
string contractName = stack.parserResult()->name.str();
|
||||
|
||||
bool const wildcardMatchesIR = true;
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesIR))
|
||||
bool const wildcardMatchesExperimental = true;
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "ir", wildcardMatchesExperimental))
|
||||
output["contracts"][sourceName][contractName]["ir"] = stack.print();
|
||||
|
||||
stack.optimize();
|
||||
@ -967,13 +1022,13 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
|
||||
sourceName,
|
||||
contractName,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" },
|
||||
wildcardMatchesIR
|
||||
wildcardMatchesExperimental
|
||||
))
|
||||
output["contracts"][sourceName][contractName]["evm"]["bytecode"] = collectEVMObject(*object.bytecode, nullptr);
|
||||
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "irOptimized", wildcardMatchesExperimental))
|
||||
output["contracts"][sourceName][contractName]["irOptimized"] = stack.print();
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesIR))
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, contractName, "evm.assembly", wildcardMatchesExperimental))
|
||||
output["contracts"][sourceName][contractName]["evm"]["assembly"] = object.assembly;
|
||||
|
||||
return output;
|
||||
|
@ -118,12 +118,15 @@ void Parser::parsePragmaVersion(SourceLocation const& _location, vector<Token> c
|
||||
static SemVerVersion const currentVersion{string(VersionString)};
|
||||
// FIXME: only match for major version incompatibility
|
||||
if (!matchExpression.matches(currentVersion))
|
||||
m_errorReporter.fatalParserError(
|
||||
_location,
|
||||
"Source file requires different compiler version (current compiler is " +
|
||||
string(VersionString) + " - note that nightly builds are considered to be "
|
||||
"strictly less than the released version"
|
||||
);
|
||||
// If m_parserErrorRecovery is true, the same message will appear from SyntaxChecker::visit(),
|
||||
// so we don't need to report anything here.
|
||||
if (!m_parserErrorRecovery)
|
||||
m_errorReporter.fatalParserError(
|
||||
_location,
|
||||
"Source file requires different compiler version (current compiler is " +
|
||||
string(VersionString) + " - note that nightly builds are considered to be "
|
||||
"strictly less than the released version"
|
||||
);
|
||||
}
|
||||
|
||||
ASTPointer<PragmaDirective> Parser::parsePragmaDirective()
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
#include <libyul/Object.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
@ -69,17 +70,19 @@ bool AsmAnalyzer::analyze(Block const& _block)
|
||||
return success && !m_errorReporter.hasErrors();
|
||||
}
|
||||
|
||||
AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Block const& _ast)
|
||||
AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object)
|
||||
{
|
||||
ErrorList errorList;
|
||||
langutil::ErrorReporter errors(errorList);
|
||||
yul::AsmAnalysisInfo analysisInfo;
|
||||
AsmAnalysisInfo analysisInfo;
|
||||
bool success = yul::AsmAnalyzer(
|
||||
analysisInfo,
|
||||
errors,
|
||||
Error::Type::SyntaxError,
|
||||
_dialect
|
||||
).analyze(_ast);
|
||||
_dialect,
|
||||
{},
|
||||
_object.dataNames()
|
||||
).analyze(*_object.code);
|
||||
solAssert(success && errorList.empty(), "Invalid assembly/yul code.");
|
||||
return analysisInfo;
|
||||
}
|
||||
@ -383,11 +386,19 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall)
|
||||
{
|
||||
if (!expectExpression(arg))
|
||||
success = false;
|
||||
else if (needsLiteralArguments && arg.type() != typeid(Literal))
|
||||
m_errorReporter.typeError(
|
||||
_funCall.functionName.location,
|
||||
"Function expects direct literals as arguments."
|
||||
);
|
||||
else if (needsLiteralArguments)
|
||||
{
|
||||
if (arg.type() != typeid(Literal))
|
||||
m_errorReporter.typeError(
|
||||
_funCall.functionName.location,
|
||||
"Function expects direct literals as arguments."
|
||||
);
|
||||
else if (!m_dataNames.count(boost::get<Literal>(arg).value))
|
||||
m_errorReporter.typeError(
|
||||
_funCall.functionName.location,
|
||||
"Unknown data object \"" + boost::get<Literal>(arg).value.str() + "\"."
|
||||
);
|
||||
}
|
||||
}
|
||||
// Use argument size instead of parameter count to avoid misleading errors.
|
||||
m_stackHeight += int(returns) - int(_funCall.arguments.size());
|
||||
|
@ -61,13 +61,15 @@ public:
|
||||
langutil::ErrorReporter& _errorReporter,
|
||||
boost::optional<langutil::Error::Type> _errorTypeForLoose,
|
||||
Dialect const& _dialect,
|
||||
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver()
|
||||
ExternalIdentifierAccess::Resolver const& _resolver = ExternalIdentifierAccess::Resolver(),
|
||||
std::set<YulString> const& _dataNames = {}
|
||||
):
|
||||
m_resolver(_resolver),
|
||||
m_info(_analysisInfo),
|
||||
m_errorReporter(_errorReporter),
|
||||
m_dialect(_dialect),
|
||||
m_errorTypeForLoose(_errorTypeForLoose)
|
||||
m_errorTypeForLoose(_errorTypeForLoose),
|
||||
m_dataNames(_dataNames)
|
||||
{
|
||||
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&m_dialect))
|
||||
m_evmVersion = evmDialect->evmVersion();
|
||||
@ -75,7 +77,9 @@ public:
|
||||
|
||||
bool analyze(Block const& _block);
|
||||
|
||||
static AsmAnalysisInfo analyzeStrictAssertCorrect(Dialect const& _dialect, Block const& _ast);
|
||||
/// Performs analysis on the outermost code of the given object and returns the analysis info.
|
||||
/// Asserts on failure.
|
||||
static AsmAnalysisInfo analyzeStrictAssertCorrect(Dialect const& _dialect, Object const& _object);
|
||||
|
||||
bool operator()(Instruction const&);
|
||||
bool operator()(Literal const& _literal);
|
||||
@ -124,6 +128,8 @@ private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
Dialect const& m_dialect;
|
||||
boost::optional<langutil::Error::Type> m_errorTypeForLoose;
|
||||
/// Names of data objects to be referenced by builtin functions with literal arguments.
|
||||
std::set<YulString> m_dataNames;
|
||||
ForLoop const* m_currentForLoop = nullptr;
|
||||
};
|
||||
|
||||
|
@ -113,7 +113,15 @@ bool AssemblyStack::analyzeParsed(Object& _object)
|
||||
{
|
||||
solAssert(_object.code, "");
|
||||
_object.analysisInfo = make_shared<AsmAnalysisInfo>();
|
||||
AsmAnalyzer analyzer(*_object.analysisInfo, m_errorReporter, boost::none, languageToDialect(m_language, m_evmVersion));
|
||||
|
||||
AsmAnalyzer analyzer(
|
||||
*_object.analysisInfo,
|
||||
m_errorReporter,
|
||||
boost::none,
|
||||
languageToDialect(m_language, m_evmVersion),
|
||||
{},
|
||||
_object.dataNames()
|
||||
);
|
||||
bool success = analyzer.analyze(*_object.code);
|
||||
for (auto& subNode: _object.subObjects)
|
||||
if (auto subObject = dynamic_cast<Object*>(subNode.get()))
|
||||
@ -153,8 +161,7 @@ void AssemblyStack::optimize(Object& _object, bool _isCreation)
|
||||
OptimiserSuite::run(
|
||||
dialect,
|
||||
meter.get(),
|
||||
*_object.code,
|
||||
*_object.analysisInfo,
|
||||
_object,
|
||||
m_optimiserSettings.optimizeStackAllocation
|
||||
);
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ add_library(yul
|
||||
backends/evm/EVMMetrics.h
|
||||
backends/evm/NoOutputAssembly.h
|
||||
backends/evm/NoOutputAssembly.cpp
|
||||
backends/wasm/EVMToEWasmTranslator.cpp
|
||||
backends/wasm/EVMToEWasmTranslator.h
|
||||
backends/wasm/EWasmCodeTransform.cpp
|
||||
backends/wasm/EWasmCodeTransform.h
|
||||
backends/wasm/EWasmObjectCompiler.cpp
|
||||
|
@ -34,7 +34,7 @@ using namespace dev;
|
||||
|
||||
map<YulString, int> CompilabilityChecker::run(
|
||||
Dialect const& _dialect,
|
||||
Block const& _ast,
|
||||
Object const& _object,
|
||||
bool _optimizeStackAllocation
|
||||
)
|
||||
{
|
||||
@ -46,16 +46,26 @@ map<YulString, int> CompilabilityChecker::run(
|
||||
if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect))
|
||||
{
|
||||
NoOutputEVMDialect noOutputDialect(*evmDialect);
|
||||
BuiltinContext builtinContext;
|
||||
|
||||
yul::AsmAnalysisInfo analysisInfo =
|
||||
yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _ast);
|
||||
yul::AsmAnalyzer::analyzeStrictAssertCorrect(noOutputDialect, _object);
|
||||
|
||||
BuiltinContext builtinContext;
|
||||
builtinContext.currentObject = &_object;
|
||||
for (auto name: _object.dataNames())
|
||||
builtinContext.subIDs[name] = 1;
|
||||
NoOutputAssembly assembly;
|
||||
CodeTransform transform(assembly, analysisInfo, _ast, noOutputDialect, builtinContext, _optimizeStackAllocation);
|
||||
CodeTransform transform(
|
||||
assembly,
|
||||
analysisInfo,
|
||||
*_object.code,
|
||||
noOutputDialect,
|
||||
builtinContext,
|
||||
_optimizeStackAllocation
|
||||
);
|
||||
try
|
||||
{
|
||||
transform(_ast);
|
||||
transform(*_object.code);
|
||||
}
|
||||
catch (StackTooDeepError const&)
|
||||
{
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/AsmDataForward.h>
|
||||
#include <libyul/Object.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@ -33,15 +34,18 @@ namespace yul
|
||||
* Component that checks whether all variables are reachable on the stack and
|
||||
* returns a mapping from function name to the largest stack difference found
|
||||
* in that function (no entry present if that function is compilable).
|
||||
*
|
||||
* This only works properly if the outermost block is compilable and
|
||||
* functions are not nested. Otherwise, it might miss reporting some functions.
|
||||
*
|
||||
* Only checks the code of the object itself, does not descend into sub-objects.
|
||||
*/
|
||||
class CompilabilityChecker
|
||||
{
|
||||
public:
|
||||
static std::map<YulString, int> run(
|
||||
Dialect const& _dialect,
|
||||
Block const& _ast,
|
||||
Object const& _object,
|
||||
bool _optimizeStackAllocation
|
||||
);
|
||||
};
|
||||
|
@ -59,3 +59,14 @@ string Object::toString(bool _yul) const
|
||||
|
||||
return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
|
||||
}
|
||||
|
||||
set<YulString> Object::dataNames() const
|
||||
{
|
||||
set<YulString> names;
|
||||
names.insert(name);
|
||||
for (auto const& subObject: subIndexByName)
|
||||
names.insert(subObject.first);
|
||||
// The empty name is not valid
|
||||
names.erase(YulString{});
|
||||
return names;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
@ -63,6 +64,10 @@ public:
|
||||
/// @returns a (parseable) string representation. Includes types if @a _yul is set.
|
||||
std::string toString(bool _yul) const override;
|
||||
|
||||
/// @returns the set of names of data objects accessible from within the code of
|
||||
/// this object.
|
||||
std::set<YulString> dataNames() const;
|
||||
|
||||
std::shared_ptr<Block> code;
|
||||
std::vector<std::shared_ptr<ObjectNode>> subObjects;
|
||||
std::map<YulString, size_t> subIndexByName;
|
||||
|
@ -42,6 +42,8 @@ using boost::is_any_of;
|
||||
|
||||
string yul::reindent(string const& _code)
|
||||
{
|
||||
int constexpr indentationWidth = 4;
|
||||
|
||||
auto const static countBraces = [](string const& _s) noexcept -> int
|
||||
{
|
||||
auto const i = _s.find("//");
|
||||
@ -65,10 +67,13 @@ string yul::reindent(string const& _code)
|
||||
if (diff < 0)
|
||||
depth += diff;
|
||||
|
||||
for (int i = 0; i < depth; ++i)
|
||||
out << '\t';
|
||||
|
||||
out << line << '\n';
|
||||
if (!line.empty())
|
||||
{
|
||||
for (int i = 0; i < depth * indentationWidth; ++i)
|
||||
out << ' ';
|
||||
out << line;
|
||||
}
|
||||
out << '\n';
|
||||
|
||||
if (diff > 0)
|
||||
depth += diff;
|
||||
|
695
libyul/backends/wasm/EVMToEWasmTranslator.cpp
Normal file
695
libyul/backends/wasm/EVMToEWasmTranslator.cpp
Normal file
@ -0,0 +1,695 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Translates Yul code from EVM dialect to eWasm dialect.
|
||||
*/
|
||||
|
||||
#include <libyul/backends/wasm/EVMToEWasmTranslator.h>
|
||||
|
||||
#include <libyul/backends/wasm/WordSizeTransform.h>
|
||||
#include <libyul/backends/wasm/WasmDialect.h>
|
||||
#include <libyul/optimiser/ExpressionSplitter.h>
|
||||
#include <libyul/optimiser/FunctionGrouper.h>
|
||||
#include <libyul/optimiser/MainFunction.h>
|
||||
#include <libyul/optimiser/FunctionHoister.h>
|
||||
#include <libyul/optimiser/Disambiguator.h>
|
||||
#include <libyul/optimiser/NameDisplacer.h>
|
||||
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/Object.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
using namespace langutil;
|
||||
|
||||
namespace
|
||||
{
|
||||
static string const polyfill{R"({
|
||||
function or_bool(a, b, c, d) -> r {
|
||||
r := i64.ne(0, i64.or(i64.or(a, b), i64.or(c, d)))
|
||||
}
|
||||
// returns a + y + c plus carry value on 64 bit values.
|
||||
// c should be at most 1
|
||||
function add_carry(x, y, c) -> r, r_c {
|
||||
let t := i64.add(x, y)
|
||||
r := i64.add(t, c)
|
||||
r_c := i64.or(
|
||||
i64.lt_u(t, x),
|
||||
i64.lt_u(r, t)
|
||||
)
|
||||
}
|
||||
function add(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
let carry
|
||||
r4, carry := add_carry(x4, y4, 0)
|
||||
r3, carry := add_carry(x3, y3, carry)
|
||||
r2, carry := add_carry(x2, y2, carry)
|
||||
r1, carry := add_carry(x1, y1, carry)
|
||||
}
|
||||
function bit_negate(x) -> y {
|
||||
y := i64.xor(x, 0xffffffffffffffff)
|
||||
}
|
||||
function sub(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// x - y = x + (~y + 1)
|
||||
let carry
|
||||
r4, carry := add_carry(x4, bit_negate(y4), 1)
|
||||
r3, carry := add_carry(x3, bit_negate(y3), carry)
|
||||
r2, carry := add_carry(x2, bit_negate(y2), carry)
|
||||
r1, carry := add_carry(x1, bit_negate(y1), carry)
|
||||
}
|
||||
function split(x) -> hi, lo {
|
||||
hi := i64.shr_u(x, 32)
|
||||
lo := i64.and(x, 0xffffffff)
|
||||
}
|
||||
// Multiplies two 64 bit values resulting in a 128 bit
|
||||
// value split into two 64 bit values.
|
||||
function mul_64x64_128(x, y) -> hi, lo {
|
||||
let xh, xl := split(x)
|
||||
let yh, yl := split(y)
|
||||
|
||||
let t0 := i64.mul(xl, yl)
|
||||
let t1 := i64.mul(xh, yl)
|
||||
let t2 := i64.mul(xl, yh)
|
||||
let t3 := i64.mul(xh, yh)
|
||||
|
||||
let t0h, t0l := split(t0)
|
||||
let u1 := i64.add(t1, t0h)
|
||||
let u1h, u1l := split(u1)
|
||||
let u2 := i64.add(t2, u1l)
|
||||
|
||||
lo := i64.or(i64.shl(u2, 32), t0l)
|
||||
hi := i64.add(t3, i64.add(i64.shr_u(u2, 32), u1h))
|
||||
}
|
||||
// Multiplies two 128 bit values resulting in a 256 bit
|
||||
// value split into four 64 bit values.
|
||||
function mul_128x128_256(x1, x2, y1, y2) -> r1, r2, r3, r4 {
|
||||
let ah, al := mul_64x64_128(x1, y1)
|
||||
let bh, bl := mul_64x64_128(x1, y2)
|
||||
let ch, cl := mul_64x64_128(x2, y1)
|
||||
let dh, dl := mul_64x64_128(x2, y2)
|
||||
|
||||
r4 := dl
|
||||
|
||||
let carry1, carry2
|
||||
let t1, t2
|
||||
|
||||
r3, carry1 := add_carry(bl, cl, 0)
|
||||
r3, carry2 := add_carry(r3, dh, 0)
|
||||
|
||||
t1, carry1 := add_carry(bh, ch, carry1)
|
||||
r2, carry2 := add_carry(t1, al, carry2)
|
||||
|
||||
r1 := i64.add(i64.add(ah, carry1), carry2)
|
||||
}
|
||||
function mul(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// TODO it would actually suffice to have mul_128x128_128 for the first two.
|
||||
let b1, b2, b3, b4 := mul_128x128_256(x3, x4, y1, y2)
|
||||
let c1, c2, c3, c4 := mul_128x128_256(x1, x2, y3, y4)
|
||||
let d1, d2, d3, d4 := mul_128x128_256(x3, x4, y3, y4)
|
||||
r4 := d4
|
||||
r3 := d3
|
||||
let t1, t2
|
||||
t1, t2, r1, r2 := add(0, 0, b3, b4, 0, 0, c3, c4)
|
||||
t1, t2, r1, r2 := add(0, 0, r1, r2, 0, 0, d1, d2)
|
||||
}
|
||||
function div(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// TODO implement properly
|
||||
r4 := i64.div_u(x4, y4)
|
||||
}
|
||||
function mod(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// TODO implement properly
|
||||
r4 := i64.rem_u(x4, y4)
|
||||
}
|
||||
function smod(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// TODO implement properly
|
||||
r4 := i64.rem_u(x4, y4)
|
||||
}
|
||||
function exp(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
// TODO implement properly
|
||||
unreachable()
|
||||
}
|
||||
|
||||
function byte(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
if i64.eqz(i64.or(i64.or(x1, x2), x3)) {
|
||||
let component
|
||||
switch i64.div_u(x4, 8)
|
||||
case 0 { component := y1 }
|
||||
case 1 { component := y2 }
|
||||
case 2 { component := y3 }
|
||||
case 3 { component := y4 }
|
||||
x4 := i64.mul(i64.rem_u(x4, 8), 8)
|
||||
r4 := i64.shr_u(component, i64.sub(56, x4))
|
||||
r4 := i64.and(0xff, r4)
|
||||
}
|
||||
}
|
||||
function xor(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
r1 := i64.xor(x1, y1)
|
||||
r2 := i64.xor(x2, y2)
|
||||
r3 := i64.xor(x3, y3)
|
||||
r4 := i64.xor(x4, y4)
|
||||
}
|
||||
function or(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
r1 := i64.or(x1, y1)
|
||||
r2 := i64.or(x2, y2)
|
||||
r3 := i64.or(x3, y3)
|
||||
r4 := i64.or(x4, y4)
|
||||
}
|
||||
function and(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
r1 := i64.and(x1, y1)
|
||||
r2 := i64.and(x2, y2)
|
||||
r3 := i64.and(x3, y3)
|
||||
r4 := i64.and(x4, y4)
|
||||
}
|
||||
function not(x1, x2, x3, x4) -> r1, r2, r3, r4 {
|
||||
let mask := 0xffffffffffffffff
|
||||
r1, r2, r3, r4 := xor(x1, x2, x3, x4, mask, mask, mask, mask)
|
||||
}
|
||||
function iszero(x1, x2, x3, x4) -> r1, r2, r3, r4 {
|
||||
r4 := i64.eqz(i64.or(i64.or(x1, x2), i64.or(x3, x4)))
|
||||
}
|
||||
function eq(x1, x2, x3, x4, y1, y2, y3, y4) -> r1, r2, r3, r4 {
|
||||
if i64.eq(x1, y1) {
|
||||
if i64.eq(x2, y2) {
|
||||
if i64.eq(x3, y3) {
|
||||
if i64.eq(x4, y4) {
|
||||
r4 := 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns 0 if a == b, -1 if a < b and 1 if a > b
|
||||
function cmp(a, b) -> r {
|
||||
switch i64.lt_u(a, b)
|
||||
case 1 { r := 0xffffffffffffffff }
|
||||
default {
|
||||
switch i64.gt_u(a, b)
|
||||
case 1 { r := 1 }
|
||||
default { r := 0 }
|
||||
}
|
||||
}
|
||||
|
||||
function lt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
switch cmp(x1, y1)
|
||||
case 0 {
|
||||
switch cmp(x2, y2)
|
||||
case 0 {
|
||||
switch cmp(x3, y3)
|
||||
case 0 {
|
||||
switch cmp(x4, y4)
|
||||
case 0 { z4 := 0 }
|
||||
case 1 { z4 := 0 }
|
||||
default { z4 := 1 }
|
||||
}
|
||||
case 1 { z4 := 0 }
|
||||
default { z4 := 1 }
|
||||
}
|
||||
case 1 { z4 := 0 }
|
||||
default { z4 := 1 }
|
||||
}
|
||||
case 1 { z4 := 0 }
|
||||
default { z4 := 1 }
|
||||
}
|
||||
function gt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
z1, z2, z3, z4 := lt(y1, y2, y3, y4, x1, x2, x3, x4)
|
||||
}
|
||||
function slt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO correct?
|
||||
x1 := i64.add(x1, 0x8000000000000000)
|
||||
y1 := i64.add(y1, 0x8000000000000000)
|
||||
z1, z2, z3, z4 := lt(x1, x2, x3, x4, y1, y2, y3, y4)
|
||||
}
|
||||
function sgt(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
z1, z2, z3, z4 := slt(y1, y2, y3, y4, x1, x2, x3, x4)
|
||||
}
|
||||
|
||||
function shl(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function shr(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function sar(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function addmod(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function mulmod(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function signextend(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
|
||||
function u256_to_i64(x1, x2, x3, x4) -> v {
|
||||
if i64.ne(0, i64.or(i64.or(x1, x2), x3)) { invalid() }
|
||||
v := x4
|
||||
}
|
||||
|
||||
function u256_to_i32(x1, x2, x3, x4) -> v {
|
||||
if i64.ne(0, i64.or(i64.or(x1, x2), x3)) { invalid() }
|
||||
if i64.ne(0, i64.shr_u(x4, 32)) { invalid() }
|
||||
v := x4
|
||||
}
|
||||
|
||||
function u256_to_i32ptr(x1, x2, x3, x4) -> v {
|
||||
v := u256_to_i32(x1, x2, x3, x4)
|
||||
}
|
||||
|
||||
function keccak256(x1, x2, x3, x4, y1, y2, y3, y4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
|
||||
function address() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getAddress(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function balance(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function origin() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getTxOrigin(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function caller() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getCaller(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function callvalue() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getCallValue(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function calldataload(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.callDataCopy(0, u256_to_i32(x1, x2, x3, x4), 32)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function calldatasize() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getCallDataSize()
|
||||
}
|
||||
function calldatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) {
|
||||
eth.callDataCopy(
|
||||
u256_to_i32ptr(x1, x2, x3, x4),
|
||||
u256_to_i32(y1, y2, y3, y4),
|
||||
u256_to_i32(z1, z2, z3, z4)
|
||||
)
|
||||
}
|
||||
|
||||
// Needed?
|
||||
function codesize() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getCodeSize(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function codecopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) {
|
||||
eth.codeCopy(
|
||||
u256_to_i32ptr(x1, x2, x3, x4),
|
||||
u256_to_i32(y1, y2, y3, y4),
|
||||
u256_to_i32(z1, z2, z3, z4)
|
||||
)
|
||||
}
|
||||
function datacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) {
|
||||
// TODO correct?
|
||||
codecopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4)
|
||||
}
|
||||
|
||||
function gasprice() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getTxGasPrice(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function extcodesize(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function extcodehash(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function extcodecopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
|
||||
function returndatasize() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getReturnDataSize()
|
||||
}
|
||||
function returndatacopy(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) {
|
||||
eth.returnDataCopy(
|
||||
u256_to_i32ptr(x1, x2, x3, x4),
|
||||
u256_to_i32(y1, y2, y3, y4),
|
||||
u256_to_i32(z1, z2, z3, z4)
|
||||
)
|
||||
}
|
||||
|
||||
function blockhash(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function coinbase() -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function timestamp() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getBlockTimestamp()
|
||||
}
|
||||
function number() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getBlockNumber()
|
||||
}
|
||||
function difficulty() -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4 := save_temp_mem_32()
|
||||
eth.getBlockDifficulty(0)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 0)
|
||||
restore_temp_mem_32(t1, t2, t3, t4)
|
||||
}
|
||||
function gaslimit() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getBlockGasLimit()
|
||||
}
|
||||
|
||||
function pop(x1, x2, x3, x4) {
|
||||
}
|
||||
|
||||
|
||||
function endian_swap_16(x) -> y {
|
||||
let hi := i64.and(i64.shl(x, 8), 0xff00)
|
||||
let lo := i64.and(i64.shr_u(x, 8), 0xff)
|
||||
y := i64.or(hi, lo)
|
||||
}
|
||||
|
||||
function endian_swap_32(x) -> y {
|
||||
let hi := i64.shl(endian_swap_16(x), 16)
|
||||
let lo := endian_swap_16(i64.shr_u(x, 16))
|
||||
y := i64.or(hi, lo)
|
||||
}
|
||||
|
||||
function endian_swap(x) -> y {
|
||||
let hi := i64.shl(endian_swap_32(x), 32)
|
||||
let lo := endian_swap_32(i64.shr_u(x, 32))
|
||||
y := i64.or(hi, lo)
|
||||
}
|
||||
function save_temp_mem_32() -> t1, t2, t3, t4 {
|
||||
t1 := i64.load(0)
|
||||
t2 := i64.load(8)
|
||||
t3 := i64.load(16)
|
||||
t4 := i64.load(24)
|
||||
}
|
||||
function restore_temp_mem_32(t1, t2, t3, t4) {
|
||||
i64.store(0, t1)
|
||||
i64.store(8, t2)
|
||||
i64.store(16, t3)
|
||||
i64.store(24, t4)
|
||||
}
|
||||
function save_temp_mem_64() -> t1, t2, t3, t4, t5, t6, t7, t8 {
|
||||
t1 := i64.load(0)
|
||||
t2 := i64.load(8)
|
||||
t3 := i64.load(16)
|
||||
t4 := i64.load(24)
|
||||
t5 := i64.load(32)
|
||||
t6 := i64.load(40)
|
||||
t7 := i64.load(48)
|
||||
t8 := i64.load(54)
|
||||
}
|
||||
function restore_temp_mem_64(t1, t2, t3, t4, t5, t6, t7, t8) {
|
||||
i64.store(0, t1)
|
||||
i64.store(8, t2)
|
||||
i64.store(16, t3)
|
||||
i64.store(24, t4)
|
||||
i64.store(32, t5)
|
||||
i64.store(40, t6)
|
||||
i64.store(48, t7)
|
||||
i64.store(54, t8)
|
||||
}
|
||||
function mload(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
let pos := u256_to_i32ptr(x1, x2, x3, x4)
|
||||
z1 := endian_swap(i64.load(pos))
|
||||
z2 := endian_swap(i64.load(i64.add(pos, 8)))
|
||||
z3 := endian_swap(i64.load(i64.add(pos, 16)))
|
||||
z4 := endian_swap(i64.load(i64.add(pos, 24)))
|
||||
}
|
||||
function mstore(x1, x2, x3, x4, y1, y2, y3, y4) {
|
||||
let pos := u256_to_i32ptr(x1, x2, x3, x4)
|
||||
i64.store(pos, endian_swap(x1))
|
||||
i64.store(i64.add(pos, 8), endian_swap(x2))
|
||||
i64.store(i64.add(pos, 16), endian_swap(x3))
|
||||
i64.store(i64.add(pos, 24), endian_swap(x4))
|
||||
}
|
||||
function mstore8(x1, x2, x3, x4, y1, y2, y3, y4) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
// Needed?
|
||||
function msize() -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function sload(x1, x2, x3, x4) -> z1, z2, z3, z4 {
|
||||
let t1, t2, t3, t4, t5, t6, t7, t8 := save_temp_mem_64()
|
||||
mstore(0, 0, 0, 0, x1, x2, x3, x4)
|
||||
eth.storageLoad(0, 16)
|
||||
z1, z2, z3, z4 := mload(0, 0, 0, 16)
|
||||
restore_temp_mem_64(t1, t2, t3, t4, t5, t6, t7, t8)
|
||||
}
|
||||
|
||||
function sstore(x1, x2, x3, x4, y1, y2, y3, y4) {
|
||||
let t1, t2, t3, t4, t5, t6, t7, t8 := save_temp_mem_64()
|
||||
mstore(0, 0, 0, 0, x1, x2, x3, x4)
|
||||
mstore(0, 0, 0, 32, y1, y2, y3, y4)
|
||||
eth.storageStore(0, 32)
|
||||
restore_temp_mem_64(t1, t2, t3, t4, t5, t6, t7, t8)
|
||||
}
|
||||
|
||||
// Needed?
|
||||
function pc() -> z1, z2, z3, z4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function gas() -> z1, z2, z3, z4 {
|
||||
z4 := eth.getGasLeft()
|
||||
}
|
||||
|
||||
function log0(p1, p2, p3, p4, s1, s2, s3, s4) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function log1(
|
||||
p1, p2, p3, p4, s1, s2, s3, s4,
|
||||
t11, t12, t13, t14
|
||||
) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function log2(
|
||||
p1, p2, p3, p4, s1, s2, s3, s4,
|
||||
t11, t12, t13, t14,
|
||||
t21, t22, t23, t24
|
||||
) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function log3(
|
||||
p1, p2, p3, p4, s1, s2, s3, s4,
|
||||
t11, t12, t13, t14,
|
||||
t21, t22, t23, t24,
|
||||
t31, t32, t33, t34
|
||||
) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function log4(
|
||||
p1, p2, p3, p4, s1, s2, s3, s4,
|
||||
t11, t12, t13, t14,
|
||||
t21, t22, t23, t24,
|
||||
t31, t32, t33, t34,
|
||||
t41, t42, t43, t44,
|
||||
) {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
|
||||
function create(x1, x2, x3, x4, y1, y2, y3, y4, z1, z2, z3, z4) -> a1, a2, a3, a4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function call(
|
||||
a1, a2, a3, a4,
|
||||
b1, b2, b3, b4,
|
||||
c1, c2, c3, c4,
|
||||
d1, d2, d3, d4,
|
||||
e1, e2, e3, e4,
|
||||
f1, f2, f3, f4,
|
||||
g1, g2, g3, g4
|
||||
) -> x1, x2, x3, x4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function callcode(
|
||||
a1, a2, a3, a4,
|
||||
b1, b2, b3, b4,
|
||||
c1, c2, c3, c4,
|
||||
d1, d2, d3, d4,
|
||||
e1, e2, e3, e4,
|
||||
f1, f2, f3, f4,
|
||||
g1, g2, g3, g4
|
||||
) -> x1, x2, x3, x4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function delegatecall(
|
||||
a1, a2, a3, a4,
|
||||
b1, b2, b3, b4,
|
||||
c1, c2, c3, c4,
|
||||
d1, d2, d3, d4,
|
||||
e1, e2, e3, e4,
|
||||
f1, f2, f3, f4
|
||||
) -> x1, x2, x3, x4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function staticcall(
|
||||
a1, a2, a3, a4,
|
||||
b1, b2, b3, b4,
|
||||
c1, c2, c3, c4,
|
||||
d1, d2, d3, d4,
|
||||
e1, e2, e3, e4,
|
||||
f1, f2, f3, f4
|
||||
) -> x1, x2, x3, x4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function create2(
|
||||
a1, a2, a3, a4,
|
||||
b1, b2, b3, b4,
|
||||
c1, c2, c3, c4,
|
||||
d1, d2, d3, d4
|
||||
) -> x1, x2, x3, x4 {
|
||||
// TODO implement
|
||||
unreachable()
|
||||
}
|
||||
function selfdestruct(a1, a2, a3, a4) {
|
||||
mstore(0, 0, 0, 0, a1, a2, a3, a4)
|
||||
// In EVM, addresses are padded to 32 bytes, so discard the first 12.
|
||||
eth.selfDestruct(12)
|
||||
}
|
||||
|
||||
function return(x1, x2, x3, x4, y1, y2, y3, y4) {
|
||||
eth.finish(
|
||||
u256_to_i32ptr(x1, x2, x3, x4),
|
||||
u256_to_i32(y1, y2, y3, y4)
|
||||
)
|
||||
}
|
||||
function revert(x1, x2, x3, x4, y1, y2, y3, y4) {
|
||||
eth.revert(
|
||||
u256_to_i32ptr(x1, x2, x3, x4),
|
||||
u256_to_i32(y1, y2, y3, y4)
|
||||
)
|
||||
}
|
||||
function invalid() {
|
||||
unreachable()
|
||||
}
|
||||
})"};
|
||||
|
||||
}
|
||||
|
||||
Object EVMToEWasmTranslator::run(Object const& _object)
|
||||
{
|
||||
if (!m_polyfill)
|
||||
parsePolyfill();
|
||||
|
||||
Block ast = boost::get<Block>(Disambiguator(m_dialect, *_object.analysisInfo)(*_object.code));
|
||||
NameDispenser nameDispenser{m_dialect, ast};
|
||||
FunctionHoister{}(ast);
|
||||
FunctionGrouper{}(ast);
|
||||
MainFunction{}(ast);
|
||||
ExpressionSplitter{m_dialect, nameDispenser}(ast);
|
||||
WordSizeTransform::run(m_dialect, ast, nameDispenser);
|
||||
|
||||
NameDisplacer{nameDispenser, m_polyfillFunctions}(ast);
|
||||
for (auto const& st: m_polyfill->statements)
|
||||
ast.statements.emplace_back(ASTCopier{}.translate(st));
|
||||
|
||||
Object ret;
|
||||
ret.code = make_shared<Block>(move(ast));
|
||||
ret.analysisInfo = make_shared<AsmAnalysisInfo>();
|
||||
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter(errors);
|
||||
AsmAnalyzer analyzer(*ret.analysisInfo, errorReporter, boost::none, WasmDialect::instance(), {}, _object.dataNames());
|
||||
if (!analyzer.analyze(*ret.code))
|
||||
{
|
||||
// TODO the errors here are "wrong" because they have invalid source references!
|
||||
string message;
|
||||
for (auto const& err: errors)
|
||||
message += langutil::SourceReferenceFormatter::formatErrorInformation(*err);
|
||||
yulAssert(false, message);
|
||||
}
|
||||
|
||||
for (auto const& subObjectNode: _object.subObjects)
|
||||
if (Object const* subObject = dynamic_cast<Object const*>(subObjectNode.get()))
|
||||
ret.subObjects.push_back(make_shared<Object>(run(*subObject)));
|
||||
else
|
||||
ret.subObjects.push_back(make_shared<Data>(dynamic_cast<Data const&>(*subObjectNode)));
|
||||
ret.subIndexByName = _object.subIndexByName;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EVMToEWasmTranslator::parsePolyfill()
|
||||
{
|
||||
ErrorList errors;
|
||||
ErrorReporter errorReporter(errors);
|
||||
shared_ptr<Scanner> scanner{make_shared<Scanner>(CharStream(polyfill, ""))};
|
||||
m_polyfill = Parser(errorReporter, WasmDialect::instance()).parse(scanner, false);
|
||||
if (!errors.empty())
|
||||
{
|
||||
string message;
|
||||
for (auto const& err: errors)
|
||||
message += langutil::SourceReferenceFormatter::formatErrorInformation(*err);
|
||||
yulAssert(false, message);
|
||||
}
|
||||
|
||||
m_polyfillFunctions.clear();
|
||||
for (auto const& statement: m_polyfill->statements)
|
||||
m_polyfillFunctions.insert(boost::get<FunctionDefinition>(statement).name);
|
||||
}
|
||||
|
46
libyul/backends/wasm/EVMToEWasmTranslator.h
Normal file
46
libyul/backends/wasm/EVMToEWasmTranslator.h
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
/**
|
||||
* Translates Yul code from EVM dialect to eWasm dialect.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/AsmDataForward.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/Dialect.h>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
struct Object;
|
||||
|
||||
class EVMToEWasmTranslator: public ASTModifier
|
||||
{
|
||||
public:
|
||||
EVMToEWasmTranslator(Dialect const& _evmDialect): m_dialect(_evmDialect) {}
|
||||
Object run(Object const& _object);
|
||||
|
||||
private:
|
||||
void parsePolyfill();
|
||||
|
||||
Dialect const& m_dialect;
|
||||
|
||||
std::shared_ptr<Block> m_polyfill;
|
||||
std::set<YulString> m_polyfillFunctions;
|
||||
};
|
||||
|
||||
}
|
@ -30,6 +30,7 @@ namespace wasm
|
||||
{
|
||||
|
||||
struct Literal;
|
||||
struct StringLiteral;
|
||||
struct LocalVariable;
|
||||
struct GlobalVariable;
|
||||
struct Label;
|
||||
@ -43,12 +44,13 @@ struct Loop;
|
||||
struct Break;
|
||||
struct Continue;
|
||||
using Expression = boost::variant<
|
||||
Literal, LocalVariable, GlobalVariable, Label,
|
||||
Literal, StringLiteral, LocalVariable, GlobalVariable, Label,
|
||||
FunctionCall, BuiltinCall, LocalAssignment, GlobalAssignment,
|
||||
Block, If, Loop, Break, Continue
|
||||
>;
|
||||
|
||||
struct Literal { uint64_t value; };
|
||||
struct StringLiteral { std::string value; };
|
||||
struct LocalVariable { std::string name; };
|
||||
struct GlobalVariable { std::string name; };
|
||||
struct Label { std::string name; };
|
||||
@ -68,6 +70,13 @@ struct Continue { Label label; };
|
||||
|
||||
struct VariableDeclaration { std::string variableName; };
|
||||
struct GlobalVariableDeclaration { std::string variableName; };
|
||||
struct FunctionImport {
|
||||
std::string module;
|
||||
std::string externalName;
|
||||
std::string internalName;
|
||||
std::vector<std::string> paramTypes;
|
||||
std::unique_ptr<std::string> returnType;
|
||||
};
|
||||
|
||||
struct FunctionDefinition
|
||||
{
|
||||
|
@ -48,10 +48,18 @@ string EWasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast)
|
||||
statement.type() == typeid(yul::FunctionDefinition),
|
||||
"Expected only function definitions at the highest level."
|
||||
);
|
||||
functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement)));
|
||||
if (statement.type() == typeid(yul::FunctionDefinition))
|
||||
functions.emplace_back(transform.translateFunction(boost::get<yul::FunctionDefinition>(statement)));
|
||||
}
|
||||
|
||||
return EWasmToText{}.run(transform.m_globalVariables, functions);
|
||||
std::vector<wasm::FunctionImport> imports;
|
||||
for (auto& imp: transform.m_functionsToImport)
|
||||
imports.emplace_back(std::move(imp.second));
|
||||
return EWasmToText{}.run(
|
||||
transform.m_globalVariables,
|
||||
imports,
|
||||
functions
|
||||
);
|
||||
}
|
||||
|
||||
wasm::Expression EWasmCodeTransform::generateMultiAssignment(
|
||||
@ -65,6 +73,8 @@ wasm::Expression EWasmCodeTransform::generateMultiAssignment(
|
||||
if (_variableNames.size() == 1)
|
||||
return { std::move(assignment) };
|
||||
|
||||
allocateGlobals(_variableNames.size() - 1);
|
||||
|
||||
wasm::Block block;
|
||||
block.statements.emplace_back(move(assignment));
|
||||
for (size_t i = 1; i < _variableNames.size(); ++i)
|
||||
@ -123,14 +133,42 @@ wasm::Expression EWasmCodeTransform::operator()(FunctionalInstruction const& _f)
|
||||
|
||||
wasm::Expression EWasmCodeTransform::operator()(FunctionCall const& _call)
|
||||
{
|
||||
if (m_dialect.builtin(_call.functionName.name))
|
||||
return wasm::BuiltinCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||
else
|
||||
// If this function returns multiple values, then the first one will
|
||||
// be returned in the expression itself and the others in global variables.
|
||||
// The values have to be used right away in an assignment or variable declaration,
|
||||
// so it is handled there.
|
||||
return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||
if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name))
|
||||
{
|
||||
if (_call.functionName.name.str().substr(0, 4) == "eth.")
|
||||
{
|
||||
yulAssert(builtin->returns.size() <= 1, "");
|
||||
// Imported function, use regular call, but mark for import.
|
||||
if (!m_functionsToImport.count(builtin->name))
|
||||
{
|
||||
wasm::FunctionImport imp{
|
||||
"ethereum",
|
||||
builtin->name.str().substr(4),
|
||||
builtin->name.str(),
|
||||
{},
|
||||
builtin->returns.empty() ? nullptr : make_unique<string>(builtin->returns.front().str())
|
||||
};
|
||||
for (auto const& param: builtin->parameters)
|
||||
imp.paramTypes.emplace_back(param.str());
|
||||
m_functionsToImport[builtin->name] = std::move(imp);
|
||||
}
|
||||
}
|
||||
else if (builtin->literalArguments)
|
||||
{
|
||||
vector<wasm::Expression> literals;
|
||||
for (auto const& arg: _call.arguments)
|
||||
literals.emplace_back(wasm::StringLiteral{boost::get<Literal>(arg).value.str()});
|
||||
return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)};
|
||||
}
|
||||
else
|
||||
return wasm::BuiltinCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||
}
|
||||
|
||||
// If this function returns multiple values, then the first one will
|
||||
// be returned in the expression itself and the others in global variables.
|
||||
// The values have to be used right away in an assignment or variable declaration,
|
||||
// so it is handled there.
|
||||
return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)};
|
||||
}
|
||||
|
||||
wasm::Expression EWasmCodeTransform::operator()(Identifier const& _identifier)
|
||||
@ -141,7 +179,7 @@ wasm::Expression EWasmCodeTransform::operator()(Identifier const& _identifier)
|
||||
wasm::Expression EWasmCodeTransform::operator()(Literal const& _literal)
|
||||
{
|
||||
u256 value = valueOfLiteral(_literal);
|
||||
yulAssert(value <= numeric_limits<uint64_t>::max(), "");
|
||||
yulAssert(value <= numeric_limits<uint64_t>::max(), "Literal too large: " + value.str());
|
||||
return wasm::Literal{uint64_t(value)};
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <libyul/optimiser/NameDispenser.h>
|
||||
|
||||
#include <stack>
|
||||
#include <map>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
@ -90,6 +91,7 @@ private:
|
||||
|
||||
std::vector<wasm::VariableDeclaration> m_localVariables;
|
||||
std::vector<wasm::GlobalVariableDeclaration> m_globalVariables;
|
||||
std::map<YulString, wasm::FunctionImport> m_functionsToImport;
|
||||
std::stack<std::pair<std::string, std::string>> m_breakContinueLabelNames;
|
||||
};
|
||||
|
||||
|
@ -28,15 +28,24 @@
|
||||
|
||||
using namespace std;
|
||||
using namespace yul;
|
||||
using namespace dev;
|
||||
|
||||
string EWasmToText::run(
|
||||
vector<wasm::GlobalVariableDeclaration> const& _globals,
|
||||
vector<wasm::FunctionImport> const& _imports,
|
||||
vector<wasm::FunctionDefinition> const& _functions
|
||||
)
|
||||
{
|
||||
string ret = "(module\n";
|
||||
// TODO Add all the interface functions:
|
||||
// ret += " (import \"ethereum\" \"getBalance\" (func $getBalance (param i32 i32)))\n";
|
||||
for (wasm::FunctionImport const& imp: _imports)
|
||||
{
|
||||
ret += " (import \"" + imp.module + "\" \"" + imp.externalName + "\" (func $" + imp.internalName;
|
||||
if (!imp.paramTypes.empty())
|
||||
ret += " (param" + joinHumanReadablePrefixed(imp.paramTypes, " ", " ") + ")";
|
||||
if (imp.returnType)
|
||||
ret += " (result " + *imp.returnType + ")";
|
||||
ret += "))\n";
|
||||
}
|
||||
|
||||
// allocate one 64k page of memory and make it available to the Ethereum client
|
||||
ret += " (memory $memory (export \"memory\") 1)\n";
|
||||
@ -56,6 +65,13 @@ string EWasmToText::operator()(wasm::Literal const& _literal)
|
||||
return "(i64.const " + to_string(_literal.value) + ")";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::StringLiteral const& _literal)
|
||||
{
|
||||
string quoted = boost::replace_all_copy(_literal.value, "\\", "\\\\");
|
||||
boost::replace_all(quoted, "\"", "\\\"");
|
||||
return "\"" + quoted + "\"";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::LocalVariable const& _identifier)
|
||||
{
|
||||
return "(get_local $" + _identifier.name + ")";
|
||||
@ -73,12 +89,14 @@ string EWasmToText::operator()(wasm::Label const& _label)
|
||||
|
||||
string EWasmToText::operator()(wasm::BuiltinCall const& _builtinCall)
|
||||
{
|
||||
return "(" + _builtinCall.functionName + " " + joinTransformed(_builtinCall.arguments) + ")";
|
||||
string args = joinTransformed(_builtinCall.arguments);
|
||||
return "(" + _builtinCall.functionName + (args.empty() ? "" : " " + args) + ")";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::FunctionCall const& _functionCall)
|
||||
{
|
||||
return "(call $" + _functionCall.functionName + " " + joinTransformed(_functionCall.arguments) + ")";
|
||||
string args = joinTransformed(_functionCall.arguments);
|
||||
return "(call $" + _functionCall.functionName + (args.empty() ? "" : " " + args) + ")";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::LocalAssignment const& _assignment)
|
||||
@ -93,16 +111,16 @@ string EWasmToText::operator()(wasm::GlobalAssignment const& _assignment)
|
||||
|
||||
string EWasmToText::operator()(wasm::If const& _if)
|
||||
{
|
||||
string text = "(if " + visit(*_if.condition) + " (then\n" + indented(joinTransformed(_if.statements)) + ")";
|
||||
string text = "(if " + visit(*_if.condition) + " (then\n" + indented(joinTransformed(_if.statements, '\n')) + ")";
|
||||
if (_if.elseStatements)
|
||||
text += "(else\n" + indented(joinTransformed(*_if.elseStatements)) + ")";
|
||||
text += "(else\n" + indented(joinTransformed(*_if.elseStatements, '\n')) + ")";
|
||||
return std::move(text) + ")\n";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::Loop const& _loop)
|
||||
{
|
||||
string label = _loop.labelName.empty() ? "" : " $" + _loop.labelName;
|
||||
return "(loop" + move(label) + "\n" + indented(joinTransformed(_loop.statements)) + ")\n";
|
||||
return "(loop" + move(label) + "\n" + indented(joinTransformed(_loop.statements, '\n')) + ")\n";
|
||||
}
|
||||
|
||||
string EWasmToText::operator()(wasm::Break const& _break)
|
||||
@ -118,17 +136,22 @@ string EWasmToText::operator()(wasm::Continue const& _continue)
|
||||
string EWasmToText::operator()(wasm::Block const& _block)
|
||||
{
|
||||
string label = _block.labelName.empty() ? "" : " $" + _block.labelName;
|
||||
return "(block" + move(label) + "\n" + indented(joinTransformed(_block.statements)) + "\n)\n";
|
||||
return "(block" + move(label) + "\n" + indented(joinTransformed(_block.statements, '\n')) + "\n)\n";
|
||||
}
|
||||
|
||||
string EWasmToText::indented(string const& _in)
|
||||
{
|
||||
string replacement;
|
||||
|
||||
if (!_in.empty())
|
||||
{
|
||||
replacement = " " + boost::replace_all_copy(_in, "\n", "\n ");
|
||||
if (_in.back() == '\n')
|
||||
replacement = replacement.substr(0, replacement.size() - 4);
|
||||
replacement.reserve(_in.size() + 4);
|
||||
replacement += " ";
|
||||
for (auto it = _in.begin(); it != _in.end(); ++it)
|
||||
if (*it == '\n' && it + 1 != _in.end() && *(it + 1) != '\n')
|
||||
replacement += "\n ";
|
||||
else
|
||||
replacement += *it;
|
||||
}
|
||||
return replacement;
|
||||
}
|
||||
@ -155,14 +178,14 @@ string EWasmToText::visit(wasm::Expression const& _expression)
|
||||
return boost::apply_visitor(*this, _expression);
|
||||
}
|
||||
|
||||
string EWasmToText::joinTransformed(vector<wasm::Expression> const& _expressions)
|
||||
string EWasmToText::joinTransformed(vector<wasm::Expression> const& _expressions, char _separator)
|
||||
{
|
||||
string ret;
|
||||
for (auto const& e: _expressions)
|
||||
{
|
||||
string t = visit(e);
|
||||
if (!t.empty() && !ret.empty() && ret.back() != '\n')
|
||||
ret += ' ';
|
||||
ret += _separator;
|
||||
ret += move(t);
|
||||
}
|
||||
return ret;
|
||||
|
@ -33,11 +33,13 @@ class EWasmToText: public boost::static_visitor<std::string>
|
||||
public:
|
||||
std::string run(
|
||||
std::vector<wasm::GlobalVariableDeclaration> const& _globals,
|
||||
std::vector<wasm::FunctionImport> const& _imports,
|
||||
std::vector<wasm::FunctionDefinition> const& _functions
|
||||
);
|
||||
|
||||
public:
|
||||
std::string operator()(wasm::Literal const& _literal);
|
||||
std::string operator()(wasm::StringLiteral const& _literal);
|
||||
std::string operator()(wasm::LocalVariable const& _identifier);
|
||||
std::string operator()(wasm::GlobalVariable const& _identifier);
|
||||
std::string operator()(wasm::Label const& _label);
|
||||
@ -57,7 +59,10 @@ private:
|
||||
std::string transform(wasm::FunctionDefinition const& _function);
|
||||
|
||||
std::string visit(wasm::Expression const& _expression);
|
||||
std::string joinTransformed(std::vector<wasm::Expression> const& _expressions);
|
||||
std::string joinTransformed(
|
||||
std::vector<wasm::Expression> const& _expressions,
|
||||
char _separator = ' '
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -47,11 +47,26 @@ WasmDialect::WasmDialect():
|
||||
addFunction(name, 2, 1);
|
||||
|
||||
addFunction("i64.eqz", 1, 1);
|
||||
addFunction("i64.store", 2, 0);
|
||||
addFunction("i64.load", 1, 1);
|
||||
|
||||
addFunction("i64.store", 2, 0, false);
|
||||
m_functions["i64.store"_yulstring].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;
|
||||
|
||||
addFunction("drop", 1, 0);
|
||||
addFunction("unreachable", 0, 0);
|
||||
|
||||
addFunction("unreachable", 0, 0, false);
|
||||
m_functions["unreachable"_yulstring].invalidatesStorage = false;
|
||||
m_functions["unreachable"_yulstring].invalidatesMemory = false;
|
||||
|
||||
addFunction("datasize", 1, 4, true, true);
|
||||
addFunction("dataoffset", 1, 4, true, true);
|
||||
|
||||
addEthereumExternals();
|
||||
}
|
||||
|
||||
BuiltinFunction const* WasmDialect::builtin(YulString _name) const
|
||||
@ -72,18 +87,87 @@ WasmDialect const& WasmDialect::instance()
|
||||
return *dialect;
|
||||
}
|
||||
|
||||
void WasmDialect::addFunction(string _name, size_t _params, size_t _returns)
|
||||
void WasmDialect::addEthereumExternals()
|
||||
{
|
||||
// These are not YulStrings because that would be too complicated with regards
|
||||
// to the YulStringRepository reset.
|
||||
static string const i64{"i64"};
|
||||
static string const i32{"i32"};
|
||||
static string const i32ptr{"i32"}; // Uses "i32" on purpose.
|
||||
struct External { string name; vector<string> parameters; vector<string> returns; };
|
||||
static vector<External> externals{
|
||||
{"getAddress", {i32ptr}, {}},
|
||||
{"getExternalBalance", {i32ptr, i32ptr}, {}},
|
||||
{"getBlockHash", {i64, i32ptr}, {i32}},
|
||||
{"call", {i64, i32ptr, i32ptr, i32ptr, i32}, {i32}},
|
||||
{"callDataCopy", {i32ptr, i32, i32}, {}},
|
||||
{"getCallDataSize", {}, {i32}},
|
||||
{"callCode", {i64, i32ptr, i32ptr, i32ptr, i32}, {i32}},
|
||||
{"callDelegate", {i64, i32ptr, i32ptr, i32}, {i32}},
|
||||
{"callStatic", {i64, i32ptr, i32ptr, i32}, {i32}},
|
||||
{"storageStore", {i32ptr, i32ptr}, {}},
|
||||
{"storageLoad", {i32ptr, i32ptr}, {}},
|
||||
{"getCaller", {i32ptr}, {}},
|
||||
{"getCallValue", {i32ptr}, {}},
|
||||
{"codeCopy", {i32ptr, i32, i32}, {}},
|
||||
{"getCodeSize", {i32ptr}, {}},
|
||||
{"getBlockCoinbase", {i32ptr}, {}},
|
||||
{"create", {i32ptr, i32ptr, i32, i32ptr}, {i32}},
|
||||
{"getBlockDifficulty", {i32ptr}, {}},
|
||||
{"externalCodeCopy", {i32ptr, i32ptr, i32, i32}, {}},
|
||||
{"getExternalCodeSize", {i32ptr}, {i32}},
|
||||
{"getGasLeft", {}, {i64}},
|
||||
{"getBlockGasLimit", {}, {i64}},
|
||||
{"getTxGasPrice", {i32ptr}, {}},
|
||||
{"log", {i32ptr, i32, i32, i32ptr, i32ptr, i32ptr, i32ptr}, {}},
|
||||
{"getBlockNumber", {}, {i64}},
|
||||
{"getTxOrigin", {i32ptr}, {}},
|
||||
{"finish", {i32ptr, i32}, {}},
|
||||
{"revert", {i32ptr, i32}, {}},
|
||||
{"getReturnDataSize", {}, {i32}},
|
||||
{"returnDataCopy", {i32ptr, i32, i32}, {}},
|
||||
{"selfDestruct", {i32ptr}, {}},
|
||||
{"getBlockTimestamp", {}, {i64}}
|
||||
};
|
||||
for (External const& ext: externals)
|
||||
{
|
||||
YulString name{"eth." + ext.name};
|
||||
BuiltinFunction& f = m_functions[name];
|
||||
f.name = name;
|
||||
for (string const& p: ext.parameters)
|
||||
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.isMSize = false;
|
||||
f.invalidatesStorage = (ext.name == "storageStore");
|
||||
// TODO some of them do not invalidate memory
|
||||
f.invalidatesMemory = true;
|
||||
f.literalArguments = false;
|
||||
}
|
||||
}
|
||||
|
||||
void WasmDialect::addFunction(
|
||||
string _name,
|
||||
size_t _params,
|
||||
size_t _returns,
|
||||
bool _movable,
|
||||
bool _literalArguments
|
||||
)
|
||||
{
|
||||
YulString name{move(_name)};
|
||||
BuiltinFunction& f = m_functions[name];
|
||||
f.name = name;
|
||||
f.parameters.resize(_params);
|
||||
f.returns.resize(_returns);
|
||||
f.movable = false;
|
||||
f.sideEffectFree = false;
|
||||
f.sideEffectFreeIfNoMSize = false;
|
||||
f.movable = _movable;
|
||||
f.sideEffectFree = _movable;
|
||||
f.sideEffectFreeIfNoMSize = _movable;
|
||||
f.isMSize = false;
|
||||
f.invalidatesStorage = true;
|
||||
f.invalidatesMemory = true;
|
||||
f.literalArguments = false;
|
||||
f.invalidatesStorage = !_movable;
|
||||
f.invalidatesMemory = !_movable;
|
||||
f.literalArguments = _literalArguments;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user