Merge pull request #4148 from ethereum/develop

Merge develop into release for 0.4.24
This commit is contained in:
chriseth 2018-05-16 14:43:57 +02:00 committed by GitHub
commit e67f014799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
254 changed files with 4904 additions and 1600 deletions

View File

@ -173,7 +173,7 @@ before_script:
&& scripts/create_source_tarball.sh)
script:
- test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh)
- test $SOLC_EMSCRIPTEN != On -o $SOLC_TESTS != On || (scripts/test_emscripten.sh)
- test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
- test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh)

View File

@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.4.23")
set(PROJECT_VERSION "0.4.24")
project(solidity VERSION ${PROJECT_VERSION})
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
@ -21,6 +21,7 @@ include(EthCcache)
# Let's find our dependencies
include(EthDependencies)
include(jsoncpp)
include_directories(SYSTEM ${JSONCPP_INCLUDE_DIR})
find_package(Threads)

View File

@ -49,21 +49,28 @@ cout << "some very long string that contains completely irrelevant text that tal
## 1. Namespaces
1. No `using namespace` declarations in header files.
2. All symbols should be declared in a namespace except for final applications.
3. Use anonymous namespaces for helpers whose scope is a cpp file only.
4. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore.
2. Use `using namespace std;` in cpp files, but avoid importing namespaces from boost and others.
3. All symbols should be declared in a namespace except for final applications.
4. Use anonymous namespaces for helpers whose scope is a cpp file only.
5. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore.
Yes:
Only in the header:
```cpp
#include <cassert>
namespace myNamespace
{
std::tuple<float, float> meanAndSigma(std::vector<float> const& _v);
}
```
No:
Only in the cpp file:
```cpp
#include <cassert>
using namespace std;
tuple<float, float> meanAndSigma(vector<float> const& _v);
tuple<float, float> myNamespace::meanAndSigma(vector<float> const& _v)
{
// ...
}
```
## 2. Preprocessor

View File

@ -1,3 +1,30 @@
### 0.4.24 (2018-05-16)
Language Features:
* Code Generator: Use native shift instructions on target Constantinople.
* General: Allow multiple variables to be declared as part of a tuple assignment, e.g. ``(uint a, uint b) = ...``.
* General: Remove deprecated ``constant`` as function state modifier from documentation and tests (but still leave it as a valid feature).
* Type Checker: Deprecate the ``years`` unit denomination and raise a warning for it (or an error as experimental 0.5.0 feature).
* Type Checker: Make literals (without explicit type casting) an error for tight packing as experimental 0.5.0 feature.
* Type Checker: Warn about wildcard tuple assignments (this will turn into an error with version 0.5.0).
* Type Checker: Warn when ``keccak256``, ``sha256`` and ``ripemd160`` are not used with a single bytes argument (suggest to use ``abi.encodePacked(...)``). This will turn into an error with version 0.5.0.
Compiler Features:
* Build System: Update internal dependency of jsoncpp to 1.8.4, which introduces more strictness and reduces memory usage.
* Control Flow Graph: Add Control Flow Graph as analysis structure.
* Control Flow Graph: Warn about returning uninitialized storage pointers.
* Gas Estimator: Only explore paths with higher gas costs. This reduces accuracy but greatly improves the speed of gas estimation.
* Optimizer: Remove unnecessary masking of the result of known short instructions (``ADDRESS``, ``CALLER``, ``ORIGIN`` and ``COINBASE``).
* Parser: Display nicer error messages by showing the actual tokens and not internal names.
* Parser: Use the entire location of the token instead of only its starting position as source location for parser errors.
* SMT Checker: Support state variables of integer and bool type.
Bugfixes:
* Code Generator: Fix ``revert`` with reason coming from a state or local string variable.
* Type Checker: Show proper error when trying to ``emit`` a non-event.
* Type Checker: Warn about empty tuple components (this will turn into an error with version 0.5.0).
* Type Checker: The ABI encoding functions are pure and thus can be used for constants.
### 0.4.23 (2018-04-19)
Features:

View File

@ -31,10 +31,12 @@ branches:
only:
- release
- develop
os: Visual Studio 2015
configuration:
- RelWithDebInfo
environment:
matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
# This is used for pushing to solidity-test-bytecodes
priv_key:
secure: yYGwg4rhCdHfwuv2mFjaNEDwAx3IKUbp0D5fMGpaKefnfk+BiMS5bqSHRiOj91PZ91P9pUk2Vu+eNuS4hTFCf1zFGfrOhlJ4Ij0xSyU5m/LQr590Mo+f7W94Xc8ubgo6j2hp9qH/szTqTzmAkmxKO5TLlWjVzVny2t/s5o5UprLS1/MdzDNLjpVNXR03oKfdWUV9a2l6+PejXCbqyUCagh6BByZqeAPbDcil6eAfxu4EPX83Fuurof+KqFzIWycBG5qK1pTipn2pxiA0QKuUrD8y8VNL0S23NTgxoxSp7nPVMd3K0qRSzPM5lrqS7Z8i3evkVwPbuhu0gSiV08jGVahH2snQ3JGYsH2D4KmVn/xiVBeJ0lRplYlfZF0GUu7iJ+DDxi6wBPhW9A25/NyD/mx7Ub2dLheyWi8AjdSCzhfRD+4We8FQQeHRo3Q0kAohFmlCXdXhrcwOOloId8r6xYwg+hWxHTt2Oe9CKwXfmiPjgl/Gd6lYgLpyyfJ8drQ6tjO/pybLEa10v74qYNdVW5LaLIsRUM9Jm/FDVTrOGYtPndi87mF+/tBJIaXXNz0EMl5xvsKW0SBfUMV49zoDDKZZgWyO9U/cfViEUi7Sdn9QLsBWLZfSgBQNkq3WGZVKPq58OxEWT9dUghQHlSVh2qWF/NUx0TRBjiJl9JM56ENTMD00y18eDcXNCeLLVYB+R1axabUPdXivrO+BrWQK94IWxKEJ+YYN8WVJWAO5T/EBDKwgiXGneePwJ75WP7XCLtuYxqjC+CeW3xBVCzCEeZB/VVBvt7fhmtcoeZZ6tAS10h0yY5WWZ/EUVorj+c/FrMm7Nlpcrd1p4hciffePSLVg+yvy9/xTuM9trYWMgj4xcDQbYsaeItHO2Z3EiUoCgNdUw6rONiNwS/XBApWhCcklWm0/g62h2gOa7/hnKG6p2omQzYw+cOzWbF9+DBzoTSXXZXqbUshVee+CD+iYJKleGYSdbMdM89HW4HyskHk6HgM1ggE8CsgD1pMhXtqLTYZBlvsZCBkHPkD9NhGD2DtrNOmJOW8xwkL2/Il6roDF4n856XNdsjvd++rvQoKr58SkyApCJeCo3sfVres0W22g+7If2b2kWC4/DphrFkeaceFzJOctBUrwstvQBXIVOcadU978A3E7jvTaMR4JL9kC/iPOUVNjNRNM/gNvTlf3CIyMMszFeftjEBGnCZaSpht2RtNapRQQb6QPkOP88nufQVZq/TP1ECmvdTUWJ7kSnAupu6u8oH2x2IIm/KKeIwSYU5rGxjRb36DwgXCHcwfRYo3VNorwTeZGj4q1TSM9PuvgzNg//gKZW6VRa+HdNm/40ZGpDsOrr55tOBqfpq9k5RmevqW/OMZS3xUuArKdYLQY75t9eWcbHSgFN2ZY1KEdyEEvVKgs6Q4lEnSSulGxroRxTU5BOoA0V4tCeCUoSPD3FB93WsO9fBPzNsqOuBtDdIkApefzc1pT38uKpmVfggKUsoWUdqMXAWqCDWr2uw9EE900RJpEY6mIEWhkcro5LAMwaqByOGpqFFUkH+UWTC102eVHEmjxKpC6c6cSzoKKU6Ckd+jVRFO7TvmVe1MKCwjXj8lcAfAM2gQ+XehtrQdIBhAmCrnzurfz2u9tKVdpiADC1ig+kMs1/HX2713LYVXzDKdk+duQ94SVtGv9F2Iv+KN5oq4UFgll6VGt7GHsJOrYYf/wrOfB09IkpmjNygvcpmmSdcXXF8ulDD6KHTGEGUlFwLOpEwKx+zX2ZvviStHhN8KsoTKSVSueDmSSI63HdTS7FxfrHJc1yAzsdqEN5g5eV/z2Fn34qy64mdFSAZMF5zsbWZYFpc9ef3llF5aRcuD90JWT2VC7rB2jeGEtiwGkDlqKzxqRvJk06wTK6+n5RncN66bDaksulOPJMAR/bRW7dinV8T6yIvybuhqDetxJQP6eyAnW4xr1YxIAG4BXGZV6XAPTgOG2oGvMdncxkcLQHXVu07x39ySqP/m2MBxn0zF3DmaqrSPIRMhS8gG3d/23Jux3YHDEOBHjdJSdwqs5F5+QBFPV2rmJnpcSoW4d3M119XI20L914c62R7wY4e6+qmi3ydQU9g6p8psZgaE3TuMsyzX3k4C30nC/3gWT+zl253NjZwfbzIdHu5LWNDY9kEHtKzLP
@ -62,11 +64,12 @@ install:
before_build:
- if not exist build mkdir build
- cd build
- cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On
- if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2015" ( cmake -G "Visual Studio 14 2015 Win64" .. -DTESTS=On )
else ( cmake -G "Visual Studio 15 2017 Win64" .. -DTESTS=On )
build_script:
- msbuild solidity.sln /p:Configuration=%CONFIGURATION% /m:%NUMBER_OF_PROCESSORS% /v:minimal
- cd %APPVEYOR_BUILD_FOLDER%
- scripts\release.bat %CONFIGURATION%
- if "%APPVEYOR_BUILD_WORKER_IMAGE%" == "Visual Studio 2017" ( scripts\release.bat %CONFIGURATION% 2017 )
- ps: $bytecodedir = git show -s --format="%cd-%H" --date=short
test_script:

View File

@ -4,12 +4,38 @@ defaults:
filters:
tags:
only: /.*/
- setup_prerelease_commit_hash: &setup_prerelease_commit_hash
name: Store commit hash and prerelease
command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
- run_build: &run_build
name: Build
command: |
mkdir -p build
cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4
- run_tests: &run_tests
name: Tests
command: scripts/tests.sh --junit_report test_results
- solc_artifact: &solc_artifact
path: build/solc/solc
destination: solc
- all_artifacts: &all_artifacts
root: build
paths:
- solc/solc
- test/soltest
- test/tools/solfuzzer
version: 2
jobs:
build_emscripten:
docker:
- image: trzeci/emscripten:sdk-tag-1.37.21-64bit
environment:
TERM: xterm
steps:
- checkout
- restore_cache:
@ -42,6 +68,8 @@ jobs:
test_emscripten_solcjs:
docker:
- image: trzeci/emscripten:sdk-tag-1.37.21-64bit
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
@ -66,6 +94,8 @@ jobs:
test_emscripten_external:
docker:
- image: trzeci/emscripten:sdk-tag-1.37.21-64bit
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
@ -87,9 +117,11 @@ jobs:
command: |
. /usr/local/nvm/nvm.sh
test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js
build_x86:
build_x86_linux:
docker:
- image: buildpack-deps:artful
environment:
TERM: xterm
steps:
- checkout
- run:
@ -97,31 +129,37 @@ jobs:
command: |
apt-get -qq update
apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libz3-dev
- run:
name: Store commit hash and prerelease
command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
- run:
name: Build
command: |
mkdir -p build
cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4
- store_artifacts:
path: build/solc/solc
destination: solc
- persist_to_workspace:
root: build
paths:
- solc/solc
- test/soltest
- test/tools/solfuzzer
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace: *all_artifacts
test_x86:
build_x86_mac:
macos:
xcode: "9.0"
environment:
TERM: xterm
steps:
- checkout
- run:
name: Install build dependencies
command: |
brew update
brew upgrade
brew unlink python
brew install z3
brew install boost
brew install cmake
- run: *setup_prerelease_commit_hash
- run: *run_build
- store_artifacts: *solc_artifact
- persist_to_workspace: *all_artifacts
test_x86_linux:
docker:
- image: buildpack-deps:artful
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
@ -132,9 +170,28 @@ jobs:
apt-get -qq update
apt-get -qy install libz3-dev libleveldb1v5
- run: mkdir -p test_results
- run: *run_tests
- store_test_results:
path: test_results/
test_x86_mac:
macos:
xcode: "9.0"
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: build
- run:
name: Tests
command: scripts/tests.sh --junit_report test_results
name: Install dependencies
command: |
brew update
brew upgrade
brew unlink python
brew install z3
- run: mkdir -p test_results
- run: *run_tests
- store_test_results:
path: test_results/
@ -148,11 +205,7 @@ jobs:
command: |
apt-get -qq update
apt-get -qy install python-sphinx
- run:
name: Store commit hash and prerelease
command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
- run: *setup_prerelease_commit_hash
- run:
name: Build documentation
command: ./scripts/docs.sh
@ -173,9 +226,14 @@ workflows:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86: *build_on_tags
- test_x86:
- build_x86_linux: *build_on_tags
- build_x86_mac: *build_on_tags
- test_x86_linux:
<<: *build_on_tags
requires:
- build_x86
- build_x86_linux
- test_x86_mac:
<<: *build_on_tags
requires:
- build_x86_mac
- docs: *build_on_tags

View File

@ -62,8 +62,9 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA
# Additional Clang-specific compiler settings.
elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
if ("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
# Set stack size to 16MB - by default Apple's clang defines a stack size of 8MB, some tests require more.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size -Wl,0x1000000")
# Set stack size to 32MB - by default Apple's clang defines a stack size of 8MB.
# Normally 16MB is enough to run all tests, but it will exceed the stack, if -DSANITIZE=address is used.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-stack_size -Wl,0x2000000")
endif()
# Some Linux-specific Clang settings. We don't want these for OS X.

View File

@ -6,17 +6,15 @@ else()
set(JSONCPP_CMAKE_COMMAND ${CMAKE_COMMAND})
endif()
# Disable implicit fallthrough warning in jsoncpp for gcc >= 7 until the upstream handles it properly
if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
set(JSONCCP_EXTRA_FLAGS -Wno-implicit-fallthrough)
else()
set(JSONCCP_EXTRA_FLAGS "")
endif()
include(GNUInstallDirs)
set(prefix "${CMAKE_BINARY_DIR}/deps")
set(JSONCPP_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(JSONCPP_LIBRARY "${prefix}/${CMAKE_INSTALL_LIBDIR}/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}")
set(JSONCPP_INCLUDE_DIR "${prefix}/include")
if(NOT MSVC)
set(JSONCPP_EXTRA_FLAGS "-std=c++11")
endif()
set(byproducts "")
if(CMAKE_VERSION VERSION_GREATER 3.1)
set(byproducts BUILD_BYPRODUCTS "${JSONCPP_LIBRARY}")
@ -25,9 +23,9 @@ endif()
ExternalProject_Add(jsoncpp-project
PREFIX "${prefix}"
DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads"
DOWNLOAD_NAME jsoncpp-1.7.7.tar.gz
URL https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz
URL_HASH SHA256=087640ebcf7fbcfe8e2717a0b9528fff89c52fcf69fa2a18cc2b538008098f97
DOWNLOAD_NAME jsoncpp-1.8.4.tar.gz
URL https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz
URL_HASH SHA256=c49deac9e0933bcb7044f08516861a2d560988540b23de2ac1ad443b219afdb6
CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND}
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
@ -36,7 +34,7 @@ ExternalProject_Add(jsoncpp-project
-DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS}
-DJSONCPP_WITH_TESTS=OFF
-DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF
-DCMAKE_CXX_FLAGS=${JSONCCP_EXTRA_FLAGS}
-DCMAKE_CXX_FLAGS=${JSONCPP_EXTRA_FLAGS}
# Overwrite build and install commands to force Release build on MSVC.
BUILD_COMMAND cmake --build <BINARY_DIR> --config Release
INSTALL_COMMAND cmake --build <BINARY_DIR> --config Release --target install

View File

@ -62,7 +62,7 @@ The following elementary types exist:
The following (fixed-size) array type exists:
- ``<type>[M]``: a fixed-length array of ``M`` elements, ``M > 0``, of the given type.
- ``<type>[M]``: a fixed-length array of ``M`` elements, ``M >= 0``, of the given type.
The following non-fixed-size types exist:
@ -77,7 +77,7 @@ of them inside parentheses, separated by commas:
- ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0``
It is possible to form tuples of tuples, arrays of tuples and so on.
It is possible to form tuples of tuples, arrays of tuples and so on. It is also possible to form zero-tuples (where ``n == 0``).
.. note::
Solidity supports all the types presented above with the same names with the exception of tuples. The ABI tuple type is utilised for encoding Solidity ``structs``.
@ -101,8 +101,8 @@ We distinguish static and dynamic types. Static types are encoded in-place and d
* ``bytes``
* ``string``
* ``T[]`` for any ``T``
* ``T[k]`` for any dynamic ``T`` and any ``k > 0``
* ``(T1,...,Tk)`` if any ``Ti`` is dynamic for ``1 <= i <= k``
* ``T[k]`` for any dynamic ``T`` and any ``k >= 0``
* ``(T1,...,Tk)`` if ``Ti`` is dynamic for some ``1 <= i <= k``
All other types are called "static".
@ -117,16 +117,16 @@ on the type of ``X`` being
- ``(T1,...,Tk)`` for ``k >= 0`` and any types ``T1``, ..., ``Tk``
``enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))``
``enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))``
where ``X(i)`` is the ``ith`` component of the value, and
where ``X = (X(1), ..., X(k))`` and
``head`` and ``tail`` are defined for ``Ti`` being a static type as
``head(X(i)) = enc(X(i))`` and ``tail(X(i)) = ""`` (the empty string)
and as
``head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... 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.
@ -144,7 +144,7 @@ on the type of ``X`` being
- ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``):
``enc(X) = enc(k) enc([X[1], ..., X[k]])``
``enc(X) = enc(k) enc([X[0], ..., X[k-1]])``
i.e. it is encoded as if it were an array of static size ``k``, prefixed with
the number of elements.

View File

@ -418,6 +418,9 @@ changes during the call, and thus references to local variables will be wrong.
Labels
------
.. note::
Labels are deprecated. Please use functions, loops, if or switch statements instead.
Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
which can change easily. Solidity inline assembly provides labels to make the use of
jumps easier. Note that labels are a low-level feature and it is possible to write
@ -519,6 +522,10 @@ is performed by replacing the variable's value on the stack by the new value.
=: v // instruction style assignment, puts the result of sload(10) into v
}
.. note::
Instruction-style assignment is deprecated.
If
--
@ -693,9 +700,9 @@ the form ``mul(add(x, y), 7)`` are preferred over pure opcode statements like
``7 y x add mul`` because in the first form, it is much easier to see which
operand is used for which opcode.
The second goal is achieved by introducing a desugaring phase that only removes
the higher level constructs in a very regular way and still allows inspecting
the generated low-level assembly code. The only non-local operation performed
The second goal is achieved by compiling the
higher level constructs to bytecode in a very regular way.
The only non-local operation performed
by the assembler is name lookup of user-defined identifiers (functions, variables, ...),
which follow very simple and regular scoping rules and cleanup of local variables from the stack.
@ -716,8 +723,6 @@ keep track of the current so-called stack height. Since all local variables
are removed at the end of a block, the stack height before and after the block
should be the same. If this is not the case, a warning is issued.
Why do we use higher-level constructs like ``switch``, ``for`` and functions:
Using ``switch``, ``for`` and functions, it should be possible to write
complex code without using ``jump`` or ``jumpi`` manually. This makes it much
easier to analyze the control flow, which allows for improved formal
@ -726,13 +731,11 @@ verification and optimization.
Furthermore, if manual jumps are allowed, computing the stack height is rather complicated.
The position of all local variables on the stack needs to be known, otherwise
neither references to local variables nor removing local variables automatically
from the stack at the end of a block will work properly. The desugaring
mechanism correctly inserts operations at unreachable blocks that adjust the
stack height properly in case of jumps that do not have a continuing control flow.
from the stack at the end of a block will work properly.
Example:
We will follow an example compilation from Solidity to desugared assembly.
We will follow an example compilation from Solidity to assembly.
We consider the runtime bytecode of the following Solidity program::
pragma solidity ^0.4.16;
@ -772,99 +775,9 @@ The following assembly will be generated::
}
}
After the desugaring phase it looks as follows::
{
mstore(0x40, 0x60)
{
let $0 := div(calldataload(0), exp(2, 226))
jumpi($case1, eq($0, 0xb3de648b))
jump($caseDefault)
$case1:
{
// the function call - we put return label and arguments on the stack
$ret1 calldataload(4) jump(f)
// This is unreachable code. Opcodes are added that mirror the
// effect of the function on the stack height: Arguments are
// removed and return values are introduced.
pop pop
let r := 0
$ret1: // the actual return point
$ret2 0x20 jump($allocate)
pop pop let ret := 0
$ret2:
mstore(ret, r)
return(ret, 0x20)
// although it is useless, the jump is automatically inserted,
// since the desugaring process is a purely syntactic operation that
// does not analyze control-flow
jump($endswitch)
}
$caseDefault:
{
revert(0, 0)
jump($endswitch)
}
$endswitch:
}
jump($afterFunction)
allocate:
{
// we jump over the unreachable code that introduces the function arguments
jump($start)
let $retpos := 0 let size := 0
$start:
// output variables live in the same scope as the arguments and is
// actually allocated.
let pos := 0
{
pos := mload(0x40)
mstore(0x40, add(pos, size))
}
// This code replaces the arguments by the return values and jumps back.
swap1 pop swap1 jump
// Again unreachable code that corrects stack height.
0 0
}
f:
{
jump($start)
let $retpos := 0 let x := 0
$start:
let y := 0
{
let i := 0
$for_begin:
jumpi($for_end, iszero(lt(i, x)))
{
y := mul(2, y)
}
$for_continue:
{ i := add(i, 1) }
jump($for_begin)
$for_end:
} // Here, a pop instruction will be inserted for i
swap1 pop swap1 jump
0 0
}
$afterFunction:
stop
}
Assembly happens in four stages:
1. Parsing
2. Desugaring (removes switch, for and functions)
3. Opcode stream generation
4. Bytecode generation
We will specify steps one to three in a pseudo-formal way. More formal
specifications will follow.
Parsing / Grammar
-----------------
Assembly Grammar
----------------
The tasks of the parser are the following:
@ -922,160 +835,3 @@ Grammar::
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
Desugaring
----------
An AST transformation removes for, switch and function constructs. The result
is still parseable by the same parser, but it will not use certain constructs.
If jumpdests are added that are only jumped to and not continued at, information
about the stack content is added, unless no local variables of outer scopes are
accessed or the stack height is the same as for the previous instruction.
Pseudocode::
desugar item: AST -> AST =
match item {
AssemblyFunctionDefinition('function' name '(' arg1, ..., argn ')' '->' ( '(' ret1, ..., retm ')' body) ->
<name>:
{
jump($<name>_start)
let $retPC := 0 let argn := 0 ... let arg1 := 0
$<name>_start:
let ret1 := 0 ... let retm := 0
{ desugar(body) }
swap and pop items so that only ret1, ... retm, $retPC are left on the stack
jump
0 (1 + n times) to compensate removal of arg1, ..., argn and $retPC
}
AssemblyFor('for' { init } condition post body) ->
{
init // cannot be its own block because we want variable scope to extend into the body
// find I such that there are no labels $forI_*
$forI_begin:
jumpi($forI_end, iszero(condition))
{ body }
$forI_continue:
{ post }
jump($forI_begin)
$forI_end:
}
'break' ->
{
// find nearest enclosing scope with label $forI_end
pop all local variables that are defined at the current point
but not at $forI_end
jump($forI_end)
0 (as many as variables were removed above)
}
'continue' ->
{
// find nearest enclosing scope with label $forI_continue
pop all local variables that are defined at the current point
but not at $forI_continue
jump($forI_continue)
0 (as many as variables were removed above)
}
AssemblySwitch(switch condition cases ( default: defaultBlock )? ) ->
{
// find I such that there is no $switchI* label or variable
let $switchI_value := condition
for each of cases match {
case val: -> jumpi($switchI_caseJ, eq($switchI_value, val))
}
if default block present: ->
{ defaultBlock jump($switchI_end) }
for each of cases match {
case val: { body } -> $switchI_caseJ: { body jump($switchI_end) }
}
$switchI_end:
}
FunctionalAssemblyExpression( identifier(arg1, arg2, ..., argn) ) ->
{
if identifier is function <name> with n args and m ret values ->
{
// find I such that $funcallI_* does not exist
$funcallI_return argn ... arg2 arg1 jump(<name>)
pop (n + 1 times)
if the current context is `let (id1, ..., idm) := f(...)` ->
let id1 := 0 ... let idm := 0
$funcallI_return:
else ->
0 (m times)
$funcallI_return:
turn the functional expression that leads to the function call
into a statement stream
}
else -> desugar(children of node)
}
default node ->
desugar(children of node)
}
Opcode Stream Generation
------------------------
During opcode stream generation, we keep track of the current stack height
in a counter,
so that accessing stack variables by name is possible. The stack height is modified with every opcode
that modifies the stack and with every label that is annotated with a stack
adjustment. Every time a new
local variable is introduced, it is registered together with the current
stack height. If a variable is accessed (either for copying its value or for
assignment), the appropriate ``DUP`` or ``SWAP`` instruction is selected depending
on the difference between the current stack height and the
stack height at the point the variable was introduced.
Pseudocode::
codegen item: AST -> opcode_stream =
match item {
AssemblyBlock({ items }) ->
join(codegen(item) for item in items)
if last generated opcode has continuing control flow:
POP for all local variables registered at the block (including variables
introduced by labels)
warn if the stack height at this point is not the same as at the start of the block
Identifier(id) ->
lookup id in the syntactic stack of blocks
match type of id
Local Variable ->
DUPi where i = 1 + stack_height - stack_height_of_identifier(id)
Label ->
// reference to be resolved during bytecode generation
PUSH<bytecode position of label>
SubAssembly ->
PUSH<bytecode position of subassembly data>
FunctionalAssemblyExpression(id ( arguments ) ) ->
join(codegen(arg) for arg in arguments.reversed())
id (which has to be an opcode, might be a function name later)
AssemblyLocalDefinition(let (id1, ..., idn) := expr) ->
register identifiers id1, ..., idn as locals in current block at current stack height
codegen(expr) - assert that expr returns n items to the stack
FunctionalAssemblyAssignment((id1, ..., idn) := expr) ->
lookup id1, ..., idn in the syntactic stack of blocks, assert that they are variables
codegen(expr)
for j = n, ..., i:
SWAPi where i = 1 + stack_height - stack_height_of_identifier(idj)
POP
AssemblyAssignment(=: id) ->
look up id in the syntactic stack of blocks, assert that it is a variable
SWAPi where i = 1 + stack_height - stack_height_of_identifier(id)
POP
LabelDefinition(name:) ->
JUMPDEST
NumberLiteral(num) ->
PUSH<num interpreted as decimal and right-aligned>
HexLiteral(lit) ->
PUSH32<lit interpreted as hex and left-aligned>
StringLiteral(lit) ->
PUSH32<lit utf-8 encoded and left-aligned>
SubAssembly(assembly <name> block) ->
append codegen(block) at the end of the code
dataSize(<name>) ->
assert that <name> is a subassembly ->
PUSH32<size of code generated from subassembly <name>>
linkerSymbol(<lit>) ->
PUSH32<zeros> and append position to linker table
}

View File

@ -432,6 +432,10 @@
"bugs": [],
"released": "2018-04-19"
},
"0.4.24": {
"bugs": [],
"released": "2018-05-16"
},
"0.4.3": {
"bugs": [
"ZeroFunctionSelector",

View File

@ -22,7 +22,8 @@ import re
# documentation root, use os.path.abspath to make it absolute, like shown here.
def setup(sphinx):
sys.path.insert(0, os.path.abspath('./utils'))
thisdir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, thisdir + '/utils')
from SolidityLexer import SolidityLexer
sphinx.add_lexer('Solidity', SolidityLexer())
@ -50,7 +51,7 @@ master_doc = 'index'
# General information about the project.
project = 'Solidity'
copyright = '2016-2017, Ethereum'
copyright = '2016-2018, Ethereum'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

@ -24,8 +24,8 @@ Creating contracts programatically on Ethereum is best done via using the JavaSc
As of today it has a method called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_
to facilitate contract creation.
When a contract is created, its constructor (a function with the same
name as the contract) is executed once.
When a contract is created, its constructor (a function declared with the
``constructor`` keyword) is executed once.
A constructor is optional. Only one constructor is allowed, and this means
overloading is not supported.
@ -473,7 +473,7 @@ The following statements are considered modifying the state:
}
.. note::
``constant`` on functions is an alias to ``view``, but this is deprecated and is planned to be dropped in version 0.5.0.
``constant`` on functions is an alias to ``view``, but this is deprecated and will be dropped in version 0.5.0.
.. note::
Getter methods are marked ``view``.
@ -841,10 +841,10 @@ Details are given in the following example.
::
pragma solidity ^0.4.16;
pragma solidity ^0.4.22;
contract owned {
function owned() { owner = msg.sender; }
constructor() { owner = msg.sender; }
address owner;
}
@ -875,7 +875,7 @@ Details are given in the following example.
// also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal {
function named(bytes32 name) {
constructor(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
@ -913,10 +913,10 @@ Note that above, we call ``mortal.kill()`` to "forward" the
destruction request. The way this is done is problematic, as
seen in the following example::
pragma solidity ^0.4.0;
pragma solidity ^0.4.22;
contract owned {
function owned() public { owner = msg.sender; }
constructor() public { owner = msg.sender; }
address owner;
}
@ -942,10 +942,10 @@ derived override, but this function will bypass
``Base1.kill``, basically because it does not even know about
``Base1``. The way around this is to use ``super``::
pragma solidity ^0.4.0;
pragma solidity ^0.4.22;
contract owned {
function owned() public { owner = msg.sender; }
constructor() public { owner = msg.sender; }
address owner;
}
@ -982,7 +982,7 @@ virtual method lookup.
Constructors
============
A constructor is an optional function declared with the ``constructor`` keyword which is executed upon contract creation.
A constructor is an optional function declared with the ``constructor`` keyword which is executed upon contract creation.
Constructor functions can be either ``public`` or ``internal``. If there is no constructor, the contract will assume the
default constructor: ``contructor() public {}``.
@ -1030,10 +1030,11 @@ A constructor set as ``internal`` causes the contract to be marked as :ref:`abst
Arguments for Base Constructors
===============================
Derived contracts need to provide all arguments needed for
the base constructors. This can be done in two ways::
The constructors of all the base contracts will be called following the
linearization rules explained below. If the base constructors have arguments,
derived contracts need to specify all of them. This can be done in two ways::
pragma solidity ^0.4.0;
pragma solidity ^0.4.22;
contract Base {
uint x;
@ -1059,6 +1060,9 @@ derived contract. Arguments have to be given either in the
inheritance list or in modifier-style in the derived constuctor.
Specifying arguments in both places is an error.
If a derived contract doesn't specify the arguments to all of its base
contracts' constructors, it will be abstract.
.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization
Multiple Inheritance and Linearization
@ -1066,12 +1070,15 @@ Multiple Inheritance and Linearization
Languages that allow multiple inheritance have to deal with
several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_.
Solidity follows the path of Python and uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_"
Solidity is similar to Python in that it uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_"
to force a specific order in the DAG of base classes. This
results in the desirable property of monotonicity but
disallows some inheritance graphs. Especially, the order in
which the base classes are given in the ``is`` directive is
important. In the following code, Solidity will give the
important: You have to list the direct base contracts
in the order from "most base-like" to "most derived".
Note that this order is different from the one used in Python.
In the following code, Solidity will give the
error "Linearization of inheritance graph impossible".
::
@ -1089,9 +1096,6 @@ The reason for this is that ``C`` requests ``X`` to override ``A``
requests to override ``X``, which is a contradiction that
cannot be resolved.
A simple rule to remember is to specify the base classes in
the order from "most base-like" to "most derived".
Inheriting Different Kinds of Members of the Same Name
======================================================
@ -1139,8 +1143,10 @@ Example of a Function Type (a variable declaration, where the variable is of typ
function(address) external returns (address) foo;
Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and
Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and
facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication.
Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method".
.. index:: ! contract;interface, ! interface contract

View File

@ -272,9 +272,12 @@ Assignment
Destructuring Assignments and Returning Multiple Values
-------------------------------------------------------
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose size is a constant at compile-time. Those tuples can be used to return multiple values at the same time and also assign them to multiple variables (or LValues in general) at the same time::
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose size is a constant at compile-time. Those tuples can be used to return multiple values at the same time.
These can then either be assigned to newly declared variables or to pre-existing variables (or LValues in general):
pragma solidity ^0.4.16;
::
pragma solidity >0.4.23 <0.5.0;
contract C {
uint[] data;
@ -284,21 +287,12 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di
}
function g() public {
// Variables declared with type
uint x;
bool b;
uint y;
// Tuple values can be assigned to these pre-existing variables
(x, b, y) = f();
// Variables declared with type and assigned from the returned tuple.
(uint x, bool b, uint y) = f();
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
// If the tuple ends in an empty component,
// the rest of the values are discarded.
(data.length,) = f(); // Sets the length to 7
// The same can be done on the left side.
// If the tuple begins in an empty component, the beginning values are discarded.
(,data[3]) = f(); // Sets data[3] to 2
(data.length,,) = f(); // Sets the length to 7
// Components can only be left out at the left-hand-side of assignments, with
// one exception:
(x,) = (1,);
@ -307,6 +301,11 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di
}
}
.. note::
Prior to version 0.4.24 it was possible to assign to tuples of smaller size, either
filling up on the left or on the right side (which ever was empty). This is
now deprecated, both sides have to have the same number of components.
Complications for Arrays and Structs
------------------------------------
@ -330,7 +329,9 @@ A variable declared anywhere within a function will be in scope for the *entire
(this will change soon, see below).
This happens because Solidity inherits its scoping rules from JavaScript.
This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block.
As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``::
As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``:
::
// This will not compile

View File

@ -203,7 +203,7 @@ situation.
If you do not want to throw, you can return a pair::
pragma solidity ^0.4.16;
pragma solidity >0.4.23 <0.5.0;
contract C {
uint[] counters;
@ -219,7 +219,7 @@ If you do not want to throw, you can return a pair::
}
function checkCounter(uint index) public view {
var (counter, error) = getCounter(index);
(uint counter, bool error) = getCounter(index);
if (error) {
// ...
} else {

View File

@ -16,7 +16,7 @@ ContractPart = StateVariableDeclaration | UsingForDeclaration
InheritanceSpecifier = UserDefinedTypeName ( '(' Expression ( ',' Expression )* ')' )?
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )? Identifier ('=' Expression)? ';'
StateVariableDeclaration = TypeName ( 'public' | 'internal' | 'private' | 'constant' )* Identifier ('=' Expression)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
@ -78,7 +78,7 @@ Break = 'break'
Return = 'return' Expression?
Throw = 'throw'
EmitStatement = 'emit' FunctionCall
VariableDefinition = ('var' IdentifierList | VariableDeclaration) ( '=' Expression )?
VariableDefinition = ('var' IdentifierList | VariableDeclaration | '(' VariableDeclaration? (',' VariableDeclaration? )* ')' ) ( '=' Expression )?
IdentifierList = '(' ( Identifier? ',' )* Identifier? ')'
// Precedence by order (see github.com/ethereum/solidity/pull/732)

View File

@ -35,7 +35,7 @@ npm / Node.js
=============
Use `npm` for a convenient and portable way to install `solcjs`, a Solidity compiler. The
`solcjs` program has less features than all options further down this page. Our
`solcjs` program has fewer features than all options further down this page. Our
:ref:`commandline-compiler` documentation assumes you are using
the full-featured compiler, `solc`. So if you install `solcjs` from `npm` then you will
stop reading the documentation here and then continue to `solc-js <https://github.com/ethereum/solc-js>`_.
@ -203,19 +203,38 @@ Prerequisites - Windows
You will need to install the following dependencies for Windows builds of Solidity:
+------------------------------+-------------------------------------------------------+
| Software | Notes |
+==============================+=======================================================+
| `Git for Windows`_ | Command-line tool for retrieving source from Github. |
+------------------------------+-------------------------------------------------------+
| `CMake`_ | Cross-platform build file generator. |
+------------------------------+-------------------------------------------------------+
| `Visual Studio 2015`_ | C++ compiler and dev environment. |
+------------------------------+-------------------------------------------------------+
+-----------------------------------+-------------------------------------------------------+
| Software | Notes |
+===================================+=======================================================+
| `Git for Windows`_ | Command-line tool for retrieving source from Github. |
+-----------------------------------+-------------------------------------------------------+
| `CMake`_ | Cross-platform build file generator. |
+-----------------------------------+-------------------------------------------------------+
| `Visual Studio 2017 Build Tools`_ | C++ compiler |
+-----------------------------------+-------------------------------------------------------+
| `Visual Studio 2017`_ (Optional) | C++ compiler and dev environment. |
+-----------------------------------+-------------------------------------------------------+
If you've already had one IDE and only need compiler and libraries,
you could install Visual Studio 2017 Build Tools.
Visual Studio 2017 provides both IDE and necessary compiler and libraries.
So if you have not got an IDE and prefer to develop solidity, Visual Studio 2017
may be an choice for you to get everything setup easily.
Here is the list of components that should be installed
in Visual Studio 2017 Build Tools or Visual Studio 2017:
* Visual Studio C++ core features
* VC++ 2017 v141 toolset (x86,x64)
* Windows Universal CRT SDK
* Windows 8.1 SDK
* C++/CLI support
.. _Git for Windows: https://git-scm.com/download/win
.. _CMake: https://cmake.org/download/
.. _Visual Studio 2015: https://www.visualstudio.com/products/vs-2015-product-editions
.. _Visual Studio 2017: https://www.visualstudio.com/vs/
.. _Visual Studio 2017 Build Tools: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017
External Dependencies
@ -263,7 +282,7 @@ And even for Windows:
mkdir build
cd build
cmake -G "Visual Studio 14 2015 Win64" ..
cmake -G "Visual Studio 15 2017 Win64" ..
This latter set of instructions should result in the creation of
**solidity.sln** in that build directory. Double-clicking on that file

View File

@ -25,7 +25,7 @@ Storage
storedData = x;
}
function get() public constant returns (uint) {
function get() public view returns (uint) {
return storedData;
}
}

View File

@ -306,12 +306,20 @@ Type Conversion Functions
JULIA has no support for implicit type conversion and therefore functions exists to provide explicit conversion.
When converting a larger type to a shorter type a runtime exception can occur in case of an overflow.
The following type conversion functions must be available:
- ``u32tobool(x:u32) -> y:bool``
- ``booltou32(x:bool) -> y:u32``
- ``u32tou64(x:u32) -> y:u64``
- ``u64tou32(x:u64) -> y:u32``
- etc. (TBD)
Truncating conversions are supported between the following types:
- ``bool``
- ``u32``
- ``u64``
- ``u256``
- ``s256``
For each of these a type conversion function exists having the prototype in the form of ``<input_type>to<output_type>(x:<input_type>) -> y:<output_type>``,
such as ``u32tobool(x:u32) -> y:bool``, ``u256tou32(x:u256) -> y:u32`` or ``s256tou256(x:s256) -> y:u256``.
.. note::
``u32tobool(x:u32) -> y:bool`` can be implemented as ``y := not(iszerou256(x))`` and
``booltou32(x:bool) -> y:u32`` can be implemented as ``switch x case true:bool { y := 1:u32 } case false:bool { y := 0:u32 }``
Low-level Functions
-------------------
@ -319,6 +327,16 @@ Low-level Functions
The following functions must be available:
+---------------------------------------------------------------------------------------------------------------+
| *Logic* |
+---------------------------------------------+-----------------------------------------------------------------+
| not(x:bool) -> z:bool | logical not |
+---------------------------------------------+-----------------------------------------------------------------+
| and(x:bool, y:bool) -> z:bool | logical and |
+---------------------------------------------+-----------------------------------------------------------------+
| or(x:bool, y:bool) -> z:bool | logical or |
+---------------------------------------------+-----------------------------------------------------------------+
| xor(x:bool, y:bool) -> z:bool | xor |
+---------------------------------------------+-----------------------------------------------------------------+
| *Arithmetics* |
+---------------------------------------------+-----------------------------------------------------------------+
| addu256(x:u256, y:u256) -> z:u256 | x + y |
@ -343,15 +361,19 @@ The following functions must be available:
+---------------------------------------------+-----------------------------------------------------------------+
| mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetics |
+---------------------------------------------+-----------------------------------------------------------------+
| ltu256(x:u256, y:u256) -> z:bool | 1 if x < y, 0 otherwise |
| ltu256(x:u256, y:u256) -> z:bool | true if x < y, false otherwise |
+---------------------------------------------+-----------------------------------------------------------------+
| gtu256(x:u256, y:u256) -> z:bool | 1 if x > y, 0 otherwise |
| gtu256(x:u256, y:u256) -> z:bool | true if x > y, false otherwise |
+---------------------------------------------+-----------------------------------------------------------------+
| sltu256(x:s256, y:s256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement |
| sltu256(x:s256, y:s256) -> z:bool | true if x < y, false otherwise |
| | (for signed numbers in two's complement) |
+---------------------------------------------+-----------------------------------------------------------------+
| sgtu256(x:s256, y:s256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement |
| sgtu256(x:s256, y:s256) -> z:bool | true if x > y, false otherwise |
| | (for signed numbers in two's complement) |
+---------------------------------------------+-----------------------------------------------------------------+
| equ256(x:u256, y:u256) -> z:bool | 1 if x == y, 0 otherwise |
| equ256(x:u256, y:u256) -> z:bool | true if x == y, false otherwise |
+---------------------------------------------+-----------------------------------------------------------------+
| iszerou256(x:u256) -> z:bool | true if x == 0, false otherwise |
+---------------------------------------------+-----------------------------------------------------------------+
| notu256(x:u256) -> z:u256 | ~x, every bit of x is negated |
+---------------------------------------------+-----------------------------------------------------------------+
@ -405,10 +427,6 @@ The following functions must be available:
| insize:u256, out:u256, | but also keep ``caller`` |
| outsize:u256) -> r:u256 | and ``callvalue`` |
+---------------------------------------------+-----------------------------------------------------------------+
| stop() | stop execution, identical to return(0,0) |
| | Perhaps it would make sense retiring this as it equals to |
| | return(0,0). It can be an optimisation by the EVM backend. |
+---------------------------------------------+-----------------------------------------------------------------+
| abort() | abort (equals to invalid instruction on EVM) |
+---------------------------------------------+-----------------------------------------------------------------+
| return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) |
@ -473,15 +491,17 @@ The following functions must be available:
+---------------------------------------------+-----------------------------------------------------------------+
| *Others* |
+---------------------------------------------+-----------------------------------------------------------------+
| discard(unused:bool) | discard value |
+---------------------------------------------+-----------------------------------------------------------------+
| discardu256(unused:u256) | discard value |
+---------------------------------------------+-----------------------------------------------------------------+
| splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's |
| x3:u64, x4:u64) | |
| x3:u64, x4:u64) | |
+---------------------------------------------+-----------------------------------------------------------------+
| combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 |
| x4:u64) -> (x:u256) | |
| x4:u64) -> (x:u256) | |
+---------------------------------------------+-----------------------------------------------------------------+
| sha3(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) |
| keccak256(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) |
+---------------------------------------------+-----------------------------------------------------------------+
Backends

1
docs/requirements.txt Normal file
View File

@ -0,0 +1 @@
sphinx_rtd_theme>=0.3.1

View File

@ -120,7 +120,7 @@ Gas Limit and Loops
Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully:
Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to
normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete
contract to be stalled at a certain point. This may not apply to ``constant`` functions that are only executed
contract to be stalled at a certain point. This may not apply to ``view`` functions that are only executed
to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations
and stall those. Please be explicit about such cases in the documentation of your contracts.

View File

@ -66,7 +66,7 @@ of votes.
Proposal[] public proposals;
/// Create a new ballot to choose one of `proposalNames`.
function Ballot(bytes32[] proposalNames) public {
constructor(bytes32[] proposalNames) public {
chairperson = msg.sender;
voters[chairperson].weight = 1;
@ -256,7 +256,7 @@ activate themselves.
/// Create a simple auction with `_biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `_beneficiary`.
function SimpleAuction(
constructor(
uint _biddingTime,
address _beneficiary
) public {
@ -388,7 +388,7 @@ high or low invalid bids.
::
pragma solidity ^0.4.22;
pragma solidity >0.4.23 <0.5.0;
contract BlindAuction {
struct Bid {
@ -418,7 +418,7 @@ high or low invalid bids.
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
function BlindAuction(
constructor(
uint _biddingTime,
uint _revealTime,
address _beneficiary
@ -467,8 +467,8 @@ high or low invalid bids.
uint refund;
for (uint i = 0; i < length; i++) {
var bid = bids[msg.sender][i];
var (value, fake, secret) =
Bid storage bid = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bid.blindedBid != keccak256(value, fake, secret)) {
// Bid was not actually revealed.
@ -553,7 +553,7 @@ Safe Remote Purchase
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
function Purchase() public payable {
constructor() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value, "Value has to be even.");
@ -602,7 +602,7 @@ Safe Remote Purchase
{
emit Aborted();
state = State.Inactive;
seller.transfer(this.balance);
seller.transfer(address(this).balance);
}
/// Confirm the purchase as buyer.
@ -637,7 +637,7 @@ Safe Remote Purchase
// block the refund - the withdraw pattern should be used.
buyer.transfer(value);
seller.transfer(this.balance);
seller.transfer(address(this).balance);
}
}

View File

@ -129,7 +129,7 @@ Structs are custom defined types that can group several variables (see
Enum Types
==========
Enums can be used to create custom types with a finite set of values (see
Enums can be used to create custom types with a finite set of 'constant values' (see
:ref:`enums` in types section).
::

View File

@ -117,7 +117,7 @@ No::
Maximum Line Length
===================
Keeping lines under the `PEP 8 recommendation <https://www.python.org/dev/peps/pep-0008/#maximum-line-length>`_ of 79 (or 99)
Keeping lines under the `PEP 8 recommendation <https://www.python.org/dev/peps/pep-0008/#maximum-line-length>`_ to a maximum of 79 (or 99)
characters helps readers easily parse the code.
Wrapped lines should conform to the following guidelines.
@ -269,7 +269,7 @@ Functions should be grouped according to their visibility and ordered:
- internal
- private
Within a grouping, place the ``constant`` functions last.
Within a grouping, place the ``view`` and ``pure`` functions last.
Yes::
@ -285,7 +285,10 @@ Yes::
// External functions
// ...
// External functions that are constant
// External functions that are view
// ...
// External functions that are pure
// ...
// Public functions

View File

@ -31,6 +31,9 @@ because of `leap seconds <https://en.wikipedia.org/wiki/Leap_second>`_.
Due to the fact that leap seconds cannot be predicted, an exact calendar
library has to be updated by an external oracle.
.. note::
The suffix ``years`` has been deprecated due to the reasons above.
These suffixes cannot be applied to variables. If you want to
interpret some input variable in e.g. days, you can do it in the following way::
@ -117,11 +120,11 @@ Error Handling
--------------
``assert(bool condition)``:
throws if the condition is not met - to be used for internal errors.
invalidates the transaction if the condition is not met - to be used for internal errors.
``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components.
reverts if the condition is not met - to be used for errors in inputs or external components.
``require(bool condition, string message)``:
throws if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
``revert()``:
abort execution and revert state changes
``revert(string reason)``:

View File

@ -14,7 +14,9 @@ Using the Commandline Compiler
One of the build targets of the Solidity repository is ``solc``, the solidity commandline compiler.
Using ``solc --help`` provides you with an explanation of all options. The compiler can produce various outputs, ranging from simple binaries and assembly over an abstract syntax tree (parse tree) to estimations of gas usage.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
If you only want to compile a single file, you run it as ``solc --bin sourceFile.sol`` and it will print the binary. If you want to get some of the more advanced output variants of ``solc``, it is probably better to tell it to output everything to separate files using ``solc -o outputDirectory --bin --ast --asm sourceFile.sol``.
Before you deploy your contract, activate the optimizer while compiling using ``solc --optimize --bin sourceFile.sol``. By default, the optimizer will optimize the contract for 200 runs. If you want to optimize for initial contract deployment and get the smallest output, set it to ``--runs=1``. If you expect many transactions and don't care for higher deployment cost and output size, set ``--runs`` to a high number.
The commandline compiler will automatically read imported files from the filesystem, but
it is also possible to provide path redirects using ``prefix=path`` in the following way:
@ -96,10 +98,13 @@ Input Description
{
// Optional: Sorted list of remappings
remappings: [ ":g/dir" ],
// Optional: Optimizer settings (enabled defaults to false)
// Optional: Optimizer settings
optimizer: {
// disabled by default
enabled: true,
runs: 500
// 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.
runs: 200
},
evmVersion: "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium or constantinople
// Metadata settings (optional)

View File

@ -28,8 +28,8 @@
using namespace std;
static_assert(
(JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 7) && (JSONCPP_VERSION_PATCH == 7),
"Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.7.7."
(JSONCPP_VERSION_MAJOR == 1) && (JSONCPP_VERSION_MINOR == 8) && (JSONCPP_VERSION_PATCH == 4),
"Unexpected jsoncpp version: " JSONCPP_VERSION_STRING ". Expecting 1.8.4."
);
namespace dev

View File

@ -47,8 +47,8 @@ class Assembly
public:
Assembly() {}
AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); }
AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); }
AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
/// Returns a tag identified by the given name. Creates it if it does not yet exist.
AssemblyItem namedTag(std::string const& _name);
AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); }

View File

@ -26,27 +26,35 @@ using namespace std;
using namespace dev;
using namespace dev::eth;
static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide");
AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const
{
assertThrow(data() < (u256(1) << 64), Exception, "Tag already has subassembly set.");
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
size_t tag = size_t(u256(data()) & 0xffffffffffffffffULL);
AssemblyItem r = *this;
r.m_type = PushTag;
r.setPushTagSubIdAndTag(_subId, size_t(data()));
r.setPushTagSubIdAndTag(_subId, tag);
return r;
}
pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const
{
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
return make_pair(size_t((data()) / (u256(1) << 64)) - 1, size_t(data()));
u256 combined = u256(data());
size_t subId = size_t((combined >> 64) - 1);
size_t tag = size_t(combined & 0xffffffffffffffffULL);
return make_pair(subId, tag);
}
void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
{
assertThrow(m_type == PushTag || m_type == Tag, Exception, "");
setData(_tag + (u256(_subId + 1) << 64));
u256 data = _tag;
if (_subId != size_t(-1))
data |= (u256(_subId) + 1) << 64;
setData(data);
}
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const

View File

@ -2,4 +2,4 @@ file(GLOB sources "*.cpp")
file(GLOB headers "*.h")
add_library(evmasm ${sources} ${headers})
target_link_libraries(evmasm PUBLIC jsoncpp devcore)
target_link_libraries(evmasm PUBLIC devcore)

View File

@ -67,6 +67,7 @@ public:
explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value):
m_params(_params), m_value(_value) {}
virtual ~ConstantOptimisationMethod() = default;
virtual bigint gasNeeded() const = 0;
/// Executes the method, potentially appending to the assembly and returns a vector of
/// assembly items the constant should be relpaced with in one sweep.

View File

@ -39,7 +39,7 @@ enum class Instruction: uint8_t
{
STOP = 0x00, ///< halts execution
ADD, ///< addition operation
MUL, ///< mulitplication operation
MUL, ///< multiplication operation
SUB, ///< subtraction operation
DIV, ///< integer division operation
SDIV, ///< signed integer division operation
@ -50,11 +50,11 @@ enum class Instruction: uint8_t
EXP, ///< exponential operation
SIGNEXTEND, ///< extend length of signed integer
LT = 0x10, ///< less-than comparision
GT, ///< greater-than comparision
SLT, ///< signed less-than comparision
SGT, ///< signed greater-than comparision
EQ, ///< equality comparision
LT = 0x10, ///< less-than comparison
GT, ///< greater-than comparison
SLT, ///< signed less-than comparison
SGT, ///< signed greater-than comparison
EQ, ///< equality comparison
ISZERO, ///< simple not operator
AND, ///< bitwise AND operation
OR, ///< bitwise OR operation
@ -293,7 +293,7 @@ struct InstructionInfo
/// Information on all the instructions.
InstructionInfo instructionInfo(Instruction _inst);
/// check whether instructions exists
/// check whether instructions exists.
bool isValidInstruction(Instruction _inst);
/// Convert from string mnemonic to Instruction type.

View File

@ -43,7 +43,7 @@ GasMeter::GasConsumption PathGasMeter::estimateMax(
auto path = unique_ptr<GasPath>(new GasPath());
path->index = _startIndex;
path->state = _state->copy();
m_queue.push_back(move(path));
queue(move(path));
GasMeter::GasConsumption gas;
while (!m_queue.empty() && !gas.isInfinite)
@ -51,12 +51,23 @@ GasMeter::GasConsumption PathGasMeter::estimateMax(
return gas;
}
void PathGasMeter::queue(std::unique_ptr<GasPath>&& _newPath)
{
if (
m_highestGasUsagePerJumpdest.count(_newPath->index) &&
_newPath->gas < m_highestGasUsagePerJumpdest.at(_newPath->index)
)
return;
m_highestGasUsagePerJumpdest[_newPath->index] = _newPath->gas;
m_queue[_newPath->index] = move(_newPath);
}
GasMeter::GasConsumption PathGasMeter::handleQueueItem()
{
assertThrow(!m_queue.empty(), OptimizerException, "");
unique_ptr<GasPath> path = move(m_queue.back());
m_queue.pop_back();
unique_ptr<GasPath> path = move(m_queue.rbegin()->second);
m_queue.erase(--m_queue.end());
shared_ptr<KnownState> state = path->state;
GasMeter meter(state, m_evmVersion, path->largestMemoryAccess);
@ -117,7 +128,7 @@ GasMeter::GasConsumption PathGasMeter::handleQueueItem()
newPath->largestMemoryAccess = meter.largestMemoryAccess();
newPath->state = state->copy();
newPath->visitedJumpdests = path->visitedJumpdests;
m_queue.push_back(move(newPath));
queue(move(newPath));
}
if (branchStops)

View File

@ -58,9 +58,17 @@ public:
GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr<KnownState> const& _state);
private:
/// Adds a new path item to the queue, but only if we do not already have
/// a higher gas usage at that point.
/// This is not exact as different state might influence higher gas costs at a later
/// point in time, but it greatly reduces computational overhead.
void queue(std::unique_ptr<GasPath>&& _newPath);
GasMeter::GasConsumption handleQueueItem();
std::vector<std::unique_ptr<GasPath>> m_queue;
/// Map of jumpdest -> gas path, so not really a queue. We only have one queued up
/// item per jumpdest, because of the behaviour of `queue` above.
std::map<size_t, std::unique_ptr<GasPath>> m_queue;
std::map<size_t, GasMeter::GasConsumption> m_highestGasUsagePerJumpdest;
std::map<u256, size_t> m_tagPositions;
AssemblyItems const& m_items;
solidity::EVMVersion m_evmVersion;

View File

@ -34,6 +34,7 @@ using AssemblyItems = std::vector<AssemblyItem>;
class PeepholeOptimisationMethod
{
public:
virtual ~PeepholeOptimisationMethod() = default;
virtual size_t windowSize() const;
virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out);
};
@ -42,6 +43,7 @@ class PeepholeOptimiser
{
public:
explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {}
virtual ~PeepholeOptimiser() = default;
bool optimise();

View File

@ -174,6 +174,26 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
});
}
for (auto const& op: std::vector<Instruction>{
Instruction::ADDRESS,
Instruction::CALLER,
Instruction::ORIGIN,
Instruction::COINBASE
})
{
u256 const mask = (u256(1) << 160) - 1;
rules.push_back({
{Instruction::AND, {{op, mask}}},
[=]() -> Pattern { return op; },
false
});
rules.push_back({
{Instruction::AND, {{mask, op}}},
[=]() -> Pattern { return op; },
false
});
}
// Double negation of opcodes with boolean result
for (auto const& op: std::vector<Instruction>{
Instruction::EQ,

35
libjulia/Exceptions.h Normal file
View File

@ -0,0 +1,35 @@
/*
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/>.
*/
/**
* Exceptions in Julia.
*/
#pragma once
#include <libdevcore/Exceptions.h>
#include <libdevcore/Assertions.h>
namespace dev
{
namespace julia
{
struct IuliaException: virtual Exception {};
struct OptimizerException: virtual IuliaException {};
}
}

View File

@ -20,9 +20,9 @@
#include <libjulia/optimiser/ASTCopier.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libdevcore/Common.h>
@ -30,10 +30,9 @@ using namespace std;
using namespace dev;
using namespace dev::julia;
Statement ASTCopier::operator()(Instruction const&)
{
solAssert(false, "Invalid operation.");
assertThrow(false, OptimizerException, "Invalid operation.");
return {};
}
@ -62,13 +61,13 @@ Statement ASTCopier::operator()(Assignment const& _assignment)
Statement ASTCopier::operator()(StackAssignment const&)
{
solAssert(false, "Invalid operation.");
assertThrow(false, OptimizerException, "Invalid operation.");
return {};
}
Statement ASTCopier::operator()(Label const&)
{
solAssert(false, "Invalid operation.");
assertThrow(false, OptimizerException, "Invalid operation.");
return {};
}

View File

@ -37,6 +37,7 @@ namespace julia
class ExpressionCopier: public boost::static_visitor<Expression>
{
public:
virtual ~ExpressionCopier() = default;
virtual Expression operator()(Literal const& _literal) = 0;
virtual Expression operator()(Identifier const& _identifier) = 0;
virtual Expression operator()(FunctionalInstruction const& _instr) = 0;
@ -46,6 +47,7 @@ public:
class StatementCopier: public boost::static_visitor<Statement>
{
public:
virtual ~StatementCopier() = default;
virtual Statement operator()(ExpressionStatement const& _statement) = 0;
virtual Statement operator()(Instruction const& _instruction) = 0;
virtual Statement operator()(Label const& _label) = 0;
@ -66,6 +68,7 @@ public:
class ASTCopier: public ExpressionCopier, public StatementCopier
{
public:
virtual ~ASTCopier() = default;
virtual Expression operator()(Literal const& _literal) override;
virtual Statement operator()(Instruction const& _instruction) override;
virtual Expression operator()(Identifier const& _identifier) override;

View File

@ -22,8 +22,6 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;

View File

@ -22,7 +22,7 @@
#include <libjulia/ASTDataForward.h>
#include <libsolidity/interface/Exceptions.h>
#include <libjulia/Exceptions.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
@ -42,14 +42,15 @@ namespace julia
class ASTWalker: public boost::static_visitor<>
{
public:
virtual ~ASTWalker() = default;
virtual void operator()(Literal const&) {}
virtual void operator()(Instruction const&) { solAssert(false, ""); }
virtual void operator()(Instruction const&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(Identifier const&) {}
virtual void operator()(FunctionalInstruction const& _instr);
virtual void operator()(FunctionCall const& _funCall);
virtual void operator()(ExpressionStatement const& _statement);
virtual void operator()(Label const&) { solAssert(false, ""); }
virtual void operator()(StackAssignment const&) { solAssert(false, ""); }
virtual void operator()(Label const&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(StackAssignment const&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(Assignment const& _assignment);
virtual void operator()(VariableDeclaration const& _varDecl);
virtual void operator()(If const& _if);
@ -82,14 +83,15 @@ protected:
class ASTModifier: public boost::static_visitor<>
{
public:
virtual ~ASTModifier() = default;
virtual void operator()(Literal&) {}
virtual void operator()(Instruction&) { solAssert(false, ""); }
virtual void operator()(Instruction&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(Identifier&) {}
virtual void operator()(FunctionalInstruction& _instr);
virtual void operator()(FunctionCall& _funCall);
virtual void operator()(ExpressionStatement& _statement);
virtual void operator()(Label&) { solAssert(false, ""); }
virtual void operator()(StackAssignment&) { solAssert(false, ""); }
virtual void operator()(Label&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(StackAssignment&) { assertThrow(false, OptimizerException, ""); }
virtual void operator()(Assignment& _assignment);
virtual void operator()(VariableDeclaration& _varDecl);
virtual void operator()(If& _if);

View File

@ -23,6 +23,7 @@
#include <libjulia/optimiser/Metrics.h>
#include <libjulia/optimiser/SyntacticalEquality.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
@ -37,7 +38,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
// TODO this search rather inefficient.
for (auto const& var: m_value)
{
solAssert(var.second, "");
assertThrow(var.second, OptimizerException, "");
if (SyntacticalEqualityChecker::equal(_e, *var.second))
{
_e = Identifier{locationOf(_e), var.first};

View File

@ -23,11 +23,11 @@
#include <libjulia/optimiser/DataFlowAnalyzer.h>
#include <libjulia/optimiser/NameCollector.h>
#include <libjulia/optimiser/Semantics.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libjulia/optimiser/Semantics.h>
#include <libdevcore/CommonData.h>
#include <boost/range/adaptor/reversed.hpp>
@ -41,7 +41,7 @@ void DataFlowAnalyzer::operator()(Assignment& _assignment)
set<string> names;
for (auto const& var: _assignment.variableNames)
names.insert(var.name);
solAssert(_assignment.value, "");
assertThrow(_assignment.value, OptimizerException, "");
visit(*_assignment.value);
handleAssignment(names, _assignment.value.get());
}
@ -120,7 +120,7 @@ void DataFlowAnalyzer::operator()(Block& _block)
m_variableScopes.emplace_back(false);
ASTModifier::operator()(_block);
m_variableScopes.pop_back();
solAssert(numScopes == m_variableScopes.size(), "");
assertThrow(numScopes == m_variableScopes.size(), OptimizerException, "");
}
void DataFlowAnalyzer::handleAssignment(set<string> const& _variables, Expression* _value)

View File

@ -20,11 +20,11 @@
#include <libjulia/optimiser/Disambiguator.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmScope.h>
#include <libsolidity/interface/Exceptions.h>
using namespace std;
using namespace dev;
using namespace dev::julia;
@ -34,21 +34,11 @@ using Scope = dev::solidity::assembly::Scope;
string Disambiguator::translateIdentifier(string const& _originalName)
{
solAssert(!m_scopes.empty() && m_scopes.back(), "");
assertThrow(!m_scopes.empty() && m_scopes.back(), OptimizerException, "");
Scope::Identifier const* id = m_scopes.back()->lookup(_originalName);
solAssert(id, "");
assertThrow(id, OptimizerException, "");
if (!m_translations.count(id))
{
string translated = _originalName;
size_t suffix = 0;
while (m_usedNames.count(translated))
{
suffix++;
translated = _originalName + "_" + std::to_string(suffix);
}
m_usedNames.insert(translated);
m_translations[id] = translated;
}
m_translations[id] = m_nameDispenser.newName(_originalName);
return m_translations.at(id);
}
@ -79,7 +69,7 @@ void Disambiguator::enterScopeInternal(Scope& _scope)
void Disambiguator::leaveScopeInternal(Scope& _scope)
{
solAssert(!m_scopes.empty(), "");
solAssert(m_scopes.back() == &_scope, "");
assertThrow(!m_scopes.empty(), OptimizerException, "");
assertThrow(m_scopes.back() == &_scope, OptimizerException, "");
m_scopes.pop_back();
}

View File

@ -23,6 +23,7 @@
#include <libjulia/ASTDataForward.h>
#include <libjulia/optimiser/ASTCopier.h>
#include <libjulia/optimiser/NameDispenser.h>
#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
@ -60,7 +61,7 @@ protected:
std::vector<solidity::assembly::Scope*> m_scopes;
std::map<void const*, std::string> m_translations;
std::set<std::string> m_usedNames;
NameDispenser m_nameDispenser;
};
}

View File

@ -23,8 +23,6 @@
#include <libjulia/ASTDataForward.h>
#include <libsolidity/interface/Exceptions.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>

View File

@ -25,8 +25,6 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <libdevcore/CommonData.h>
using namespace std;

View File

@ -0,0 +1,259 @@
/*
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/>.
*/
/**
* Optimiser component that performs function inlining for arbitrary functions.
*/
#include <libjulia/optimiser/FullInliner.h>
#include <libjulia/optimiser/ASTCopier.h>
#include <libjulia/optimiser/ASTWalker.h>
#include <libjulia/optimiser/NameCollector.h>
#include <libjulia/optimiser/Semantics.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libdevcore/CommonData.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
FullInliner::FullInliner(Block& _ast):
m_ast(_ast)
{
assertThrow(m_ast.statements.size() >= 1, OptimizerException, "");
assertThrow(m_ast.statements.front().type() == typeid(Block), OptimizerException, "");
m_nameDispenser.m_usedNames = NameCollector(m_ast).names();
for (size_t i = 1; i < m_ast.statements.size(); ++i)
{
assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, "");
FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i));
m_functions[fun.name] = &fun;
m_functionsToVisit.insert(&fun);
}
}
void FullInliner::run()
{
assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, "");
InlineModifier(*this, m_nameDispenser, "").visit(m_ast.statements[0]);
while (!m_functionsToVisit.empty())
handleFunction(**m_functionsToVisit.begin());
}
void FullInliner::handleFunction(FunctionDefinition& _fun)
{
if (!m_functionsToVisit.count(&_fun))
return;
m_functionsToVisit.erase(&_fun);
(InlineModifier(*this, m_nameDispenser, _fun.name))(_fun.body);
}
void InlineModifier::operator()(FunctionalInstruction& _instruction)
{
visitArguments(_instruction.arguments);
}
void InlineModifier::operator()(FunctionCall&)
{
assertThrow(false, OptimizerException, "Should be handled in visit() instead.");
}
void InlineModifier::operator()(ForLoop& _loop)
{
(*this)(_loop.pre);
// Do not visit the condition because we cannot inline there.
(*this)(_loop.post);
(*this)(_loop.body);
}
void InlineModifier::operator()(Block& _block)
{
// This is only used if needed to minimize the number of move operations.
vector<Statement> modifiedStatements;
for (size_t i = 0; i < _block.statements.size(); ++i)
{
visit(_block.statements.at(i));
if (!m_statementsToPrefix.empty())
{
if (modifiedStatements.empty())
std::move(
_block.statements.begin(),
_block.statements.begin() + i,
back_inserter(modifiedStatements)
);
modifiedStatements += std::move(m_statementsToPrefix);
m_statementsToPrefix.clear();
}
if (!modifiedStatements.empty())
modifiedStatements.emplace_back(std::move(_block.statements[i]));
}
if (!modifiedStatements.empty())
_block.statements = std::move(modifiedStatements);
}
void InlineModifier::visit(Expression& _expression)
{
if (_expression.type() != typeid(FunctionCall))
return ASTModifier::visit(_expression);
FunctionCall& funCall = boost::get<FunctionCall>(_expression);
FunctionDefinition& fun = m_driver.function(funCall.functionName.name);
m_driver.handleFunction(fun);
// TODO: Insert good heuristic here. Perhaps implement that inside the driver.
bool doInline = funCall.functionName.name != m_currentFunction;
if (fun.returnVariables.size() > 1)
doInline = false;
{
vector<string> argNames;
vector<string> argTypes;
for (auto const& arg: fun.parameters)
{
argNames.push_back(fun.name + "_" + arg.name);
argTypes.push_back(arg.type);
}
visitArguments(funCall.arguments, argNames, argTypes, doInline);
}
if (!doInline)
return;
map<string, string> variableReplacements;
for (size_t i = 0; i < funCall.arguments.size(); ++i)
variableReplacements[fun.parameters[i].name] = boost::get<Identifier>(funCall.arguments[i]).name;
if (fun.returnVariables.empty())
_expression = noop(funCall.location);
else
{
string returnVariable = fun.returnVariables[0].name;
variableReplacements[returnVariable] = newName(fun.name + "_" + returnVariable);
m_statementsToPrefix.emplace_back(VariableDeclaration{
funCall.location,
{{funCall.location, variableReplacements[returnVariable], fun.returnVariables[0].type}},
{}
});
_expression = Identifier{funCall.location, variableReplacements[returnVariable]};
}
m_statementsToPrefix.emplace_back(BodyCopier(m_nameDispenser, fun.name + "_", variableReplacements)(fun.body));
}
void InlineModifier::visit(Statement& _statement)
{
ASTModifier::visit(_statement);
// Replace pop(0) expression statemets (and others) by empty blocks.
if (_statement.type() == typeid(ExpressionStatement))
{
ExpressionStatement& expSt = boost::get<ExpressionStatement>(_statement);
if (expSt.expression.type() == typeid(FunctionalInstruction))
{
FunctionalInstruction& funInstr = boost::get<FunctionalInstruction>(expSt.expression);
if (funInstr.instruction == solidity::Instruction::POP)
if (MovableChecker(funInstr.arguments.at(0)).movable())
_statement = Block{expSt.location, {}};
}
}
}
void InlineModifier::visitArguments(
vector<Expression>& _arguments,
vector<string> const& _nameHints,
vector<string> const& _types,
bool _moveToFront
)
{
// If one of the elements moves parts to the front, all other elements right of it
// also have to be moved to the front to keep the order of evaluation.
vector<Statement> prefix;
for (size_t i = 0; i < _arguments.size(); ++i)
{
auto& arg = _arguments[i];
// TODO optimize vector operations, check that it actually moves
auto internalPrefix = visitRecursively(arg);
if (!internalPrefix.empty())
{
_moveToFront = true;
// We go through the arguments left to right, so we have to invert
// the prefixes.
prefix = std::move(internalPrefix) + std::move(prefix);
}
else if (_moveToFront)
{
auto location = locationOf(arg);
string var = newName(i < _nameHints.size() ? _nameHints[i] : "");
prefix.emplace(prefix.begin(), VariableDeclaration{
location,
{{TypedName{location, var, i < _types.size() ? _types[i] : ""}}},
make_shared<Expression>(std::move(arg))
});
arg = Identifier{location, var};
}
}
m_statementsToPrefix += std::move(prefix);
}
vector<Statement> InlineModifier::visitRecursively(Expression& _expression)
{
vector<Statement> saved;
saved.swap(m_statementsToPrefix);
visit(_expression);
saved.swap(m_statementsToPrefix);
return saved;
}
string InlineModifier::newName(string const& _prefix)
{
return m_nameDispenser.newName(_prefix);
}
Expression InlineModifier::noop(SourceLocation const& _location)
{
return FunctionalInstruction{_location, solidity::Instruction::POP, {
Literal{_location, assembly::LiteralKind::Number, "0", ""}
}};
}
Statement BodyCopier::operator()(VariableDeclaration const& _varDecl)
{
for (auto const& var: _varDecl.variables)
m_variableReplacements[var.name] = m_nameDispenser.newName(m_varNamePrefix + var.name);
return ASTCopier::operator()(_varDecl);
}
Statement BodyCopier::operator()(FunctionDefinition const& _funDef)
{
assertThrow(false, OptimizerException, "Function hoisting has to be done before function inlining.");
return _funDef;
}
string BodyCopier::translateIdentifier(string const& _name)
{
if (m_variableReplacements.count(_name))
return m_variableReplacements.at(_name);
else
return _name;
}

View File

@ -0,0 +1,179 @@
/*
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/>.
*/
/**
* Optimiser component that performs function inlining for arbitrary functions.
*/
#pragma once
#include <libjulia/ASTDataForward.h>
#include <libjulia/optimiser/ASTCopier.h>
#include <libjulia/optimiser/ASTWalker.h>
#include <libjulia/optimiser/NameDispenser.h>
#include <libjulia/Exceptions.h>
#include <libevmasm/SourceLocation.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
#include <set>
namespace dev
{
namespace julia
{
class NameCollector;
/**
* Optimiser component that modifies an AST in place, inlining arbitrary functions.
*
* Code of the form
*
* function f(a, b) -> c { ... }
* h(g(x(...), f(arg1(...), arg2(...)), y(...)), z(...))
*
* is transformed into
*
* function f(a, b) -> c { ... }
*
* let z1 := z(...) let y1 := y(...) let a2 := arg2(...) let a1 := arg1(...)
* let c1 := 0
* { code of f, with replacements: a -> a1, b -> a2, c -> c1, d -> d1 }
* h(g(x(...), c1, y1), z1)
*
* No temporary variable is created for expressions that are "movable"
* (i.e. they are "pure", have no side-effects and also do not depend on other code
* that might have side-effects).
*
* This component can only be used on sources with unique names and with hoisted functions,
* i.e. the root node has to be a block that itself contains a single block followed by all
* function definitions.
*/
class FullInliner: public ASTModifier
{
public:
explicit FullInliner(Block& _ast);
void run();
/// Perform inlining operations inside the given function.
void handleFunction(FunctionDefinition& _function);
FunctionDefinition& function(std::string _name) { return *m_functions.at(_name); }
private:
/// The AST to be modified. The root block itself will not be modified, because
/// we store pointers to functions.
Block& m_ast;
std::map<std::string, FunctionDefinition*> m_functions;
std::set<FunctionDefinition*> m_functionsToVisit;
NameDispenser m_nameDispenser;
};
/**
* Class that walks the AST of a block that does not contain function definitions and perform
* the actual code modifications.
*/
class InlineModifier: public ASTModifier
{
public:
InlineModifier(FullInliner& _driver, NameDispenser& _nameDispenser, std::string _functionName):
m_currentFunction(std::move(_functionName)),
m_driver(_driver),
m_nameDispenser(_nameDispenser)
{ }
~InlineModifier()
{
assertThrow(m_statementsToPrefix.empty(), OptimizerException, "");
}
virtual void operator()(FunctionalInstruction&) override;
virtual void operator()(FunctionCall&) override;
virtual void operator()(ForLoop&) override;
virtual void operator()(Block& _block) override;
using ASTModifier::visit;
virtual void visit(Expression& _expression) override;
virtual void visit(Statement& _st) override;
private:
/// Visits a list of expressions (usually an argument list to a function call) and tries
/// to inline them. If one of them is inlined, all right of it have to be moved to the front
/// (to keep the order of evaluation). If @a _moveToFront is true, all elements are moved
/// to the front. @a _nameHints and @_types are used for the newly created variables, but
/// both can be empty.
void visitArguments(
std::vector<Expression>& _arguments,
std::vector<std::string> const& _nameHints = {},
std::vector<std::string> const& _types = {},
bool _moveToFront = false
);
/// Visits an expression, but saves and restores the current statements to prefix and returns
/// the statements that should be prefixed for @a _expression.
std::vector<Statement> visitRecursively(Expression& _expression);
std::string newName(std::string const& _prefix);
/// @returns an expression returning nothing.
Expression noop(SourceLocation const& _location);
/// List of statements that should go in front of the currently visited AST element,
/// at the statement level.
std::vector<Statement> m_statementsToPrefix;
std::string m_currentFunction;
FullInliner& m_driver;
NameDispenser& m_nameDispenser;
};
/**
* Creates a copy of a block that is supposed to be the body of a function.
* Applies replacements to referenced variables and creates new names for
* variable declarations.
*/
class BodyCopier: public ASTCopier
{
public:
BodyCopier(
NameDispenser& _nameDispenser,
std::string const& _varNamePrefix,
std::map<std::string, std::string> const& _variableReplacements
):
m_nameDispenser(_nameDispenser),
m_varNamePrefix(_varNamePrefix),
m_variableReplacements(_variableReplacements)
{}
using ASTCopier::operator ();
virtual Statement operator()(VariableDeclaration const& _varDecl) override;
virtual Statement operator()(FunctionDefinition const& _funDef) override;
virtual std::string translateIdentifier(std::string const& _name) override;
NameDispenser& m_nameDispenser;
std::string const& m_varNamePrefix;
std::map<std::string, std::string> m_variableReplacements;
};
}
}

View File

@ -23,8 +23,6 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <boost/range/algorithm_ext/erase.hpp>
using namespace std;

View File

@ -25,8 +25,6 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <libdevcore/CommonData.h>
using namespace std;

View File

@ -20,9 +20,9 @@
#include <libjulia/optimiser/InlinableExpressionFunctionFinder.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libjulia/optimiser/Utilities.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
using namespace std;
using namespace dev;
@ -56,7 +56,7 @@ void InlinableExpressionFunctionFinder::operator()(FunctionDefinition const& _fu
// We cannot overwrite previous settings, because this function definition
// would not be valid here if we were searching inside a functionally inlinable
// function body.
solAssert(m_disallowedIdentifiers.empty() && !m_foundDisallowedIdentifier, "");
assertThrow(m_disallowedIdentifiers.empty() && !m_foundDisallowedIdentifier, OptimizerException, "");
m_disallowedIdentifiers = set<string>{retVariable, _function.name};
boost::apply_visitor(*this, *assignment.value);
if (!m_foundDisallowedIdentifier)

View File

@ -23,8 +23,6 @@
#include <libjulia/ASTDataForward.h>
#include <libjulia/optimiser/ASTWalker.h>
#include <libsolidity/interface/Exceptions.h>
#include <set>
namespace dev

View File

@ -0,0 +1,54 @@
/*
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/>.
*/
/**
* Changes the topmost block to be a function with a specific name ("main") which has no
* inputs nor outputs.
*/
#include <libjulia/optimiser/MainFunction.h>
#include <libjulia/optimiser/NameCollector.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libdevcore/CommonData.h>
using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
void MainFunction::operator()(Block& _block)
{
assertThrow(_block.statements.size() >= 1, OptimizerException, "");
assertThrow(_block.statements[0].type() == typeid(Block), OptimizerException, "");
for (size_t i = 1; i < _block.statements.size(); ++i)
assertThrow(_block.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, "");
/// @todo this should handle scopes properly and instead of an assertion it should rename the conflicting function
assertThrow(NameCollector(_block).names().count("main") == 0, OptimizerException, "");
Block& block = boost::get<Block>(_block.statements[0]);
FunctionDefinition main{
block.location,
"main",
{},
{},
std::move(block)
};
_block.statements[0] = std::move(main);
}

View File

@ -0,0 +1,41 @@
/*
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/>.
*/
/**
* Changes the topmost block to be a function with a specific name ("main") which has no
* inputs nor outputs.
*/
#pragma once
#include <libjulia/ASTDataForward.h>
namespace dev
{
namespace julia
{
/**
* Prerequisites: Function Grouper
*/
class MainFunction
{
public:
void operator()(Block& _block);
};
}
}

View File

@ -35,7 +35,6 @@ void NameCollector::operator()(VariableDeclaration const& _varDecl)
void NameCollector::operator ()(FunctionDefinition const& _funDef)
{
m_names.insert(_funDef.name);
m_functions[_funDef.name] = &_funDef;
for (auto const arg: _funDef.parameters)
m_names.insert(arg.name);
for (auto const ret: _funDef.returnVariables)

View File

@ -37,15 +37,18 @@ namespace julia
class NameCollector: public ASTWalker
{
public:
explicit NameCollector(Block const& _block)
{
(*this)(_block);
}
using ASTWalker::operator ();
virtual void operator()(VariableDeclaration const& _varDecl) override;
virtual void operator()(FunctionDefinition const& _funDef) override;
std::set<std::string> const& names() const { return m_names; }
std::map<std::string, FunctionDefinition const*> const& functions() const { return m_functions; }
std::set<std::string> names() const { return m_names; }
private:
std::set<std::string> m_names;
std::map<std::string, FunctionDefinition const*> m_functions;
};
/**

View File

@ -0,0 +1,38 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Optimiser component that can create new unique names.
*/
#include <libjulia/optimiser/NameDispenser.h>
using namespace std;
using namespace dev;
using namespace dev::julia;
string NameDispenser::newName(string const& _prefix)
{
string name = _prefix;
size_t suffix = 0;
while (name.empty() || m_usedNames.count(name))
{
suffix++;
name = _prefix + "_" + std::to_string(suffix);
}
m_usedNames.insert(name);
return name;
}

View File

@ -0,0 +1,37 @@
/*
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/>.
*/
/**
* Optimiser component that can create new unique names.
*/
#pragma once
#include <set>
#include <string>
namespace dev
{
namespace julia
{
struct NameDispenser
{
std::string newName(std::string const& _prefix);
std::set<std::string> m_usedNames;
};
}
}

View File

@ -87,3 +87,12 @@ simple rules like ``x + 0 == x`` to simplify expressions.
## Ineffective Statement Remover
This step removes statements that have no side-effects.
## WebAssembly specific
### Main Function
Changes the topmost block to be a function with a specific name ("main") which has no
inputs nor outputs.
Depends on the Function Grouper.

View File

@ -22,6 +22,7 @@
#include <libjulia/optimiser/Metrics.h>
#include <libjulia/optimiser/ASTCopier.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
@ -44,7 +45,7 @@ void Rematerialiser::visit(Expression& _e)
expressionValid = false;
break;
}
solAssert(m_value.at(name), "");
assertThrow(m_value.at(name), OptimizerException, "");
auto const& value = *m_value.at(name);
if (expressionValid && CodeSize::codeSize(value) <= 7)
_e = (ASTCopier{}).translate(value);

View File

@ -20,6 +20,8 @@
#include <libjulia/optimiser/Semantics.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libevmasm/SemanticInformation.h>
@ -56,5 +58,5 @@ void MovableChecker::operator()(FunctionCall const&)
void MovableChecker::visit(Statement const&)
{
solAssert(false, "Movability for statement requested.");
assertThrow(false, OptimizerException, "Movability for statement requested.");
}

View File

@ -20,8 +20,9 @@
#include <libjulia/optimiser/SyntacticalEquality.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/Exceptions.h>
#include <libdevcore/CommonData.h>
@ -62,7 +63,7 @@ bool SyntacticalEqualityChecker::equal(Expression const& _e1, Expression const&
}
else
{
solAssert(false, "Invlid expression");
assertThrow(false, OptimizerException, "Invalid expression");
}
return false;
}

View File

@ -23,6 +23,7 @@
#include <libjulia/optimiser/NameCollector.h>
#include <libjulia/optimiser/Semantics.h>
#include <libjulia/optimiser/Utilities.h>
#include <libjulia/Exceptions.h>
#include <libsolidity/inlineasm/AsmData.h>
@ -108,8 +109,8 @@ void UnusedPruner::subtractReferences(map<string, size_t> const& _subtrahend)
{
for (auto const& ref: _subtrahend)
{
solAssert(m_references.count(ref.first), "");
solAssert(m_references.at(ref.first) >= ref.second, "");
assertThrow(m_references.count(ref.first), OptimizerException, "");
assertThrow(m_references.at(ref.first) >= ref.second, OptimizerException, "");
m_references[ref.first] -= ref.second;
m_shouldRunAgain = true;
}

View File

@ -22,16 +22,11 @@
#include <libjulia/ASTDataForward.h>
#include <libdevcore/Exceptions.h>
namespace dev
{
namespace julia
{
struct IuliaException: virtual Exception {};
struct OptimizerException: virtual IuliaException {};
/// Removes statements that are just empty blocks (non-recursive).
void removeEmptyBlocks(Block& _block);

View File

@ -28,7 +28,7 @@ else()
endif()
add_library(solidity ${sources} ${headers})
target_link_libraries(solidity PUBLIC evmasm devcore)
target_link_libraries(solidity PUBLIC evmasm devcore ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})
if (${Z3_FOUND})
target_link_libraries(solidity PUBLIC ${Z3_LIBRARY})

View File

@ -0,0 +1,156 @@
/*
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/analysis/ControlFlowAnalyzer.h>
using namespace std;
using namespace dev::solidity;
bool ControlFlowAnalyzer::analyze(ASTNode const& _astRoot)
{
_astRoot.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
{
auto const& functionFlow = m_cfg.functionFlow(_function);
checkUnassignedStorageReturnValues(_function, functionFlow.entry, functionFlow.exit);
return false;
}
set<VariableDeclaration const*> ControlFlowAnalyzer::variablesAssignedInNode(CFGNode const *node)
{
set<VariableDeclaration const*> result;
for (auto expression: node->block.expressions)
{
if (auto const* assignment = dynamic_cast<Assignment const*>(expression))
{
stack<Expression const*> expressions;
expressions.push(&assignment->leftHandSide());
while (!expressions.empty())
{
Expression const* expression = expressions.top();
expressions.pop();
if (auto const *tuple = dynamic_cast<TupleExpression const*>(expression))
for (auto const& component: tuple->components())
expressions.push(component.get());
else if (auto const* identifier = dynamic_cast<Identifier const*>(expression))
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(
identifier->annotation().referencedDeclaration
))
result.insert(variableDeclaration);
}
}
}
return result;
}
void ControlFlowAnalyzer::checkUnassignedStorageReturnValues(
FunctionDefinition const& _function,
CFGNode const* _functionEntry,
CFGNode const* _functionExit
) const
{
if (_function.returnParameterList()->parameters().empty())
return;
map<CFGNode const*, set<VariableDeclaration const*>> unassigned;
{
auto& unassignedAtFunctionEntry = unassigned[_functionEntry];
for (auto const& returnParameter: _function.returnParameterList()->parameters())
if (returnParameter->type()->dataStoredIn(DataLocation::Storage))
unassignedAtFunctionEntry.insert(returnParameter.get());
}
stack<CFGNode const*> nodesToTraverse;
nodesToTraverse.push(_functionEntry);
// walk all paths from entry with maximal set of unassigned return values
while (!nodesToTraverse.empty())
{
auto node = nodesToTraverse.top();
nodesToTraverse.pop();
auto& unassignedAtNode = unassigned[node];
if (node->block.returnStatement != nullptr)
if (node->block.returnStatement->expression())
unassignedAtNode.clear();
if (!unassignedAtNode.empty())
{
// kill all return values to which a value is assigned
for (auto const* variableDeclaration: variablesAssignedInNode(node))
unassignedAtNode.erase(variableDeclaration);
// kill all return values referenced in inline assembly
// a reference is enough, checking whether there actually was an assignment might be overkill
for (auto assembly: node->block.inlineAssemblyStatements)
for (auto const& ref: assembly->annotation().externalReferences)
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
unassignedAtNode.erase(variableDeclaration);
}
for (auto const& exit: node->exits)
{
auto& unassignedAtExit = unassigned[exit];
auto oldSize = unassignedAtExit.size();
unassignedAtExit.insert(unassignedAtNode.begin(), unassignedAtNode.end());
// (re)traverse an exit, if we are on a path with new unassigned return values to consider
// this will terminate, since there is only a finite number of unassigned return values
if (unassignedAtExit.size() > oldSize)
nodesToTraverse.push(exit);
}
}
if (!unassigned[_functionExit].empty())
{
vector<VariableDeclaration const*> unassignedOrdered(
unassigned[_functionExit].begin(),
unassigned[_functionExit].end()
);
sort(
unassignedOrdered.begin(),
unassignedOrdered.end(),
[](VariableDeclaration const* lhs, VariableDeclaration const* rhs) -> bool {
return lhs->id() < rhs->id();
}
);
for (auto const* returnVal: unassignedOrdered)
{
SecondarySourceLocation ssl;
for (CFGNode* lastNodeBeforeExit: _functionExit->entries)
if (unassigned[lastNodeBeforeExit].count(returnVal))
{
if (!!lastNodeBeforeExit->block.returnStatement)
ssl.append("Problematic return:", lastNodeBeforeExit->block.returnStatement->location());
else
ssl.append("Problematic end of function:", _function.location());
}
m_errorReporter.warning(
returnVal->location(),
"This variable is of storage pointer type and might be returned without assignment. "
"This can cause storage corruption. Assign the variable (potentially from itself) "
"to remove this warning.",
ssl
);
}
}
}

View File

@ -0,0 +1,52 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <set>
namespace dev
{
namespace solidity
{
class ControlFlowAnalyzer: private ASTConstVisitor
{
public:
explicit ControlFlowAnalyzer(CFG const& _cfg, ErrorReporter& _errorReporter):
m_cfg(_cfg), m_errorReporter(_errorReporter) {}
bool analyze(ASTNode const& _astRoot);
virtual bool visit(FunctionDefinition const& _function) override;
private:
static std::set<VariableDeclaration const*> variablesAssignedInNode(CFGNode const *node);
void checkUnassignedStorageReturnValues(
FunctionDefinition const& _function,
CFGNode const* _functionEntry,
CFGNode const* _functionExit
) const;
CFG const& m_cfg;
ErrorReporter& m_errorReporter;
};
}
}

View File

@ -0,0 +1,370 @@
/*
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/analysis/ControlFlowBuilder.h>
using namespace dev;
using namespace solidity;
using namespace std;
ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow):
m_nodeContainer(_nodeContainer), m_currentFunctionFlow(_functionFlow), m_currentNode(_functionFlow.entry)
{
}
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function
)
{
auto functionFlow = unique_ptr<FunctionFlow>(new FunctionFlow());
functionFlow->entry = _nodeContainer.newNode();
functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
builder.appendControlFlow(_function);
connect(builder.m_currentNode, functionFlow->exit);
return functionFlow;
}
unique_ptr<ModifierFlow> ControlFlowBuilder::createModifierFlow(
CFG::NodeContainer& _nodeContainer,
ModifierDefinition const& _modifier
)
{
auto modifierFlow = unique_ptr<ModifierFlow>(new ModifierFlow());
modifierFlow->entry = _nodeContainer.newNode();
modifierFlow->exit = _nodeContainer.newNode();
modifierFlow->revert = _nodeContainer.newNode();
modifierFlow->placeholderEntry = _nodeContainer.newNode();
modifierFlow->placeholderExit = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *modifierFlow);
builder.appendControlFlow(_modifier);
connect(builder.m_currentNode, modifierFlow->exit);
return modifierFlow;
}
bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
{
solAssert(!!m_currentNode, "");
switch(_operation.getOperator())
{
case Token::Or:
case Token::And:
{
appendControlFlow(_operation.leftExpression());
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _operation.rightExpression());
mergeFlow(nodes, nodes[1]);
return false;
}
default:
break;
}
return ASTConstVisitor::visit(_operation);
}
bool ControlFlowBuilder::visit(Conditional const& _conditional)
{
solAssert(!!m_currentNode, "");
_conditional.condition().accept(*this);
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _conditional.trueExpression());
nodes[1] = createFlow(nodes[1], _conditional.falseExpression());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{
solAssert(!!m_currentNode, "");
_ifStatement.condition().accept(*this);
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _ifStatement.trueStatement());
if (_ifStatement.falseStatement())
{
nodes[1] = createFlow(nodes[1], *_ifStatement.falseStatement());
mergeFlow(nodes);
}
else
mergeFlow(nodes, nodes[1]);
return false;
}
bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
{
solAssert(!!m_currentNode, "");
if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this);
auto condition = createLabelHere();
if (_forStatement.condition())
appendControlFlow(*_forStatement.condition());
auto loopExpression = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
appendControlFlow(_forStatement.body());
}
placeAndConnectLabel(loopExpression);
if (auto expression = _forStatement.loopExpression())
appendControlFlow(*expression);
connect(m_currentNode, condition);
m_currentNode = afterFor;
return false;
}
bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
{
solAssert(!!m_currentNode, "");
if (_whileStatement.isDoWhile())
{
auto afterWhile = newLabel();
auto whileBody = createLabelHere();
{
// Note that "continue" in this case currently indeed jumps to whileBody
// and not to the condition. This is inconsistent with JavaScript and C and
// therefore a bug. This will be fixed in the future (planned for 0.5.0)
// and the Control Flow Graph will have to be adjusted accordingly.
BreakContinueScope scope(*this, afterWhile, whileBody);
appendControlFlow(_whileStatement.body());
}
appendControlFlow(_whileStatement.condition());
connect(m_currentNode, whileBody);
placeAndConnectLabel(afterWhile);
}
else
{
auto whileCondition = createLabelHere();
appendControlFlow(_whileStatement.condition());
auto nodes = splitFlow<2>();
auto whileBody = nodes[0];
auto afterWhile = nodes[1];
m_currentNode = whileBody;
{
BreakContinueScope scope(*this, afterWhile, whileCondition);
appendControlFlow(_whileStatement.body());
}
connect(m_currentNode, whileCondition);
m_currentNode = afterWhile;
}
return false;
}
bool ControlFlowBuilder::visit(Break const&)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_breakJump, "");
connect(m_currentNode, m_breakJump);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(Continue const&)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_continueJump, "");
connect(m_currentNode, m_continueJump);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(Throw const&)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_currentFunctionFlow.revert, "");
connect(m_currentNode, m_currentFunctionFlow.revert);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(Block const&)
{
solAssert(!!m_currentNode, "");
createLabelHere();
return true;
}
void ControlFlowBuilder::endVisit(Block const&)
{
solAssert(!!m_currentNode, "");
createLabelHere();
}
bool ControlFlowBuilder::visit(Return const& _return)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_currentFunctionFlow.exit, "");
solAssert(!m_currentNode->block.returnStatement, "");
m_currentNode->block.returnStatement = &_return;
connect(m_currentNode, m_currentFunctionFlow.exit);
m_currentNode = newLabel();
return true;
}
bool ControlFlowBuilder::visit(PlaceholderStatement const&)
{
solAssert(!!m_currentNode, "");
auto modifierFlow = dynamic_cast<ModifierFlow const*>(&m_currentFunctionFlow);
solAssert(!!modifierFlow, "");
connect(m_currentNode, modifierFlow->placeholderEntry);
m_currentNode = newLabel();
connect(modifierFlow->placeholderExit, m_currentNode);
return false;
}
bool ControlFlowBuilder::visitNode(ASTNode const& node)
{
solAssert(!!m_currentNode, "");
if (auto const* expression = dynamic_cast<Expression const*>(&node))
m_currentNode->block.expressions.emplace_back(expression);
else if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(&node))
m_currentNode->block.variableDeclarations.emplace_back(variableDeclaration);
else if (auto const* assembly = dynamic_cast<InlineAssembly const*>(&node))
m_currentNode->block.inlineAssemblyStatements.emplace_back(assembly);
return true;
}
bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
{
solAssert(!!m_currentNode, "");
solAssert(!!_functionCall.expression().annotation().type, "");
if (auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type))
switch (functionType->kind())
{
case FunctionType::Kind::Revert:
solAssert(!!m_currentFunctionFlow.revert, "");
_functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this);
connect(m_currentNode, m_currentFunctionFlow.revert);
m_currentNode = newLabel();
return false;
case FunctionType::Kind::Require:
case FunctionType::Kind::Assert:
{
solAssert(!!m_currentFunctionFlow.revert, "");
_functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this);
connect(m_currentNode, m_currentFunctionFlow.revert);
auto nextNode = newLabel();
connect(m_currentNode, nextNode);
m_currentNode = nextNode;
return false;
}
default:
break;
}
return ASTConstVisitor::visit(_functionCall);
}
void ControlFlowBuilder::appendControlFlow(ASTNode const& _node)
{
_node.accept(*this);
}
CFGNode* ControlFlowBuilder::createFlow(CFGNode* _entry, ASTNode const& _node)
{
auto oldCurrentNode = m_currentNode;
m_currentNode = _entry;
appendControlFlow(_node);
auto endNode = m_currentNode;
m_currentNode = oldCurrentNode;
return endNode;
}
void ControlFlowBuilder::connect(CFGNode* _from, CFGNode* _to)
{
solAssert(_from, "");
solAssert(_to, "");
_from->exits.push_back(_to);
_to->entries.push_back(_from);
}
CFGNode* ControlFlowBuilder::newLabel()
{
return m_nodeContainer.newNode();
}
CFGNode* ControlFlowBuilder::createLabelHere()
{
auto label = m_nodeContainer.newNode();
connect(m_currentNode, label);
m_currentNode = label;
return label;
}
void ControlFlowBuilder::placeAndConnectLabel(CFGNode* _node)
{
connect(m_currentNode, _node);
m_currentNode = _node;
}
ControlFlowBuilder::BreakContinueScope::BreakContinueScope(
ControlFlowBuilder& _parser,
CFGNode* _breakJump,
CFGNode* _continueJump
): m_parser(_parser), m_origBreakJump(_parser.m_breakJump), m_origContinueJump(_parser.m_continueJump)
{
m_parser.m_breakJump = _breakJump;
m_parser.m_continueJump = _continueJump;
}
ControlFlowBuilder::BreakContinueScope::~BreakContinueScope()
{
m_parser.m_breakJump = m_origBreakJump;
m_parser.m_continueJump = m_origContinueJump;
}

View File

@ -0,0 +1,143 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <array>
#include <memory>
namespace dev {
namespace solidity {
/** Helper class that builds the control flow of a function or modifier.
* Modifiers are not yet applied to the functions. This is done in a second
* step in the CFG class.
*/
class ControlFlowBuilder: private ASTConstVisitor
{
public:
static std::unique_ptr<FunctionFlow> createFunctionFlow(
CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function
);
static std::unique_ptr<ModifierFlow> createModifierFlow(
CFG::NodeContainer& _nodeContainer,
ModifierDefinition const& _modifier
);
private:
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
virtual bool visit(BinaryOperation const& _operation) override;
virtual bool visit(Conditional const& _conditional) override;
virtual bool visit(IfStatement const& _ifStatement) override;
virtual bool visit(ForStatement const& _forStatement) override;
virtual bool visit(WhileStatement const& _whileStatement) override;
virtual bool visit(Break const&) override;
virtual bool visit(Continue const&) override;
virtual bool visit(Throw const&) override;
virtual bool visit(Block const&) override;
virtual void endVisit(Block const&) override;
virtual bool visit(Return const& _return) override;
virtual bool visit(PlaceholderStatement const&) override;
virtual bool visit(FunctionCall const& _functionCall) override;
/// Appends the control flow of @a _node to the current control flow.
void appendControlFlow(ASTNode const& _node);
/// Starts at @a _entry and parses the control flow of @a _node.
/// @returns The node at which the parsed control flow ends.
/// m_currentNode is not affected (it is saved and restored).
CFGNode* createFlow(CFGNode* _entry, ASTNode const& _node);
/// Creates an arc from @a _from to @a _to.
static void connect(CFGNode* _from, CFGNode* _to);
protected:
virtual bool visitNode(ASTNode const& node) override;
private:
/// Splits the control flow starting at the current node into n paths.
/// m_currentNode is set to nullptr and has to be set manually or
/// using mergeFlow later.
template<size_t n>
std::array<CFGNode*, n> splitFlow()
{
std::array<CFGNode*, n> result;
for (auto& node: result)
{
node = m_nodeContainer.newNode();
connect(m_currentNode, node);
}
m_currentNode = nullptr;
return result;
}
/// Merges the control flow of @a _nodes to @a _endNode.
/// If @a _endNode is nullptr, a new node is creates and used as end node.
/// Sets the merge destination as current node.
/// Note: @a _endNode may be one of the nodes in @a _nodes.
template<size_t n>
void mergeFlow(std::array<CFGNode*, n> const& _nodes, CFGNode* _endNode = nullptr)
{
CFGNode* mergeDestination = (_endNode == nullptr) ? m_nodeContainer.newNode() : _endNode;
for (auto& node: _nodes)
if (node != mergeDestination)
connect(node, mergeDestination);
m_currentNode = mergeDestination;
}
CFGNode* newLabel();
CFGNode* createLabelHere();
void placeAndConnectLabel(CFGNode *_node);
CFG::NodeContainer& m_nodeContainer;
/// The control flow of the function that is currently parsed.
/// Note: this can also be a ModifierFlow
FunctionFlow const& m_currentFunctionFlow;
CFGNode* m_currentNode = nullptr;
/// The current jump destination of break Statements.
CFGNode* m_breakJump = nullptr;
/// The current jump destination of continue Statements.
CFGNode* m_continueJump = nullptr;
/// Helper class that replaces the break and continue jump destinations for the
/// current scope and restores the originals at the end of the scope.
class BreakContinueScope
{
public:
BreakContinueScope(ControlFlowBuilder& _parser, CFGNode* _breakJump, CFGNode* _continueJump);
~BreakContinueScope();
private:
ControlFlowBuilder& m_parser;
CFGNode* m_origBreakJump;
CFGNode* m_origContinueJump;
};
};
}
}

View File

@ -0,0 +1,136 @@
/*
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/analysis/ControlFlowGraph.h>
#include <libsolidity/analysis/ControlFlowBuilder.h>
#include <boost/range/adaptor/reversed.hpp>
#include <algorithm>
using namespace std;
using namespace dev::solidity;
bool CFG::constructFlow(ASTNode const& _astRoot)
{
_astRoot.accept(*this);
applyModifiers();
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
bool CFG::visit(ModifierDefinition const& _modifier)
{
m_modifierControlFlow[&_modifier] = ControlFlowBuilder::createModifierFlow(m_nodeContainer, _modifier);
return false;
}
bool CFG::visit(FunctionDefinition const& _function)
{
m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
return false;
}
FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const
{
solAssert(m_functionControlFlow.count(&_function), "");
return *m_functionControlFlow.find(&_function)->second;
}
CFGNode* CFG::NodeContainer::newNode()
{
m_nodes.emplace_back(new CFGNode());
return m_nodes.back().get();
}
void CFG::applyModifiers()
{
for (auto const& function: m_functionControlFlow)
{
for (auto const& modifierInvocation: boost::adaptors::reverse(function.first->modifiers()))
{
if (auto modifierDefinition = dynamic_cast<ModifierDefinition const*>(
modifierInvocation->name()->annotation().referencedDeclaration
))
{
solAssert(m_modifierControlFlow.count(modifierDefinition), "");
applyModifierFlowToFunctionFlow(*m_modifierControlFlow[modifierDefinition], function.second.get());
}
}
}
}
void CFG::applyModifierFlowToFunctionFlow(
ModifierFlow const& _modifierFlow,
FunctionFlow* _functionFlow
)
{
solAssert(!!_functionFlow, "");
map<CFGNode*, CFGNode*> copySrcToCopyDst;
// inherit the revert node of the function
copySrcToCopyDst[_modifierFlow.revert] = _functionFlow->revert;
// replace the placeholder nodes by the function entry and exit
copySrcToCopyDst[_modifierFlow.placeholderEntry] = _functionFlow->entry;
copySrcToCopyDst[_modifierFlow.placeholderExit] = _functionFlow->exit;
stack<CFGNode*> nodesToCopy;
nodesToCopy.push(_modifierFlow.entry);
// map the modifier entry to a new node that will become the new function entry
copySrcToCopyDst[_modifierFlow.entry] = m_nodeContainer.newNode();
while (!nodesToCopy.empty())
{
CFGNode* copySrcNode = nodesToCopy.top();
nodesToCopy.pop();
solAssert(copySrcToCopyDst.count(copySrcNode), "");
CFGNode* copyDstNode = copySrcToCopyDst[copySrcNode];
copyDstNode->block = copySrcNode->block;
for (auto const& entry: copySrcNode->entries)
{
if (!copySrcToCopyDst.count(entry))
{
copySrcToCopyDst[entry] = m_nodeContainer.newNode();
nodesToCopy.push(entry);
}
copyDstNode->entries.emplace_back(copySrcToCopyDst[entry]);
}
for (auto const& exit: copySrcNode->exits)
{
if (!copySrcToCopyDst.count(exit))
{
copySrcToCopyDst[exit] = m_nodeContainer.newNode();
nodesToCopy.push(exit);
}
copyDstNode->exits.emplace_back(copySrcToCopyDst[exit]);
}
}
// if the modifier control flow never reached its exit node,
// we need to create a new (disconnected) exit node now
if (!copySrcToCopyDst.count(_modifierFlow.exit))
copySrcToCopyDst[_modifierFlow.exit] = m_nodeContainer.newNode();
_functionFlow->entry = copySrcToCopyDst[_modifierFlow.entry];
_functionFlow->exit = copySrcToCopyDst[_modifierFlow.exit];
}

View File

@ -0,0 +1,148 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <map>
#include <memory>
#include <stack>
#include <vector>
namespace dev
{
namespace solidity
{
/** Basic Control Flow Block.
* Basic block of control flow. Consists of a set of (unordered) AST nodes
* for which control flow is always linear. A basic control flow block
* encompasses at most one scope. Reverts are considered to break the control
* flow.
* @todo Handle function calls correctly. So far function calls are not considered
* to change the control flow.
*/
struct ControlFlowBlock
{
/// All variable declarations inside this control flow block.
std::vector<VariableDeclaration const*> variableDeclarations;
/// All expressions inside this control flow block (this includes all subexpressions!).
std::vector<Expression const*> expressions;
/// All inline assembly statements inside in this control flow block.
std::vector<InlineAssembly const*> inlineAssemblyStatements;
/// If control flow returns in this node, the return statement is stored in returnStatement,
/// otherwise returnStatement is nullptr.
Return const* returnStatement = nullptr;
};
/** Node of the Control Flow Graph.
* The control flow is a directed graph connecting control flow blocks.
* An arc between two nodes indicates that the control flow can possibly
* move from its start node to its end node during execution.
*/
struct CFGNode
{
/// Entry nodes. All CFG nodes from which control flow may move into this node.
std::vector<CFGNode*> entries;
/// Exit nodes. All CFG nodes to which control flow may continue after this node.
std::vector<CFGNode*> exits;
/// Control flow in the node.
ControlFlowBlock block;
};
/** Describes the control flow of a function. */
struct FunctionFlow
{
virtual ~FunctionFlow() {}
/// Entry node. Control flow of the function starts here.
/// This node is empty and does not have any entries.
CFGNode* entry = nullptr;
/// Exit node. All non-reverting control flow of the function ends here.
/// This node is empty and does not have any exits, but may have multiple entries
/// (e.g. all return statements of the function).
CFGNode* exit = nullptr;
/// Revert node. Control flow of the function in case of revert.
/// This node is empty does not have any exits, but may have multiple entries
/// (e.g. all assert, require, revert and throw statements).
CFGNode* revert = nullptr;
};
/** Describes the control flow of a modifier.
* Every placeholder breaks the control flow. The node preceding the
* placeholder is assigned placeholderEntry as exit and the node
* following the placeholder is assigned placeholderExit as entry.
*/
struct ModifierFlow: FunctionFlow
{
/// Control flow leading towards a placeholder exit in placeholderEntry.
CFGNode* placeholderEntry = nullptr;
/// Control flow coming from a placeholder enter from placeholderExit.
CFGNode* placeholderExit = nullptr;
};
class CFG: private ASTConstVisitor
{
public:
explicit CFG(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool constructFlow(ASTNode const& _astRoot);
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual bool visit(FunctionDefinition const& _function) override;
FunctionFlow const& functionFlow(FunctionDefinition const& _function) const;
class NodeContainer
{
public:
CFGNode* newNode();
private:
std::vector<std::unique_ptr<CFGNode>> m_nodes;
};
private:
/// Initially the control flow for all functions *ignoring* modifiers and for
/// all modifiers is constructed. Afterwards the control flow of functions
/// is adjusted by applying all modifiers.
void applyModifiers();
/// Creates a copy of the modifier flow @a _modifierFlow, while replacing the
/// placeholder entry and exit with the function entry and exit, as well as
/// replacing the modifier revert node with the function's revert node.
/// The resulting control flow is the new function flow with the modifier applied.
/// @a _functionFlow is updated in-place.
void applyModifierFlowToFunctionFlow(
ModifierFlow const& _modifierFlow,
FunctionFlow* _functionFlow
);
ErrorReporter& m_errorReporter;
/// Node container.
/// All nodes allocated during the construction of the control flow graph
/// are owned by the CFG class and stored in this container.
NodeContainer m_nodeContainer;
std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow;
std::map<ModifierDefinition const*, std::unique_ptr<ModifierFlow>> m_modifierControlFlow;
};
}
}

View File

@ -1067,6 +1067,7 @@ void TypeChecker::endVisit(EmitStatement const& _emit)
{
if (
_emit.eventCall().annotation().kind != FunctionCallKind::FunctionCall ||
type(_emit.eventCall().expression())->category() != Type::Category::Function ||
dynamic_cast<FunctionType const&>(*type(_emit.eventCall().expression())).kind() != FunctionType::Kind::Event
)
m_errorReporter.typeError(_emit.eventCall().expression().location(), "Expression has to be an event invocation.");
@ -1075,6 +1076,7 @@ void TypeChecker::endVisit(EmitStatement const& _emit)
bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
{
bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (!_statement.initialValue())
{
// No initial value is only permitted for single variables with specified type.
@ -1091,7 +1093,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
if (varDecl.referenceLocation() == VariableDeclaration::Location::Default)
errorText += " Did you mean '<type> memory " + varDecl.name() + "'?";
solAssert(m_scope, "");
if (m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050))
if (v050)
m_errorReporter.declarationError(varDecl.location(), errorText);
else
m_errorReporter.warning(varDecl.location(), errorText);
@ -1131,12 +1133,33 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
") in value for variable assignment (0) needed"
);
}
else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back())
m_errorReporter.fatalTypeError(
_statement.location(),
"Wildcard both at beginning and end of variable declaration list is only allowed "
"if the number of components is equal."
);
else if (valueTypes.size() != variables.size())
{
if (v050)
m_errorReporter.fatalTypeError(
_statement.location(),
"Different number of components on the left hand side (" +
toString(variables.size()) +
") than on the right hand side (" +
toString(valueTypes.size()) +
")."
);
else if (!variables.front() && !variables.back())
m_errorReporter.fatalTypeError(
_statement.location(),
"Wildcard both at beginning and end of variable declaration list is only allowed "
"if the number of components is equal."
);
else
m_errorReporter.warning(
_statement.location(),
"Different number of components on the left hand side (" +
toString(variables.size()) +
") than on the right hand side (" +
toString(valueTypes.size()) +
")."
);
}
size_t minNumValues = variables.size();
if (!variables.empty() && (!variables.back() || !variables.front()))
--minNumValues;
@ -1200,8 +1223,9 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
string extension;
if (auto type = dynamic_cast<IntegerType const*>(var.annotation().type.get()))
{
int numBits = type->numBits();
unsigned numBits = type->numBits();
bool isSigned = type->isSigned();
solAssert(numBits > 0, "");
string minValue;
string maxValue;
if (isSigned)
@ -1333,6 +1357,7 @@ bool TypeChecker::visit(Conditional const& _conditional)
bool TypeChecker::visit(Assignment const& _assignment)
{
bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
requireLValue(_assignment.leftHandSide());
TypePointer t = type(_assignment.leftHandSide());
_assignment.annotation().type = t;
@ -1345,11 +1370,29 @@ bool TypeChecker::visit(Assignment const& _assignment)
);
// Sequenced assignments of tuples is not valid, make the result a "void" type.
_assignment.annotation().type = make_shared<TupleType>();
expectType(_assignment.rightHandSide(), *tupleType);
// expectType does not cause fatal errors, so we have to check again here.
if (dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get()))
if (TupleType const* rhsType = dynamic_cast<TupleType const*>(type(_assignment.rightHandSide()).get()))
{
checkDoubleStorageAssignment(_assignment);
// @todo For 0.5.0, this code shoud move to TupleType::isImplicitlyConvertibleTo,
// but we cannot do it right now.
if (rhsType->components().size() != tupleType->components().size())
{
string message =
"Different number of components on the left hand side (" +
toString(tupleType->components().size()) +
") than on the right hand side (" +
toString(rhsType->components().size()) +
").";
if (v050)
m_errorReporter.typeError(_assignment.location(), message);
else
m_errorReporter.warning(_assignment.location(), message);
}
}
}
else if (t->category() == Type::Category::Mapping)
{
@ -1406,8 +1449,10 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
}
else
{
bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
bool isPure = true;
TypePointer inlineArrayType;
for (size_t i = 0; i < components.size(); ++i)
{
// Outside of an lvalue-context, the only situation where a component can be empty is (x,).
@ -1418,6 +1463,17 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
components[i]->accept(*this);
types.push_back(type(*components[i]));
if (types[i]->category() == Type::Category::Tuple)
if (dynamic_cast<TupleType const&>(*types[i]).components().empty())
{
if (_tuple.isInlineArray())
m_errorReporter.fatalTypeError(components[i]->location(), "Array component cannot be empty.");
if (v050)
m_errorReporter.fatalTypeError(components[i]->location(), "Tuple component cannot be empty.");
else
m_errorReporter.warning(components[i]->location(), "Tuple component cannot be empty.");
}
// Note: code generation will visit each of the expression even if they are not assigned from.
if (types[i]->category() == Type::Category::RationalNumber && components.size() > 1)
if (!dynamic_cast<RationalNumberType const&>(*types[i]).mobileType())
@ -1648,12 +1704,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else
_functionCall.annotation().type = make_shared<TupleType>(returnTypes);
bool const v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
string msg;
if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3)
m_errorReporter.warning(_functionCall.location(), "\"sha3\" has been deprecated in favour of \"keccak256\"");
msg = "\"sha3\" has been deprecated in favour of \"keccak256\"";
else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct)
m_errorReporter.warning(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\"");
msg = "\"suicide\" has been deprecated in favour of \"selfdestruct\"";
if (!msg.empty())
{
if (v050)
m_errorReporter.typeError(_functionCall.location(), msg);
else
m_errorReporter.warning(_functionCall.location(), msg);
}
}
if (!m_insideEmitStatement && functionType->kind() == FunctionType::Kind::Event)
{
@ -1674,18 +1740,55 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
{
/* If no mobile type is available an error will be raised elsewhere. */
if (literal->mobileType())
m_errorReporter.warning(
arguments[i]->location(),
"The type of \"" +
argType->toString() +
"\" was inferred as " +
literal->mobileType()->toString() +
". This is probably not desired. Use an explicit type to silence this warning."
);
{
if (v050)
m_errorReporter.typeError(
arguments[i]->location(),
"Cannot perform packed encoding for a literal. Please convert it to an explicit type first."
);
else
m_errorReporter.warning(
arguments[i]->location(),
"The type of \"" +
argType->toString() +
"\" was inferred as " +
literal->mobileType()->toString() +
". This is probably not desired. Use an explicit type to silence this warning."
);
}
}
}
}
if (functionType->takesSinglePackedBytesParameter())
{
if (
(arguments.size() > 1) ||
(arguments.size() == 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
)
{
string msg =
"This function only accepts a single \"bytes\" argument. Please use "
"\"abi.encodePacked(...)\" or a similar function to encode the data.";
if (v050)
m_errorReporter.typeError(_functionCall.location(), msg);
else
m_errorReporter.warning(_functionCall.location(), msg);
}
if (arguments.size() == 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
{
string msg =
"The provided argument of type " +
type(*arguments.front())->toString() +
" is not implicitly convertible to expected type bytes memory.";
if (v050)
m_errorReporter.typeError(_functionCall.location(), msg);
else
m_errorReporter.warning(_functionCall.location(), msg);
}
}
if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
{
solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
@ -2019,6 +2122,9 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto tt = dynamic_cast<TypeType const*>(exprType.get()))
if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true;
if (auto magicType = dynamic_cast<MagicType const*>(exprType.get()))
if (magicType->kind() == MagicType::Kind::ABI)
annotation.isPure = true;
return false;
}
@ -2202,6 +2308,7 @@ void TypeChecker::endVisit(Literal const& _literal)
"For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals"
);
}
if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None)
{
if (v050)
@ -2217,6 +2324,21 @@ void TypeChecker::endVisit(Literal const& _literal)
"You can use an expression of the form \"0x1234 * 1 day\" instead."
);
}
if (_literal.subDenomination() == Literal::SubDenomination::Year)
{
if (v050)
m_errorReporter.typeError(
_literal.location(),
"Using \"years\" as a unit denomination is deprecated."
);
else
m_errorReporter.warning(
_literal.location(),
"Using \"years\" as a unit denomination is deprecated."
);
}
if (!_literal.annotation().type)
_literal.annotation().type = Type::forLiteral(_literal);

View File

@ -146,6 +146,7 @@ private:
class Scopable
{
public:
virtual ~Scopable() = default;
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
ASTNode const* scope() const { return m_scope; }
@ -307,6 +308,7 @@ private:
class VariableScope
{
public:
virtual ~VariableScope() = default;
void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); }
std::vector<VariableDeclaration const*> const& localVariables() const { return m_localVariables; }
@ -320,6 +322,7 @@ private:
class Documented
{
public:
virtual ~Documented() = default;
explicit Documented(ASTPointer<ASTString> const& _documentation): m_documentation(_documentation) {}
/// @return A shared pointer of an ASTString.
@ -336,6 +339,7 @@ protected:
class ImplementationOptional
{
public:
virtual ~ImplementationOptional() = default;
explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {}
/// @return whether this node is fully implemented or not

View File

@ -43,6 +43,7 @@ namespace solidity
class ASTVisitor
{
public:
virtual ~ASTVisitor() = default;
virtual bool visit(SourceUnit& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective& _node) { return visitNode(_node); }
@ -147,6 +148,7 @@ protected:
class ASTConstVisitor
{
public:
virtual ~ASTConstVisitor() = default;
virtual bool visit(SourceUnit const& _node) { return visitNode(_node); }
virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); }
virtual bool visit(ImportDirective const& _node) { return visitNode(_node); }

View File

@ -425,14 +425,14 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
}
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
IntegerType::IntegerType(unsigned _bits, IntegerType::Modifier _modifier):
m_bits(_bits), m_modifier(_modifier)
{
if (isAddress())
solAssert(m_bits == 160, "");
solAssert(
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(_bits)
"Invalid bit number for integer type: " + dev::toString(m_bits)
);
}
@ -584,7 +584,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
{
if (isAddress())
return {
{"balance", make_shared<IntegerType >(256)},
{"balance", make_shared<IntegerType>(256)},
{"call", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCall, true, StateMutability::Payable)},
{"callcode", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareCallCode, true, StateMutability::Payable)},
{"delegatecall", make_shared<FunctionType>(strings(), strings{"bool"}, FunctionType::Kind::BareDelegateCall, true)},
@ -595,13 +595,12 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
return MemberList::MemberMap();
}
FixedPointType::FixedPointType(int _totalBits, int _fractionalDigits, FixedPointType::Modifier _modifier):
FixedPointType::FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, FixedPointType::Modifier _modifier):
m_totalBits(_totalBits), m_fractionalDigits(_fractionalDigits), m_modifier(_modifier)
{
solAssert(
8 <= m_totalBits && m_totalBits <= 256 && m_totalBits % 8 == 0 &&
0 <= m_fractionalDigits && m_fractionalDigits <= 80,
"Invalid bit number(s) for fixed type: " +
8 <= m_totalBits && m_totalBits <= 256 && m_totalBits % 8 == 0 && m_fractionalDigits <= 80,
"Invalid bit number(s) for fixed type: " +
dev::toString(_totalBits) + "x" + dev::toString(_fractionalDigits)
);
}
@ -627,8 +626,7 @@ bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool FixedPointType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
return _convertTo.category() == category() ||
_convertTo.category() == Category::Integer ||
_convertTo.category() == Category::FixedBytes;
(_convertTo.category() == Category::Integer && !dynamic_cast<IntegerType const&>(_convertTo).isAddress());
}
TypePointer FixedPointType::unaryOperatorResult(Token::Value _operator) const
@ -682,13 +680,7 @@ bigint FixedPointType::minIntegerValue() const
TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (
_other->category() != Category::RationalNumber &&
_other->category() != category() &&
_other->category() != Category::Integer
)
return TypePointer();
auto commonType = Type::commonType(shared_from_this(), _other); //might be fixed point or integer
auto commonType = Type::commonType(shared_from_this(), _other);
if (!commonType)
return TypePointer();
@ -696,19 +688,16 @@ TypePointer FixedPointType::binaryOperatorResult(Token::Value _operator, TypePoi
// All fixed types can be compared
if (Token::isCompareOp(_operator))
return commonType;
if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator))
if (Token::isBitOp(_operator) || Token::isBooleanOp(_operator) || _operator == Token::Exp)
return TypePointer();
if (auto fixType = dynamic_pointer_cast<FixedPointType const>(commonType))
{
if (Token::Exp == _operator)
return TypePointer();
}
else if (auto intType = dynamic_pointer_cast<IntegerType const>(commonType))
if (intType->isAddress())
return TypePointer();
return commonType;
}
std::shared_ptr<IntegerType> FixedPointType::asIntegerType() const
{
return make_shared<IntegerType>(numBits(), isSigned() ? IntegerType::Modifier::Signed : IntegerType::Modifier::Unsigned);
}
tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
{
rational value;
@ -860,7 +849,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
if (isFractional())
return false;
IntegerType const& targetType = dynamic_cast<IntegerType const&>(_convertTo);
int forSignBit = (targetType.isSigned() ? 1 : 0);
unsigned forSignBit = (targetType.isSigned() ? 1 : 0);
if (m_value > rational(0))
{
if (m_value.numerator() <= (u256(-1) >> (256 - targetType.numBits() + forSignBit)))
@ -1148,7 +1137,7 @@ u256 RationalNumberType::literalValue(Literal const*) const
auto fixed = fixedPointType();
solAssert(fixed, "");
int fractionalDigits = fixed->fractionalDigits();
shiftedValue = (m_value.numerator() / m_value.denominator()) * pow(bigint(10), fractionalDigits);
shiftedValue = m_value.numerator() * pow(bigint(10), fractionalDigits) / m_value.denominator();
}
// we ignore the literal and hope that the type was correctly determined
@ -1274,17 +1263,12 @@ bool StringLiteralType::isValidUTF8() const
return dev::validateUTF8(m_value);
}
shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
FixedBytesType::FixedBytesType(unsigned _bytes): m_bytes(_bytes)
{
if (_literal.length() <= 32)
return make_shared<FixedBytesType>(_literal.length());
return shared_ptr<FixedBytesType>();
}
FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes)
{
solAssert(m_bytes >= 0 && m_bytes <= 32,
"Invalid byte number for fixed bytes type: " + dev::toString(m_bytes));
solAssert(
m_bytes > 0 && m_bytes <= 32,
"Invalid byte number for fixed bytes type: " + dev::toString(m_bytes)
);
}
bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@ -2881,7 +2865,11 @@ bool FunctionType::isPure() const
m_kind == Kind::RIPEMD160 ||
m_kind == Kind::AddMod ||
m_kind == Kind::MulMod ||
m_kind == Kind::ObjectCreation;
m_kind == Kind::ObjectCreation ||
m_kind == Kind::ABIEncode ||
m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeWithSignature;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)

View File

@ -138,6 +138,7 @@ private:
class Type: private boost::noncopyable, public std::enable_shared_from_this<Type>
{
public:
virtual ~Type() = default;
enum class Category
{
Integer, RationalNumber, StringLiteral, Bool, FixedPoint, Array,
@ -318,7 +319,7 @@ public:
};
virtual Category category() const override { return Category::Integer; }
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned);
explicit IntegerType(unsigned _bits, Modifier _modifier = Modifier::Unsigned);
virtual std::string richIdentifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
@ -341,7 +342,7 @@ public:
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
int numBits() const { return m_bits; }
unsigned numBits() const { return m_bits; }
bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
@ -349,7 +350,7 @@ public:
bigint maxValue() const;
private:
int m_bits;
unsigned m_bits;
Modifier m_modifier;
};
@ -365,7 +366,7 @@ public:
};
virtual Category category() const override { return Category::FixedPoint; }
explicit FixedPointType(int _totalBits, int _fractionalDigits, Modifier _modifier = Modifier::Unsigned);
explicit FixedPointType(unsigned _totalBits, unsigned _fractionalDigits, Modifier _modifier = Modifier::Unsigned);
virtual std::string richIdentifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
@ -385,9 +386,9 @@ public:
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
/// Number of bits used for this type in total.
int numBits() const { return m_totalBits; }
unsigned numBits() const { return m_totalBits; }
/// Number of decimal digits after the radix point.
int fractionalDigits() const { return m_fractionalDigits; }
unsigned fractionalDigits() const { return m_fractionalDigits; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
/// @returns the largest integer value this type con hold. Note that this is not the
/// largest value in general.
@ -396,9 +397,12 @@ public:
/// smallest value in general.
bigint minIntegerValue() const;
/// @returns the smallest integer type that can hold this type with fractional parts shifted to integers.
std::shared_ptr<IntegerType> asIntegerType() const;
private:
int m_totalBits;
int m_fractionalDigits;
unsigned m_totalBits;
unsigned m_fractionalDigits;
Modifier m_modifier;
};
@ -502,11 +506,7 @@ class FixedBytesType: public Type
public:
virtual Category category() const override { return Category::FixedBytes; }
/// @returns the smallest bytes type for the given literal or an empty pointer
/// if no type fits.
static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal);
explicit FixedBytesType(int _bytes);
explicit FixedBytesType(unsigned _bytes);
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
@ -524,10 +524,10 @@ public:
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
int numBytes() const { return m_bytes; }
unsigned numBytes() const { return m_bytes; }
private:
int m_bytes;
unsigned m_bytes;
};
/**
@ -1046,8 +1046,8 @@ public:
return *m_declaration;
}
bool hasDeclaration() const { return !!m_declaration; }
/// @returns true if the result of this function only depends on its arguments
/// and it does not modify the state.
/// @returns true if the result of this function only depends on its arguments,
/// does not modify the state and is a compile-time constant.
/// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const;
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
@ -1058,6 +1058,22 @@ public:
/// true iff arguments are to be padded to multiples of 32 bytes for external calls
bool padArguments() const { return !(m_kind == Kind::SHA3 || m_kind == Kind::SHA256 || m_kind == Kind::RIPEMD160 || m_kind == Kind::ABIEncodePacked); }
bool takesArbitraryParameters() const { return m_arbitraryParameters; }
/// true iff the function takes a single bytes parameter and it is passed on without padding.
/// @todo until 0.5.0, this is just a "recommendation".
bool takesSinglePackedBytesParameter() const
{
// @todo add the call kinds here with 0.5.0 and perhaps also log0.
switch (m_kind)
{
case FunctionType::Kind::SHA3:
case FunctionType::Kind::SHA256:
case FunctionType::Kind::RIPEMD160:
return true;
default:
return false;
}
}
bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
bool bound() const { return m_bound; }

View File

@ -371,7 +371,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
if (toCategory == Type::Category::Integer)
body =
Whiskers("converted := <convert>(<shift>(value))")
("shift", shiftRightFunction(256 - from.numBytes() * 8, false))
("shift", shiftRightFunction(256 - from.numBytes() * 8))
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
.render();
else
@ -458,8 +458,8 @@ string ABIFunctions::splitExternalFunctionIdFunction()
}
)")
("functionName", functionName)
("shr32", shiftRightFunction(32, false))
("shr64", shiftRightFunction(64, false))
("shr32", shiftRightFunction(32))
("shr64", shiftRightFunction(64))
.render();
});
}
@ -831,7 +831,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
templ("encodeToMemoryFun", encodeToMemoryFun);
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
for (size_t i = 0; i < itemsPerSlot; ++i)
items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8, false);
items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8);
templ("items", items);
return templ.render();
}
@ -927,7 +927,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
}
else
memberTempl("preprocess", "");
memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)");
memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8) + "(slotValue)");
}
else
{
@ -1401,37 +1401,75 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
string ABIFunctions::shiftLeftFunction(size_t _numBits)
{
solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits);
return createFunction(functionName, [&]() {
solAssert(_numBits < 256, "");
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := mul(value, <multiplier>)
}
)")
("functionName", functionName)
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
if (m_evmVersion.hasBitwiseShifting())
{
return createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := shl(<numBits>, value)
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
.render();
});
}
else
{
return createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := mul(value, <multiplier>)
}
)")
("functionName", functionName)
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
}
string ABIFunctions::shiftRightFunction(size_t _numBits, bool _signed)
string ABIFunctions::shiftRightFunction(size_t _numBits)
{
string functionName = "shift_right_" + to_string(_numBits) + (_signed ? "_signed" : "_unsigned");
return createFunction(functionName, [&]() {
solAssert(_numBits < 256, "");
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := <div>(value, <multiplier>)
}
)")
("functionName", functionName)
("div", _signed ? "sdiv" : "div")
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
solAssert(_numBits < 256, "");
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
if (m_evmVersion.hasBitwiseShifting())
{
return createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := shr(<numBits>, value)
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
.render();
});
}
else
{
return createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := div(value, <multiplier>)
}
)")
("functionName", functionName)
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
}
string ABIFunctions::roundUpFunction()

View File

@ -22,6 +22,8 @@
#pragma once
#include <libsolidity/interface/EVMVersion.h>
#include <libsolidity/ast/ASTForward.h>
#include <vector>
@ -48,6 +50,8 @@ using TypePointers = std::vector<TypePointer>;
class ABIFunctions
{
public:
explicit ABIFunctions(EVMVersion _evmVersion = EVMVersion{}) : m_evmVersion(_evmVersion) {}
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
/// into memory, converting the types to @a _targetTypes on the fly.
/// Parameters are: <headStart> <value_n> ... <value_1>, i.e.
@ -191,7 +195,7 @@ private:
std::string copyToMemoryFunction(bool _fromCalldata);
std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(size_t _numBits, bool _signed);
std::string shiftRightFunction(size_t _numBits);
/// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction();
@ -225,6 +229,8 @@ private:
/// Map from function name to code for a multi-use function.
std::map<std::string, std::string> m_requestedFunctions;
EVMVersion m_evmVersion;
};
}

View File

@ -55,7 +55,8 @@ public:
explicit CompilerContext(EVMVersion _evmVersion = EVMVersion{}, CompilerContext* _runtimeContext = nullptr):
m_asm(std::make_shared<eth::Assembly>()),
m_evmVersion(_evmVersion),
m_runtimeContext(_runtimeContext)
m_runtimeContext(_runtimeContext),
m_abiFunctions(m_evmVersion)
{
if (m_runtimeContext)
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());

View File

@ -89,7 +89,6 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
toSizeAfterFreeMemoryPointer();
m_context << Instruction::REVERT;
m_context.adjustStackOffset(_argumentType.sizeOnStack());
}
unsigned CompilerUtils::loadFromMemory(
@ -110,7 +109,7 @@ void CompilerUtils::loadFromMemoryDynamic(
bool _padToWordBoundaries,
bool _keepUpdatedMemoryOffset
)
{
{
if (_keepUpdatedMemoryOffset)
m_context << Instruction::DUP1;
@ -396,7 +395,7 @@ void CompilerUtils::encodeToMemory(
// leave end_of_mem as dyn head pointer
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++;
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables.");
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables.");
}
else
{
@ -599,15 +598,15 @@ void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
if (_leftAligned)
{
m_context << Instruction::DUP1;
rightShiftNumberOnStack(64 + 32, false);
rightShiftNumberOnStack(64 + 32);
// <input> <address>
m_context << Instruction::SWAP1;
rightShiftNumberOnStack(64, false);
rightShiftNumberOnStack(64);
}
else
{
m_context << Instruction::DUP1;
rightShiftNumberOnStack(32, false);
rightShiftNumberOnStack(32);
m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1;
}
m_context << u256(0xffffffffUL) << Instruction::AND;
@ -675,7 +674,7 @@ void CompilerUtils::convertType(
// conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8, false);
rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8);
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
@ -688,7 +687,7 @@ void CompilerUtils::convertType(
m_context << Instruction::POP << u256(0);
else if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded)
{
int bytes = min(typeOnStack.numBytes(), targetType.numBytes());
unsigned bytes = min(typeOnStack.numBytes(), targetType.numBytes());
m_context << ((u256(1) << (256 - bytes * 8)) - 1);
m_context << Instruction::NOT << Instruction::AND;
}
@ -741,7 +740,7 @@ void CompilerUtils::convertType(
else if (targetTypeCategory == Type::Category::FixedPoint)
{
solAssert(
stackTypeCategory == Type::Category::Integer ||
stackTypeCategory == Type::Category::Integer ||
stackTypeCategory == Type::Category::RationalNumber ||
stackTypeCategory == Type::Category::FixedPoint,
"Invalid conversion to FixedMxNType requested."
@ -796,7 +795,7 @@ void CompilerUtils::convertType(
bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes)
{
int const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
solAssert(data.size() <= 32, "");
m_context << (h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes))));
}
@ -1242,7 +1241,7 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
bool leftAligned = _type.category() == Type::Category::FixedBytes;
// add leading or trailing zeros by dividing/multiplying depending on alignment
int shiftFactor = (32 - numBytes) * 8;
rightShiftNumberOnStack(shiftFactor, false);
rightShiftNumberOnStack(shiftFactor);
if (leftAligned)
leftShiftNumberOnStack(shiftFactor);
}
@ -1265,13 +1264,20 @@ void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
void CompilerUtils::leftShiftNumberOnStack(unsigned _bits)
{
solAssert(_bits < 256, "");
m_context << (u256(1) << _bits) << Instruction::MUL;
if (m_context.evmVersion().hasBitwiseShifting())
m_context << _bits << Instruction::SHL;
else
m_context << (u256(1) << _bits) << Instruction::MUL;
}
void CompilerUtils::rightShiftNumberOnStack(unsigned _bits, bool _isSigned)
void CompilerUtils::rightShiftNumberOnStack(unsigned _bits)
{
solAssert(_bits < 256, "");
m_context << (u256(1) << _bits) << Instruction::SWAP1 << (_isSigned ? Instruction::SDIV : Instruction::DIV);
// NOTE: If we add signed right shift, SAR rounds differently than SDIV
if (m_context.evmVersion().hasBitwiseShifting())
m_context << _bits << Instruction::SHR;
else
m_context << (u256(1) << _bits) << Instruction::SWAP1 << Instruction::DIV;
}
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords)

View File

@ -254,7 +254,7 @@ public:
/// Helper function to shift top value on the stack to the right.
/// Stack pre: <value> <shift_by_bits>
/// Stack post: <shifted_value>
void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false);
void rightShiftNumberOnStack(unsigned _bits);
/// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic();

View File

@ -548,7 +548,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (m_context.runtimeContext())
// We have a runtime context, so we need the creation part.
utils().rightShiftNumberOnStack(32, false);
utils().rightShiftNumberOnStack(32);
else
// Extract the runtime part.
m_context << ((u256(1) << 32) - 1) << Instruction::AND;
@ -933,7 +933,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// condition was not met, flag an error
m_context.appendInvalid();
else if (arguments.size() > 1)
{
utils().revertWithStringData(*arguments.at(1)->annotation().type);
// Here, the argument is consumed, but in the other branch, it is still there.
m_context.adjustStackOffset(arguments.at(1)->annotation().type->sizeOnStack());
}
else
m_context.appendRevert();
// the success branch
@ -1706,13 +1710,23 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
m_context.appendConditionalInvalid();
}
m_context << Instruction::SWAP1;
// stack: value_to_shift shift_amount
switch (_operator)
{
case Token::SHL:
m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::MUL;
if (m_context.evmVersion().hasBitwiseShifting())
m_context << Instruction::SHL;
else
m_context << u256(2) << Instruction::EXP << Instruction::MUL;
break;
case Token::SAR:
m_context << Instruction::SWAP1 << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
// NOTE: SAR rounds differently than SDIV
if (m_context.evmVersion().hasBitwiseShifting() && !c_valueSigned)
m_context << Instruction::SHR;
else
m_context << u256(2) << Instruction::EXP << Instruction::SWAP1 << (c_valueSigned ? Instruction::SDIV : Instruction::DIV);
break;
case Token::SHR:
default:

View File

@ -267,7 +267,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
else if (m_dataType->category() == Type::Category::FixedBytes)
{
solAssert(_sourceType.category() == Type::Category::FixedBytes, "source not fixed bytes");
CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes(), false);
CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes());
}
else
{

View File

@ -49,6 +49,7 @@ protected:
m_context(_compilerContext), m_dataType(_dataType) {}
public:
virtual ~LValue() {}
/// @returns the number of stack slots occupied by the lvalue reference
virtual unsigned sizeOnStack() const { return 1; }
/// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true,

View File

@ -58,6 +58,19 @@ void SMTChecker::analyze(SourceUnit const& _source)
_source.accept(*this);
}
bool SMTChecker::visit(ContractDefinition const& _contract)
{
for (auto _var : _contract.stateVariables())
if (_var->type()->isValueType())
createVariable(*_var);
return true;
}
void SMTChecker::endVisit(ContractDefinition const&)
{
m_stateVariables.clear();
}
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
{
if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
@ -72,13 +85,13 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
"Assertion checker does not yet support constructors and functions with modifiers."
);
m_currentFunction = &_function;
// We only handle local variables, so we clear at the beginning of the function.
// If we add storage variables, those should be cleared differently.
m_interface->reset();
m_variables.clear();
m_variables.insert(m_stateVariables.begin(), m_stateVariables.end());
m_pathConditions.clear();
m_conditionalExecutionHappened = false;
m_loopExecutionHappened = false;
initializeLocalVariables(_function);
resetStateVariables();
return true;
}
@ -132,6 +145,7 @@ bool SMTChecker::visit(WhileStatement const& _node)
visitBranch(_node.body(), expr(_node.condition()));
}
m_loopExecutionHappened = true;
resetVariables(touchedVariables);
return false;
@ -171,7 +185,7 @@ bool SMTChecker::visit(ForStatement const& _node)
m_interface->pop();
m_conditionalExecutionHappened = true;
m_loopExecutionHappened = true;
std::swap(sequenceCountersStart, m_variables);
resetVariables(touchedVariables);
@ -548,7 +562,6 @@ SMTChecker::VariableSequenceCounters SMTChecker::visitBranch(Statement const& _s
if (_condition)
popPathCondition();
m_conditionalExecutionHappened = true;
std::swap(m_variables, beforeVars);
return beforeVars;
@ -586,15 +599,21 @@ void SMTChecker::checkCondition(
expressionsToEvaluate.emplace_back(currentValue(*var));
expressionNames.push_back(var->name());
}
for (auto const& var: m_stateVariables)
if (knownVariable(*var.first))
{
expressionsToEvaluate.emplace_back(currentValue(*var.first));
expressionNames.push_back(var.first->name());
}
}
smt::CheckResult result;
vector<string> values;
tie(result, values) = checkSatisfiableAndGenerateModel(expressionsToEvaluate);
string conditionalComment;
if (m_conditionalExecutionHappened)
conditionalComment =
"\nNote that some information is erased after conditional execution of parts of the code.\n"
string loopComment;
if (m_loopExecutionHappened)
loopComment =
"\nNote that some information is erased after the execution of loops.\n"
"You can re-introduce information using require().";
switch (result)
{
@ -607,17 +626,18 @@ void SMTChecker::checkCondition(
message << " for:\n";
solAssert(values.size() == expressionNames.size(), "");
for (size_t i = 0; i < values.size(); ++i)
message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
if (expressionsToEvaluate.at(i).name != values.at(i))
message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
}
else
message << ".";
m_errorReporter.warning(_location, message.str() + conditionalComment);
m_errorReporter.warning(_location, message.str() + loopComment);
break;
}
case smt::CheckResult::UNSATISFIABLE:
break;
case smt::CheckResult::UNKNOWN:
m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment);
m_errorReporter.warning(_location, _description + " might happen here." + loopComment);
break;
case smt::CheckResult::ERROR:
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
@ -722,6 +742,15 @@ void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
setZeroValue(*retParam);
}
void SMTChecker::resetStateVariables()
{
for (auto const& variable: m_stateVariables)
{
newValue(*variable.first);
setUnknownValue(*variable.first);
}
}
void SMTChecker::resetVariables(vector<Declaration const*> _variables)
{
for (auto const* decl: _variables)
@ -752,7 +781,14 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{
solAssert(m_variables.count(&_varDecl) == 0, "");
m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
solAssert(m_stateVariables.count(&_varDecl) == 0, "");
if (_varDecl.isLocalVariable())
m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
else
{
solAssert(_varDecl.isStateVariable(), "");
m_stateVariables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
}
return true;
}
else

View File

@ -50,6 +50,8 @@ private:
// because the order of expression evaluation is undefined
// TODO: or just force a certain order, but people might have a different idea about that.
virtual bool visit(ContractDefinition const& _node) override;
virtual void endVisit(ContractDefinition const& _node) override;
virtual void endVisit(VariableDeclaration const& _node) override;
virtual bool visit(FunctionDefinition const& _node) override;
virtual void endVisit(FunctionDefinition const& _node) override;
@ -111,6 +113,7 @@ private:
smt::CheckResult checkSatisfiable();
void initializeLocalVariables(FunctionDefinition const& _function);
void resetStateVariables();
void resetVariables(std::vector<Declaration const*> _variables);
/// Given two different branches and the touched variables,
/// merge the touched variables into after-branch ite variables
@ -160,9 +163,10 @@ private:
std::shared_ptr<smt::SolverInterface> m_interface;
std::shared_ptr<VariableUsage> m_variableUsage;
bool m_conditionalExecutionHappened = false;
bool m_loopExecutionHappened = false;
std::map<Expression const*, smt::Expression> m_expressions;
std::map<Declaration const*, SSAVariable> m_variables;
std::map<Declaration const*, SSAVariable> m_stateVariables;
std::vector<smt::Expression> m_pathConditions;
ErrorReporter& m_errorReporter;

View File

@ -193,6 +193,7 @@ DEV_SIMPLE_EXCEPTION(SolverError);
class SolverInterface
{
public:
virtual ~SolverInterface() = default;
virtual void reset() = 0;
virtual void push() = 0;

View File

@ -40,6 +40,7 @@ public:
Declaration const& _decl,
smt::SolverInterface& _interface
);
virtual ~SymbolicVariable() = default;
smt::Expression operator()(int _seq) const
{

View File

@ -33,7 +33,6 @@ VariableUsage::VariableUsage(ASTNode const& _node)
solAssert(declaration, "");
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
if (
varDecl->isLocalVariable() &&
identifier->annotation().lValueRequested &&
varDecl->annotation().type->isValueType()
)

View File

@ -54,6 +54,7 @@ bool AsmAnalyzer::analyze(Block const& _block)
bool AsmAnalyzer::operator()(Label const& _label)
{
solAssert(!_label.name.empty(), "");
checkLooseFeature(
_label.location,
"The use of labels is deprecated. Please use \"if\", \"switch\", \"for\" or function calls instead."
@ -107,6 +108,7 @@ bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
{
solAssert(!_identifier.name.empty(), "");
size_t numErrorsBefore = m_errorReporter.errors().size();
bool success = true;
if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
@ -208,6 +210,7 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
{
solAssert(_assignment.value, "");
int const expectedItems = _assignment.variableNames.size();
solAssert(expectedItems >= 1, "");
int const stackHeight = m_stackHeight;
@ -259,6 +262,7 @@ bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
{
solAssert(!_funDef.name.empty(), "");
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
@ -280,6 +284,7 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
{
solAssert(!_funCall.functionName.name.empty(), "");
bool success = true;
size_t arguments = 0;
size_t returns = 0;
@ -349,6 +354,8 @@ bool AsmAnalyzer::operator()(If const& _if)
bool AsmAnalyzer::operator()(Switch const& _switch)
{
solAssert(_switch.expression, "");
bool success = true;
if (!expectExpression(*_switch.expression))
@ -391,6 +398,8 @@ bool AsmAnalyzer::operator()(Switch const& _switch)
bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
{
solAssert(_for.condition, "");
Scope* originalScope = m_currentScope;
bool success = true;
@ -478,6 +487,7 @@ bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation con
bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
{
solAssert(!_variable.name.empty(), "");
bool success = true;
size_t numErrorsBefore = m_errorReporter.errors().size();
size_t variableSize(-1);

View File

@ -276,7 +276,7 @@ assembly::Expression Parser::parseExpression()
int args = instructionInfo(instr.instruction).args;
if (args > 0 && currentToken() != Token::LParen)
fatalParserError(string(
"Expected token \"(\" (\"" +
"Expected '(' (instruction \"" +
instructionNames().at(instr.instruction) +
"\" expects " +
boost::lexical_cast<string>(args) +
@ -504,7 +504,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
/// check for premature closing parentheses
if (currentToken() == Token::RParen)
fatalParserError(string(
"Expected expression (\"" +
"Expected expression (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
boost::lexical_cast<string>(args) +
@ -516,7 +516,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
{
if (currentToken() != Token::Comma)
fatalParserError(string(
"Expected comma (\"" +
"Expected ',' (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
boost::lexical_cast<string>(args) +
@ -529,7 +529,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
ret.location.end = endPosition();
if (currentToken() == Token::Comma)
fatalParserError(string(
"Expected ')' (\"" +
"Expected ')' (instruction \"" +
instructionNames().at(instr) +
"\" expects " +
boost::lexical_cast<string>(args) +

View File

@ -29,6 +29,8 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/analysis/GlobalContext.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/analysis/TypeChecker.h>
@ -222,6 +224,22 @@ bool CompilerStack::analyze()
noErrors = false;
}
if (noErrors)
{
CFG cfg(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!cfg.constructFlow(*source->ast))
noErrors = false;
if (noErrors)
{
ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!controlFlowAnalyzer.analyze(*source->ast))
noErrors = false;
}
}
if (noErrors)
{
StaticAnalyzer staticAnalyzer(m_errorReporter);

View File

@ -136,13 +136,22 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation(
ExpressionClasses& classes = state->expressionClasses();
using Id = ExpressionClasses::Id;
using Ids = vector<Id>;
// div(calldataload(0), 1 << 224) equals to hashValue
Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(_signature)))));
Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))});
classes.forceEqual(hashValue, Instruction::DIV, Ids{
calldata,
classes.find(u256(1) << 224)
});
if (!m_evmVersion.hasBitwiseShifting())
// div(calldataload(0), 1 << 224) equals to hashValue
classes.forceEqual(
hashValue,
Instruction::DIV,
Ids{calldata, classes.find(u256(1) << 224)}
);
else
// shr(0xe0, calldataload(0)) equals to hashValue
classes.forceEqual(
hashValue,
Instruction::SHR,
Ids{classes.find(u256(0xe0)), calldata}
);
// lt(calldatasize(), 4) equals to 0 (ignore the shortcut for fallback functions)
classes.forceEqual(
classes.find(u256(0)),

View File

@ -72,7 +72,7 @@ bool DocStringParser::parse(string const& _docString, ErrorReporter& _errorRepor
auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end)
{
appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found");
appendError("End of tag " + string(tagPos, tagNameEndPos) + " not found");
break;
}

View File

@ -54,6 +54,7 @@ public:
template <class NodeType, typename... Args>
ASTPointer<NodeType> createNode(Args&& ... _args)
{
solAssert(m_location.sourceName, "");
if (m_location.end < 0)
markEndPosition();
return make_shared<NodeType>(m_location, forward<Args>(_args)...);
@ -528,7 +529,7 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
break;
expectToken(Token::Comma);
if (m_scanner->currentToken() != Token::Identifier)
fatalParserError(string("Expected Identifier after ','"));
fatalParserError(string("Expected identifier after ','"));
}
if (members.size() == 0)
parserError({"enum with no members is not allowed."});
@ -1057,16 +1058,16 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const
if (m_scanner->currentToken() != Token::Identifier)
fatalParserError("Expected event name or path.");
vector<ASTPointer<PrimaryExpression>> path;
IndexAccessedPath iap;
while (true)
{
path.push_back(parseIdentifier());
iap.path.push_back(parseIdentifier());
if (m_scanner->currentToken() != Token::Period)
break;
m_scanner->next();
};
auto eventName = expressionFromIndexAccessStructure(path, {});
auto eventName = expressionFromIndexAccessStructure(iap);
expectToken(Token::LParen);
vector<ASTPointer<Expression>> arguments;
@ -1083,61 +1084,124 @@ ASTPointer<EmitStatement> Parser::parseEmitStatement(ASTPointer<ASTString> const
ASTPointer<Statement> Parser::parseSimpleStatement(ASTPointer<ASTString> const& _docString)
{
RecursionGuard recursionGuard(*this);
LookAheadInfo statementType;
IndexAccessedPath iap;
if (m_scanner->currentToken() == Token::LParen)
{
ASTNodeFactory nodeFactory(*this);
size_t emptyComponents = 0;
// First consume all empty components.
expectToken(Token::LParen);
while (m_scanner->currentToken() == Token::Comma)
{
m_scanner->next();
emptyComponents++;
}
// Now see whether we have a variable declaration or an expression.
tie(statementType, iap) = tryParseIndexAccessedPath();
switch (statementType)
{
case LookAheadInfo::VariableDeclaration:
{
vector<ASTPointer<VariableDeclaration>> variables;
ASTPointer<Expression> value;
// We have already parsed something like `(,,,,a.b.c[2][3]`
VarDeclParserOptions options;
options.allowLocationSpecifier = true;
variables = vector<ASTPointer<VariableDeclaration>>(emptyComponents, nullptr);
variables.push_back(parseVariableDeclaration(options, typeNameFromIndexAccessStructure(iap)));
while (m_scanner->currentToken() != Token::RParen)
{
expectToken(Token::Comma);
if (m_scanner->currentToken() == Token::Comma || m_scanner->currentToken() == Token::RParen)
variables.push_back(nullptr);
else
variables.push_back(parseVariableDeclaration(options));
}
expectToken(Token::RParen);
expectToken(Token::Assign);
value = parseExpression();
nodeFactory.setEndPositionFromNode(value);
return nodeFactory.createNode<VariableDeclarationStatement>(_docString, variables, value);
}
case LookAheadInfo::Expression:
{
// Complete parsing the expression in the current component.
vector<ASTPointer<Expression>> components(emptyComponents, nullptr);
components.push_back(parseExpression(expressionFromIndexAccessStructure(iap)));
while (m_scanner->currentToken() != Token::RParen)
{
expectToken(Token::Comma);
if (m_scanner->currentToken() == Token::Comma || m_scanner->currentToken() == Token::RParen)
components.push_back(ASTPointer<Expression>());
else
components.push_back(parseExpression());
}
nodeFactory.markEndPosition();
expectToken(Token::RParen);
return parseExpressionStatement(_docString, nodeFactory.createNode<TupleExpression>(components, false));
}
default:
solAssert(false, "");
}
}
else
{
tie(statementType, iap) = tryParseIndexAccessedPath();
switch (statementType)
{
case LookAheadInfo::VariableDeclaration:
return parseVariableDeclarationStatement(_docString, typeNameFromIndexAccessStructure(iap));
case LookAheadInfo::Expression:
return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(iap));
default:
solAssert(false, "");
}
}
}
bool Parser::IndexAccessedPath::empty() const
{
if (!indices.empty())
{
solAssert(!path.empty(), "");
}
return path.empty() && indices.empty();
}
pair<Parser::LookAheadInfo, Parser::IndexAccessedPath> Parser::tryParseIndexAccessedPath()
{
// These two cases are very hard to distinguish:
// x[7 * 20 + 3] a; - x[7 * 20 + 3] = 9;
// x[7 * 20 + 3] a; and x[7 * 20 + 3] = 9;
// In the first case, x is a type name, in the second it is the name of a variable.
// As an extension, we can even have:
// `x.y.z[1][2] a;` and `x.y.z[1][2] = 10;`
// Where in the first, x.y.z leads to a type name where in the second, it accesses structs.
switch (peekStatementType())
auto statementType = peekStatementType();
switch (statementType)
{
case LookAheadInfo::VariableDeclarationStatement:
return parseVariableDeclarationStatement(_docString);
case LookAheadInfo::ExpressionStatement:
return parseExpressionStatement(_docString);
case LookAheadInfo::VariableDeclaration:
case LookAheadInfo::Expression:
return make_pair(statementType, IndexAccessedPath());
default:
break;
}
// At this point, we have 'Identifier "["' or 'Identifier "." Identifier' or 'ElementoryTypeName "["'.
// We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )+'
// We parse '(Identifier ("." Identifier)* |ElementaryTypeName) ( "[" Expression "]" )*'
// until we can decide whether to hand this over to ExpressionStatement or create a
// VariableDeclarationStatement out of it.
vector<ASTPointer<PrimaryExpression>> path;
bool startedWithElementary = false;
if (m_scanner->currentToken() == Token::Identifier)
path.push_back(parseIdentifier());
else
{
startedWithElementary = true;
unsigned firstNum;
unsigned secondNum;
tie(firstNum, secondNum) = m_scanner->currentTokenInfo();
ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum);
path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken));
m_scanner->next();
}
while (!startedWithElementary && m_scanner->currentToken() == Token::Period)
{
m_scanner->next();
path.push_back(parseIdentifier());
}
vector<pair<ASTPointer<Expression>, SourceLocation>> indices;
while (m_scanner->currentToken() == Token::LBrack)
{
expectToken(Token::LBrack);
ASTPointer<Expression> index;
if (m_scanner->currentToken() != Token::RBrack)
index = parseExpression();
SourceLocation indexLocation = path.front()->location();
indexLocation.end = endPosition();
indices.push_back(make_pair(index, indexLocation));
expectToken(Token::RBrack);
}
IndexAccessedPath iap = parseIndexAccessedPath();
if (m_scanner->currentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->currentToken()))
return parseVariableDeclarationStatement(_docString, typeNameIndexAccessStructure(path, indices));
return make_pair(LookAheadInfo::VariableDeclaration, move(iap));
else
return parseExpressionStatement(_docString, expressionFromIndexAccessStructure(path, indices));
return make_pair(LookAheadInfo::Expression, move(iap));
}
ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStatement(
@ -1145,6 +1209,9 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTPointer<TypeName> const& _lookAheadArrayType
)
{
// This does not parse multi variable declaration statements starting directly with
// `(`, they are parsed in parseSimpleStatement, because they are hard to distinguish
// from tuple expressions.
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
if (_lookAheadArrayType)
@ -1207,23 +1274,24 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
ASTPointer<ExpressionStatement> Parser::parseExpressionStatement(
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
ASTPointer<Expression> const& _partialParserResult
)
{
RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseExpression(_lookAheadIndexAccessStructure);
ASTPointer<Expression> expression = parseExpression(_partialParserResult);
return ASTNodeFactory(*this, expression).createNode<ExpressionStatement>(_docString, expression);
}
ASTPointer<Expression> Parser::parseExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseBinaryExpression(4, _lookAheadIndexAccessStructure);
ASTPointer<Expression> expression = parseBinaryExpression(4, _partiallyParsedExpression);
if (Token::isAssignmentOp(m_scanner->currentToken()))
{
Token::Value assignmentOperator = expectAssignmentOperator();
Token::Value assignmentOperator = m_scanner->currentToken();
m_scanner->next();
ASTPointer<Expression> rightHandSide = parseExpression();
ASTNodeFactory nodeFactory(*this, expression);
nodeFactory.setEndPositionFromNode(rightHandSide);
@ -1245,11 +1313,11 @@ ASTPointer<Expression> Parser::parseExpression(
ASTPointer<Expression> Parser::parseBinaryExpression(
int _minPrecedence,
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
ASTPointer<Expression> expression = parseUnaryExpression(_lookAheadIndexAccessStructure);
ASTPointer<Expression> expression = parseUnaryExpression(_partiallyParsedExpression);
ASTNodeFactory nodeFactory(*this, expression);
int precedence = Token::precedence(m_scanner->currentToken());
for (; precedence >= _minPrecedence; --precedence)
@ -1265,14 +1333,14 @@ ASTPointer<Expression> Parser::parseBinaryExpression(
}
ASTPointer<Expression> Parser::parseUnaryExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
ASTNodeFactory nodeFactory = _partiallyParsedExpression ?
ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this);
Token::Value token = m_scanner->currentToken();
if (!_lookAheadIndexAccessStructure && (Token::isUnaryOp(token) || Token::isCountOp(token)))
if (!_partiallyParsedExpression && (Token::isUnaryOp(token) || Token::isCountOp(token)))
{
// prefix expression
m_scanner->next();
@ -1283,7 +1351,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
else
{
// potential postfix expression
ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_lookAheadIndexAccessStructure);
ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression);
token = m_scanner->currentToken();
if (!Token::isCountOp(token))
return subExpression;
@ -1294,16 +1362,16 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
}
ASTPointer<Expression> Parser::parseLeftHandSideExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure
ASTPointer<Expression> const& _partiallyParsedExpression
)
{
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory = _lookAheadIndexAccessStructure ?
ASTNodeFactory(*this, _lookAheadIndexAccessStructure) : ASTNodeFactory(*this);
ASTNodeFactory nodeFactory = _partiallyParsedExpression ?
ASTNodeFactory(*this, _partiallyParsedExpression) : ASTNodeFactory(*this);
ASTPointer<Expression> expression;
if (_lookAheadIndexAccessStructure)
expression = _lookAheadIndexAccessStructure;
if (_partiallyParsedExpression)
expression = _partiallyParsedExpression;
else if (m_scanner->currentToken() == Token::New)
{
expectToken(Token::New);
@ -1517,44 +1585,79 @@ Parser::LookAheadInfo Parser::peekStatementType() const
bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
if (token == Token::Mapping || token == Token::Function || token == Token::Var)
return LookAheadInfo::VariableDeclarationStatement;
return LookAheadInfo::VariableDeclaration;
if (mightBeTypeName)
{
Token::Value next = m_scanner->peekNextToken();
if (next == Token::Identifier || Token::isLocationSpecifier(next))
return LookAheadInfo::VariableDeclarationStatement;
return LookAheadInfo::VariableDeclaration;
if (next == Token::LBrack || next == Token::Period)
return LookAheadInfo::IndexAccessStructure;
}
return LookAheadInfo::ExpressionStatement;
return LookAheadInfo::Expression;
}
ASTPointer<TypeName> Parser::typeNameIndexAccessStructure(
vector<ASTPointer<PrimaryExpression>> const& _path,
vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices
)
Parser::IndexAccessedPath Parser::parseIndexAccessedPath()
{
solAssert(!_path.empty(), "");
IndexAccessedPath iap;
if (m_scanner->currentToken() == Token::Identifier)
{
iap.path.push_back(parseIdentifier());
while (m_scanner->currentToken() == Token::Period)
{
m_scanner->next();
iap.path.push_back(parseIdentifier());
}
}
else
{
unsigned firstNum;
unsigned secondNum;
tie(firstNum, secondNum) = m_scanner->currentTokenInfo();
ElementaryTypeNameToken elemToken(m_scanner->currentToken(), firstNum, secondNum);
iap.path.push_back(ASTNodeFactory(*this).createNode<ElementaryTypeNameExpression>(elemToken));
m_scanner->next();
}
while (m_scanner->currentToken() == Token::LBrack)
{
expectToken(Token::LBrack);
ASTPointer<Expression> index;
if (m_scanner->currentToken() != Token::RBrack)
index = parseExpression();
SourceLocation indexLocation = iap.path.front()->location();
indexLocation.end = endPosition();
iap.indices.push_back(make_pair(index, indexLocation));
expectToken(Token::RBrack);
}
return iap;
}
ASTPointer<TypeName> Parser::typeNameFromIndexAccessStructure(Parser::IndexAccessedPath const& _iap)
{
if (_iap.empty())
return {};
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this);
SourceLocation location = _path.front()->location();
location.end = _path.back()->location().end;
SourceLocation location = _iap.path.front()->location();
location.end = _iap.path.back()->location().end;
nodeFactory.setLocation(location);
ASTPointer<TypeName> type;
if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_path.front().get()))
if (auto typeName = dynamic_cast<ElementaryTypeNameExpression const*>(_iap.path.front().get()))
{
solAssert(_path.size() == 1, "");
solAssert(_iap.path.size() == 1, "");
type = nodeFactory.createNode<ElementaryTypeName>(typeName->typeName());
}
else
{
vector<ASTString> path;
for (auto const& el: _path)
for (auto const& el: _iap.path)
path.push_back(dynamic_cast<Identifier const&>(*el).name());
type = nodeFactory.createNode<UserDefinedTypeName>(path);
}
for (auto const& lengthExpression: _indices)
for (auto const& lengthExpression: _iap.indices)
{
nodeFactory.setLocation(lengthExpression.second);
type = nodeFactory.createNode<ArrayTypeName>(type, lengthExpression.first);
@ -1563,26 +1666,27 @@ ASTPointer<TypeName> Parser::typeNameIndexAccessStructure(
}
ASTPointer<Expression> Parser::expressionFromIndexAccessStructure(
vector<ASTPointer<PrimaryExpression>> const& _path,
vector<pair<ASTPointer<Expression>, SourceLocation>> const& _indices
Parser::IndexAccessedPath const& _iap
)
{
solAssert(!_path.empty(), "");
if (_iap.empty())
return {};
RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this, _path.front());
ASTPointer<Expression> expression(_path.front());
for (size_t i = 1; i < _path.size(); ++i)
ASTNodeFactory nodeFactory(*this, _iap.path.front());
ASTPointer<Expression> expression(_iap.path.front());
for (size_t i = 1; i < _iap.path.size(); ++i)
{
SourceLocation location(_path.front()->location());
location.end = _path[i]->location().end;
SourceLocation location(_iap.path.front()->location());
location.end = _iap.path[i]->location().end;
nodeFactory.setLocation(location);
Identifier const& identifier = dynamic_cast<Identifier const&>(*_path[i]);
Identifier const& identifier = dynamic_cast<Identifier const&>(*_iap.path[i]);
expression = nodeFactory.createNode<MemberAccess>(
expression,
make_shared<ASTString>(identifier.name())
);
}
for (auto const& index: _indices)
for (auto const& index: _iap.indices)
{
nodeFactory.setLocation(index.second);
expression = nodeFactory.createNode<IndexAccess>(expression, index.first);
@ -1598,40 +1702,10 @@ ASTPointer<ParameterList> Parser::createEmptyParameterList()
return nodeFactory.createNode<ParameterList>(vector<ASTPointer<VariableDeclaration>>());
}
string Parser::currentTokenName()
{
Token::Value token = m_scanner->currentToken();
if (Token::isElementaryTypeName(token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return elemTypeName.toString();
}
else
return Token::name(token);
}
Token::Value Parser::expectAssignmentOperator()
{
Token::Value op = m_scanner->currentToken();
if (!Token::isAssignmentOp(op))
fatalParserError(
string("Expected assignment operator, got '") +
currentTokenName() +
string("'")
);
m_scanner->next();
return op;
}
ASTPointer<ASTString> Parser::expectIdentifierToken()
{
Token::Value id = m_scanner->currentToken();
if (id != Token::Identifier)
fatalParserError(
string("Expected identifier, got '") +
currentTokenName() +
string("'")
);
// do not advance on success
expectToken(Token::Identifier, false);
return getLiteralAndAdvance();
}

View File

@ -118,19 +118,19 @@ private:
);
ASTPointer<ExpressionStatement> parseExpressionStatement(
ASTPointer<ASTString> const& _docString,
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseBinaryExpression(int _minPrecedence = 4,
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseUnaryExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parseLeftHandSideExpression(
ASTPointer<Expression> const& _lookAheadIndexAccessStructure = ASTPointer<Expression>()
ASTPointer<Expression> const& _partiallyParsedExpression = ASTPointer<Expression>()
);
ASTPointer<Expression> parsePrimaryExpression();
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments();
@ -143,26 +143,32 @@ private:
/// Used as return value of @see peekStatementType.
enum class LookAheadInfo
{
IndexAccessStructure, VariableDeclarationStatement, ExpressionStatement
IndexAccessStructure, VariableDeclaration, Expression
};
/// Structure that represents a.b.c[x][y][z]. Can be converted either to an expression
/// or to a type name. For this to be valid, path cannot be empty, but indices can be empty.
struct IndexAccessedPath
{
std::vector<ASTPointer<PrimaryExpression>> path;
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> indices;
bool empty() const;
};
std::pair<LookAheadInfo, IndexAccessedPath> tryParseIndexAccessedPath();
/// Performs limited look-ahead to distinguish between variable declaration and expression statement.
/// For source code of the form "a[][8]" ("IndexAccessStructure"), this is not possible to
/// decide with constant look-ahead.
LookAheadInfo peekStatementType() const;
/// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]".
ASTPointer<TypeName> typeNameIndexAccessStructure(
std::vector<ASTPointer<PrimaryExpression>> const& _path,
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices
);
/// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]".
ASTPointer<Expression> expressionFromIndexAccessStructure(
std::vector<ASTPointer<PrimaryExpression>> const& _path,
std::vector<std::pair<ASTPointer<Expression>, SourceLocation>> const& _indices
);
/// @returns an IndexAccessedPath as a prestage to parsing a variable declaration (type name)
/// or an expression;
IndexAccessedPath parseIndexAccessedPath();
/// @returns a typename parsed in look-ahead fashion from something like "a.b[8][2**70]",
/// or an empty pointer if an empty @a _pathAndIncides has been supplied.
ASTPointer<TypeName> typeNameFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices);
/// @returns an expression parsed in look-ahead fashion from something like "a.b[8][2**70]",
/// or an empty pointer if an empty @a _pathAndIncides has been supplied.
ASTPointer<Expression> expressionFromIndexAccessStructure(IndexAccessedPath const& _pathAndIndices);
std::string currentTokenName();
Token::Value expectAssignmentOperator();
ASTPointer<ASTString> expectIdentifierToken();
ASTPointer<ASTString> getLiteralAndAdvance();
///@}

View File

@ -63,42 +63,32 @@ Token::Value ParserBase::advance()
return m_scanner->next();
}
void ParserBase::expectToken(Token::Value _value)
void ParserBase::expectToken(Token::Value _value, bool _advance)
{
Token::Value tok = m_scanner->currentToken();
if (tok != _value)
{
if (Token::isReservedKeyword(tok))
auto tokenName = [this](Token::Value _token)
{
fatalParserError(
string("Expected token ") +
string(Token::name(_value)) +
string(" got reserved keyword '") +
string(Token::name(tok)) +
string("'")
);
}
else if (Token::isElementaryTypeName(tok)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
fatalParserError(
string("Expected token ") +
string(Token::name(_value)) +
string(" got '") +
elemTypeName.toString() +
string("'")
);
}
else
fatalParserError(
string("Expected token ") +
string(Token::name(_value)) +
string(" got '") +
string(Token::name(m_scanner->currentToken())) +
string("'")
);
if (_token == Token::Identifier)
return string("identifier");
else if (_token == Token::EOS)
return string("end of source");
else if (Token::isReservedKeyword(_token))
return string("reserved keyword '") + Token::friendlyName(_token) + "'";
else if (Token::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return string("'") + elemTypeName.toString() + "'";
}
else
return string("'") + Token::friendlyName(_token) + "'";
};
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
}
m_scanner->next();
if (_advance)
m_scanner->next();
}
void ParserBase::increaseRecursionDepth()
@ -116,10 +106,10 @@ void ParserBase::decreaseRecursionDepth()
void ParserBase::parserError(string const& _description)
{
m_errorReporter.parserError(SourceLocation(position(), position(), sourceName()), _description);
m_errorReporter.parserError(SourceLocation(position(), endPosition(), sourceName()), _description);
}
void ParserBase::fatalParserError(string const& _description)
{
m_errorReporter.fatalParserError(SourceLocation(position(), position(), sourceName()), _description);
m_errorReporter.fatalParserError(SourceLocation(position(), endPosition(), sourceName()), _description);
}

View File

@ -63,7 +63,7 @@ protected:
///@{
///@name Helper functions
/// If current token value is not _value, throw exception otherwise advance token.
void expectToken(Token::Value _value);
void expectToken(Token::Value _value, bool _advance = true);
Token::Value currentToken() const;
Token::Value peekNextToken() const;
std::string currentLiteral() const;

View File

@ -304,6 +304,17 @@ public:
return m_string[tok];
}
static std::string friendlyName(Value tok)
{
char const* ret = toString(tok);
if (ret == nullptr)
{
ret = name(tok);
solAssert(ret != nullptr, "");
}
return std::string(ret);
}
// @returns the precedence > 0 for binary and compare
// operators; returns 0 otherwise.
static int precedence(Value tok)

Some files were not shown because too many files have changed in this diff Show More