Merge pull request #3892 from ethereum/develop

Merge develop into release for 0.4.22
This commit is contained in:
chriseth 2018-04-16 23:03:49 +02:00 committed by GitHub
commit 4cb486ee99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
328 changed files with 8690 additions and 2248 deletions

View File

@ -84,12 +84,12 @@ matrix:
sudo: required sudo: required
compiler: gcc compiler: gcc
node_js: node_js:
- "7" - "8"
services: services:
- docker - docker
before_install: before_install:
- nvm install 7 - nvm install 8
- nvm use 7 - nvm use 8
- docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit - docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit
env: env:
- SOLC_EMSCRIPTEN=On - SOLC_EMSCRIPTEN=On
@ -159,6 +159,8 @@ cache:
install: install:
- test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh) - test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.sh)
- test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh) - test "$TRAVIS_OS_NAME" != "linux" || (scripts/install_cmake.sh)
# - if [ "$TRAVIS_BRANCH" != release -a -z "$TRAVIS_TAG" ]; then SOLC_TESTS=Off; fi
- SOLC_TESTS=Off
- if [ "$TRAVIS_BRANCH" = release -o -n "$TRAVIS_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi - if [ "$TRAVIS_BRANCH" = release -o -n "$TRAVIS_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
- echo -n "$TRAVIS_COMMIT" > commit_hash.txt - echo -n "$TRAVIS_COMMIT" > commit_hash.txt

View File

@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy() eth_policy()
# project name and version should be set after cmake_policy CMP0048 # project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.4.21") set(PROJECT_VERSION "0.4.22")
project(solidity VERSION ${PROJECT_VERSION}) project(solidity VERSION ${PROJECT_VERSION})
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)

262
CODING_STYLE.md Normal file
View File

@ -0,0 +1,262 @@
0. Formatting
GOLDEN RULE: Follow the style of the existing code when you make changes.
a. Use tabs for leading indentation
- tab stops are every 4 characters (only relevant for line length).
- One indentation level -> exactly one byte (i.e. a tab character) in the source file.
b. Line widths:
- Lines should be at most 99 characters wide to make diff views readable and reduce merge conflicts.
- Lines of comments should be formatted according to ease of viewing, but simplicity is to be preferred over beauty.
c. Single-statement blocks should not have braces, unless required for clarity.
d. Never place condition bodies on same line as condition.
e. Space between keyword and opening parenthesis, but not following opening parenthesis or before final parenthesis.
f. No spaces for unary operators, `->` or `.`.
g. No space before ':' but one after it, except in the ternary operator: one on both sides.
h. Add spaces around all other operators.
i. Braces, when used, always have their own lines and are at same indentation level as "parent" scope.
j. If lines are broken, a list of elements enclosed with parentheses (of any kind) and separated by a
separator (of any kind) are formatted such that there is exactly one element per line, followed by
the separator, the opening parenthesis is on the first line, followed by a line break and the closing
parenthesis is on a line of its own (unindented). See example below.
(WRONG)
if( a==b[ i ] ) { printf ("Hello\n"); }
foo->bar(someLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName);
cout << "some very long string that contains completely irrelevant text that talks about this and that and contains the words \"lorem\" and \"ipsum\"" << endl;
(RIGHT)
if (a == b[i])
printf("Hello\n"); // NOTE spaces used instead of tab here for clarity - first byte should be '\t'.
foo->bar(
someLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName
);
cout <<
"some very long string that contains completely irrelevant " <<
"text that talks about this and that and contains the words " <<
"\"lorem\" and \"ipsum\"" <<
endl;
1. Namespaces;
a. No "using namespace" declarations in header files.
b. All symbols should be declared in a namespace except for final applications.
c. Use anonymous namespaces for helpers whose scope is a cpp file only.
d. Preprocessor symbols should be prefixed with the namespace in all-caps and an underscore.
(WRONG)
#include <cassert>
using namespace std;
tuple<float, float> meanAndSigma(vector<float> const& _v);
(CORRECT)
#include <cassert>
std::tuple<float, float> meanAndSigma(std::vector<float> const& _v);
2. Preprocessor;
a. File comment is always at top, and includes:
- Copyright.
- License (e.g. see COPYING).
b. Never use #ifdef/#define/#endif file guards. Prefer #pragma once as first line below file comment.
c. Prefer static const variable to value macros.
d. Prefer inline constexpr functions to function macros.
e. Split complex macro on multiple lines with '\'.
3. Capitalization;
GOLDEN RULE: Preprocessor: ALL_CAPS; C++: camelCase.
a. Use camelCase for splitting words in names, except where obviously extending STL/boost functionality in which case follow those naming conventions.
b. The following entities' first alpha is upper case:
- Type names.
- Template parameters.
- Enum members.
- static const variables that form an external API.
c. All preprocessor symbols (macros, macro arguments) in full uppercase with underscore word separation.
All other entities' first alpha is lower case.
4. Variable prefixes:
a. Leading underscore "_" to parameter names.
- Exception: "o_parameterName" when it is used exclusively for output. See 6(f).
- Exception: "io_parameterName" when it is used for both input and output. See 6(f).
b. Leading "g_" to global (non-const) variables.
c. Leading "s_" to static (non-const, non-global) variables.
5. Assertions:
- use `solAssert` and `solUnimplementedAssert` generously to check assumptions
that span across different parts of the code base, for example before dereferencing
a pointer.
6. Declarations:
a. {Typename} + {qualifiers} + {name}.
b. Only one per line.
c. Associate */& with type, not variable (at ends with parser, but more readable, and safe if in conjunction with (b)).
d. Favour declarations close to use; don't habitually declare at top of scope ala C.
e. Pass non-trivial parameters as const reference, unless the data is to be copied into the function, then either pass by const reference or by value and use std::move.
f. If a function returns multiple values, use std::tuple (std::pair acceptable) or better introduce a struct type. Do not use */& arguments.
g. Use parameters of pointer type only if ``nullptr`` is a valid argument, use references otherwise. Often, ``boost::optional`` is better suited than a raw pointer.
h. Never use a macro where adequate non-preprocessor C++ can be written.
i. Only use ``auto`` if the type is very long and rather irrelevant.
j. Do not pass bools: prefer enumerations instead.
k. Prefer enum class to straight enum.
l. Always initialize POD variables, even if their value is overwritten later.
(WRONG)
const double d = 0;
int i, j;
char *s;
float meanAndSigma(std::vector<float> _v, float* _sigma, bool _approximate);
Derived* x(dynamic_cast<Derived*>(base));
for (map<ComplexTypeOne, ComplexTypeTwo>::iterator i = l.begin(); i != l.end(); ++l) {}
(CORRECT)
enum class Accuracy
{
Approximate,
Exact
};
struct MeanSigma
{
float mean;
float standardDeviation;
};
double const d = 0;
int i;
int j;
char* s;
MeanAndSigma ms meanAndSigma(std::vector<float> const& _v, Accuracy _a);
Derived* x = dynamic_cast<Derived*>(base);
for (auto i = x->begin(); i != x->end(); ++i) {}
7. Structs & classes
a. Structs to be used when all members public and no virtual functions.
- In this case, members should be named naturally and not prefixed with 'm_'
b. Classes to be used in all other circumstances.
8. Members:
a. One member per line only.
b. Private, non-static, non-const fields prefixed with m_.
c. Avoid public fields, except in structs.
d. Use override, final and const as much as possible.
e. No implementations with the class declaration, except:
- template or force-inline method (though prefer implementation at bottom of header file).
- one-line implementation (in which case include it in same line as declaration).
f. For a property 'foo'
- Member: m_foo;
- Getter: foo() [ also: for booleans, isFoo() ];
- Setter: setFoo();
9. Naming
a. Avoid unpronouncable names
b. Names should be shortened only if they are extremely common, but shortening should be generally avoided
c. Avoid prefixes of initials (e.g. do not use IMyInterface, CMyImplementation)
c. Find short, memorable & (at least semi-) descriptive names for commonly used classes or name-fragments.
- A dictionary and thesaurus are your friends.
- Spell correctly.
- Think carefully about the class's purpose.
- Imagine it as an isolated component to try to decontextualise it when considering its name.
- Don't be trapped into naming it (purely) in terms of its implementation.
10. Type-definitions
a. Prefer 'using' to 'typedef'. e.g. using ints = std::vector<int>; rather than typedef std::vector<int> ints;
b. Generally avoid shortening a standard form that already includes all important information:
- e.g. stick to shared_ptr<X> rather than shortening to ptr<X>.
c. Where there are exceptions to this (due to excessive use and clear meaning), note the change prominently and use it consistently.
- e.g. using Guard = std::lock_guard<std::mutex>; ///< Guard is used throughout the codebase since it is clear in meaning and used commonly.
d. In general expressions should be roughly as important/semantically meaningful as the space they occupy.
e. Avoid introducing aliases for types unless they are very complicated. Consider the number of items a brain can keep track of at the same time.
11. Commenting
a. Comments should be doxygen-compilable, using @notation rather than \notation.
b. Document the interface, not the implementation.
- Documentation should be able to remain completely unchanged, even if the method is reimplemented.
- Comment in terms of the method properties and intended alteration to class state (or what aspects of the state it reports).
- Be careful to scrutinise documentation that extends only to intended purpose and usage.
- Reject documentation that is simply an English transaction of the implementation.
c. Avoid in-code comments. Instead, try to extract blocks of functionality into functions. This often already eliminates the need for an in-code comment.
12. Include Headers
Includes should go in increasing order of generality (libsolidity -> libevmasm -> libdevcore -> boost -> STL).
The corresponding .h file should be the first include in the respective .cpp file.
Insert empty lines between blocks of include files.
Example:
```
#include <libsolidity/codegen/ExpressionCompiler.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/CompilerContext.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libevmasm/GasMeter.h>
#include <libdevcore/Common.h>
#include <libdevcore/SHA3.h>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <utility>
#include <numeric>
```
See http://stackoverflow.com/questions/614302/c-header-order/614333#614333 for the reason: this makes it easier to find missing includes in header files.
13. Recommended reading
Herb Sutter and Bjarne Stroustrup
- "C++ Core Guidelines" (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md)
Herb Sutter and Andrei Alexandrescu
- "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices"
Scott Meyers
- "Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rd Edition)"
- "More Effective C++: 35 New Ways to Improve Your Programs and Designs"
- "Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14"

View File

@ -1,3 +1,56 @@
### 0.4.22 (2018-04-16)
Features:
* Code Generator: Initialize arrays without using ``msize()``.
* Code Generator: More specialized and thus optimized implementation for ``x.push(...)``
* Commandline interface: Error when missing or inaccessible file detected. Suppress it with the ``--ignore-missing`` flag.
* Constant Evaluator: Fix evaluation of single element tuples.
* General: Add encoding routines ``abi.encodePacked``, ``abi.encode``, ``abi.encodeWithSelector`` and ``abi.encodeWithSignature``.
* General: Add global function ``gasleft()`` and deprecate ``msg.gas``.
* General: Add global function ``blockhash(uint)`` and deprecate ``block.hash(uint)``.
* General: Allow providing reason string for ``revert()`` and ``require()``.
* General: Introduce new constructor syntax using the ``constructor`` keyword as experimental 0.5.0 feature.
* General: Limit the number of errors output in a single run to 256.
* General: Support accessing dynamic return data in post-byzantium EVMs.
* Inheritance: Error when using empty parentheses for base class constructors that require arguments as experimental 0.5.0 feature.
* Inheritance: Error when using no parentheses in modifier-style constructor calls as experimental 0.5.0 feature.
* Interfaces: Allow overriding external functions in interfaces with public in an implementing contract.
* Optimizer: Optimize ``SHL`` and ``SHR`` only involving constants (Constantinople only).
* Optimizer: Remove useless ``SWAP1`` instruction preceding a commutative instruction (such as ``ADD``, ``MUL``, etc).
* Optimizer: Replace comparison operators (``LT``, ``GT``, etc) with opposites if preceded by ``SWAP1``, e.g. ``SWAP1 LT`` is replaced with ``GT``.
* Optimizer: Optimize across ``mload`` if ``msize()`` is not used.
* Static Analyzer: Error on duplicated super constructor calls as experimental 0.5.0 feature.
* Syntax Checker: Issue warning for empty structs (or error as experimental 0.5.0 feature).
* Syntax Checker: Warn about modifiers on functions without implementation (this will turn into an error with version 0.5.0).
* Syntax Tests: Add source locations to syntax test expectations.
* Type Checker: Improve documentation and warnings for accessing contract members inherited from ``address``.
Bugfixes:
* Code Generator: Allow ``block.blockhash`` without being called.
* Code Generator: Do not include internal functions in the runtime bytecode which are only referenced in the constructor.
* Code Generator: Properly skip unneeded storage array cleanup when not reducing length.
* Code Generator: Bugfix in modifier lookup in libraries.
* Code Generator: Implement packed encoding of external function types.
* Code Generator: Treat empty base constructor argument list as not provided.
* Code Generator: Properly force-clean bytesXX types for shortening conversions.
* Commandline interface: Fix error messages for imported files that do not exist.
* Commandline interface: Support ``--evm-version constantinople`` properly.
* DocString Parser: Fix error message for empty descriptions.
* Gas Estimator: Correctly ignore costs of fallback function for other functions.
* JSON AST: Remove storage qualifier for type name strings.
* Parser: Fix internal compiler error when parsing ``var`` declaration without identifier.
* Parser: Fix parsing of getters for function type variables.
* Standard JSON: Support ``constantinople`` as ``evmVersion`` properly.
* Static Analyzer: Fix non-deterministic order of unused variable warnings.
* Static Analyzer: Invalid arithmetic with constant expressions causes errors.
* Type Checker: Fix detection of recursive structs.
* Type Checker: Fix asymmetry bug when comparing with literal numbers.
* Type System: Improve error message when attempting to shift by a fractional amount.
* Type System: Make external library functions accessible.
* Type System: Prevent encoding of weird types.
* Type System: Restrict rational numbers to 4096 bits.
### 0.4.21 (2018-03-07) ### 0.4.21 (2018-03-07)
Features: Features:

View File

@ -4,7 +4,7 @@
## Useful links ## Useful links
To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts. To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
You can start using [Solidity in your browser](https://ethereum.github.io/browser-solidity/) with no need to download or compile anything. You can start using [Solidity in your browser](http://remix.ethereum.org) with no need to download or compile anything.
The changelog for this project can be found [here](https://github.com/ethereum/solidity/blob/develop/Changelog.md). The changelog for this project can be found [here](https://github.com/ethereum/solidity/blob/develop/Changelog.md).

View File

@ -11,6 +11,7 @@ Checklist for making a release:
- [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key). - [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key).
- [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems). - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems).
- [ ] Update the homebrew realease in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb (version and hash) - [ ] Update the homebrew realease in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb (version and hash)
- [ ] Update the default version on readthedocs.
- [ ] Make a release of ``solc-js``: Increment the version number, create a pull request for that, merge it after tests succeeded. - [ ] Make a release of ``solc-js``: Increment the version number, create a pull request for that, merge it after tests succeeded.
- [ ] Run ``npm publish`` in the updated ``solc-js`` repository. - [ ] Run ``npm publish`` in the updated ``solc-js`` repository.
- [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry. - [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.

View File

@ -71,7 +71,7 @@ build_script:
test_script: test_script:
- cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION%
- soltest.exe --show-progress -- --no-ipc --no-smt - soltest.exe --show-progress -- --testpath %APPVEYOR_BUILD_FOLDER%\test --no-ipc --no-smt
# Skip bytecode compare if private key is not available # Skip bytecode compare if private key is not available
- cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%
- ps: if ($env:priv_key) { - ps: if ($env:priv_key) {

View File

@ -1,3 +1,10 @@
defaults:
# The default for tags is to not run, so we have to explicitly match a filter.
- build_on_tags: &build_on_tags
filters:
tags:
only: /.*/
version: 2 version: 2
jobs: jobs:
build_emscripten: build_emscripten:
@ -48,7 +55,7 @@ jobs:
export NVM_DIR="/usr/local/nvm" export NVM_DIR="/usr/local/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm --version nvm --version
nvm install 6 nvm install 8
node --version node --version
npm --version npm --version
- run: - run:
@ -72,7 +79,7 @@ jobs:
export NVM_DIR="/usr/local/nvm" export NVM_DIR="/usr/local/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm --version nvm --version
nvm install 7 nvm install 8
node --version node --version
npm --version npm --version
- run: - run:
@ -89,20 +96,12 @@ jobs:
name: Install build dependencies name: Install build dependencies
command: | command: |
apt-get -qq update apt-get -qq update
apt-get -qy install ccache cmake libboost-all-dev libz3-dev apt-get -qy install cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libz3-dev
- run: - run:
name: Store commit hash and prerelease name: Store commit hash and prerelease
command: | command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt echo -n "$CIRCLE_SHA1" > commit_hash.txt
- restore_cache:
key: ccache-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
key: ccache-{{ arch }}-{{ .Branch }}-
key: ccache-{{ arch }}-develop-
key: ccache-{{ arch }}-
- run:
name: Configure ccache
command: ccache -M 200M && ccache -c && ccache -s && ccache -z
- run: - run:
name: Build name: Build
command: | command: |
@ -110,14 +109,6 @@ jobs:
cd build cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4 make -j4
- run:
name: CCache statistics
command: ccache -s
- save_cache:
key: ccache-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
when: always
paths:
- ~/.ccache
- store_artifacts: - store_artifacts:
path: build/solc/solc path: build/solc/solc
destination: solc destination: solc
@ -126,7 +117,7 @@ jobs:
paths: paths:
- solc/solc - solc/solc
- test/soltest - test/soltest
- test/solfuzzer - test/tools/solfuzzer
test_x86: test_x86:
docker: docker:
@ -173,15 +164,18 @@ workflows:
version: 2 version: 2
build_all: build_all:
jobs: jobs:
- build_emscripten - build_emscripten: *build_on_tags
- test_emscripten_solcjs: - test_emscripten_solcjs:
<<: *build_on_tags
requires: requires:
- build_emscripten - build_emscripten
- test_emscripten_external: - test_emscripten_external:
<<: *build_on_tags
requires: requires:
- build_emscripten - build_emscripten
- build_x86 - build_x86: *build_on_tags
- test_x86: - test_x86:
<<: *build_on_tags
requires: requires:
- build_x86 - build_x86
- docs - docs: *build_on_tags

View File

@ -34,13 +34,6 @@ endif()
set(ETH_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR}) set(ETH_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR})
set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts) set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts)
find_program(CTEST_COMMAND ctest)
#message(STATUS "CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}")
#message(STATUS "CMake Helper Path: ${ETH_CMAKE_DIR}")
#message(STATUS "CMake Script Path: ${ETH_SCRIPTS_DIR}")
#message(STATUS "ctest path: ${CTEST_COMMAND}")
## use multithreaded boost libraries, with -mt suffix ## use multithreaded boost libraries, with -mt suffix
set(Boost_USE_MULTITHREADED ON) set(Boost_USE_MULTITHREADED ON)
option(Boost_USE_STATIC_LIBS "Link Boost statically" ON) option(Boost_USE_STATIC_LIBS "Link Boost statically" ON)

View File

@ -54,11 +54,11 @@ The following elementary types exist:
- ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``. - ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``.
- ``fixed``, ``ufixed``: synonyms for ``fixed128x19``, ``ufixed128x19`` respectively. For computing the function selector, ``fixed128x19`` and ``ufixed128x19`` have to be used. - ``fixed``, ``ufixed``: synonyms for ``fixed128x18``, ``ufixed128x18`` respectively. For computing the function selector, ``fixed128x18`` and ``ufixed128x18`` have to be used.
- ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``. - ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``.
- ``function``: an address (20 bytes) folled by a function selector (4 bytes). Encoded identical to ``bytes24``. - ``function``: an address (20 bytes) followed by a function selector (4 bytes). Encoded identical to ``bytes24``.
The following (fixed-size) array type exists: The following (fixed-size) array type exists:
@ -164,9 +164,9 @@ on the type of ``X`` being
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes. - ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-order (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is 32 bytes.
- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false`` - ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false``
- ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``. - ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``.
- ``fixed``: as in the ``fixed128x19`` case - ``fixed``: as in the ``fixed128x18`` case
- ``ufixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``. - ``ufixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``.
- ``ufixed``: as in the ``ufixed128x19`` case - ``ufixed``: as in the ``ufixed128x18`` case
- ``bytes<M>``: ``enc(X)`` is the sequence of bytes in ``X`` padded with trailing zero-bytes to a length of 32 bytes. - ``bytes<M>``: ``enc(X)`` is the sequence of bytes in ``X`` padded with trailing zero-bytes to a length of 32 bytes.
Note that for any ``X``, ``len(enc(X))`` is a multiple of 32. Note that for any ``X``, ``len(enc(X))`` is a multiple of 32.

View File

@ -405,6 +405,16 @@ changes during the call, and thus references to local variables will be wrong.
} }
} }
.. note::
If you access variables of a type that spans less than 256 bits
(for example ``uint64``, ``address``, ``bytes16`` or ``byte``),
you cannot make any assumptions about bits not part of the
encoding of the type. Especially, do not assume them to be zero.
To be safe, always clear the data properly before you use it
in a context where this is important:
``uint32 x = f(); assembly { x := and(x, 0xffffffff) /* now use x */ }``
To clean signed types, you can use the ``signextend`` opcode.
Labels Labels
------ ------
@ -647,6 +657,11 @@ Solidity manages memory in a very simple way: There is a "free memory pointer"
at position ``0x40`` in memory. If you want to allocate memory, just use the memory at position ``0x40`` in memory. If you want to allocate memory, just use the memory
from that point on and update the pointer accordingly. from that point on and update the pointer accordingly.
The first 64 bytes of memory can be used as "scratch space" for short-term
allocation. The 32 bytes after the free memory pointer (i.e. starting at ``0x60``)
is meant to be zero permanently and is used as the initial value for
empty dynamic memory arrays.
Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is Elements in memory arrays in Solidity always occupy multiples of 32 bytes (yes, this is
even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory even true for ``byte[]``, but not for ``bytes`` and ``string``). Multi-dimensional memory
arrays are pointers to memory arrays. The length of a dynamic array is stored at the arrays are pointers to memory arrays. The length of a dynamic array is stored at the

View File

@ -422,6 +422,10 @@
"bugs": [], "bugs": [],
"released": "2018-03-07" "released": "2018-03-07"
}, },
"0.4.22": {
"bugs": [],
"released": "2018-04-16"
},
"0.4.3": { "0.4.3": {
"bugs": [ "bugs": [
"ZeroFunctionSelector", "ZeroFunctionSelector",

View File

@ -130,7 +130,7 @@ restrictions highly readable.
:: ::
pragma solidity ^0.4.11; pragma solidity ^0.4.22;
contract AccessRestriction { contract AccessRestriction {
// These will be assigned at the construction // These will be assigned at the construction
@ -147,7 +147,10 @@ restrictions highly readable.
// a certain address. // a certain address.
modifier onlyBy(address _account) modifier onlyBy(address _account)
{ {
require(msg.sender == _account); require(
msg.sender == _account,
"Sender not authorized."
);
// Do not forget the "_;"! It will // Do not forget the "_;"! It will
// be replaced by the actual function // be replaced by the actual function
// body when the modifier is used. // body when the modifier is used.
@ -164,7 +167,10 @@ restrictions highly readable.
} }
modifier onlyAfter(uint _time) { modifier onlyAfter(uint _time) {
require(now >= _time); require(
now >= _time,
"Function called too early."
);
_; _;
} }
@ -186,7 +192,10 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0, // This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`. // where it was possible to skip the part after `_;`.
modifier costs(uint _amount) { modifier costs(uint _amount) {
require(msg.value >= _amount); require(
msg.value >= _amount,
"Not enough Ether provided."
);
_; _;
if (msg.value > _amount) if (msg.value > _amount)
msg.sender.send(msg.value - _amount); msg.sender.send(msg.value - _amount);
@ -194,6 +203,7 @@ restrictions highly readable.
function forceOwnerChange(address _newOwner) function forceOwnerChange(address _newOwner)
public public
payable
costs(200 ether) costs(200 ether)
{ {
owner = _newOwner; owner = _newOwner;
@ -272,7 +282,7 @@ function finishes.
:: ::
pragma solidity ^0.4.11; pragma solidity ^0.4.22;
contract StateMachine { contract StateMachine {
enum Stages { enum Stages {
@ -289,7 +299,10 @@ function finishes.
uint public creationTime = now; uint public creationTime = now;
modifier atStage(Stages _stage) { modifier atStage(Stages _stage) {
require(stage == _stage); require(
stage == _stage,
"Function cannot be called at this time."
);
_; _;
} }

View File

@ -40,7 +40,7 @@ This means that cyclic creation dependencies are impossible.
:: ::
pragma solidity ^0.4.16; pragma solidity ^0.4.22;
contract OwnedToken { contract OwnedToken {
// TokenCreator is a contract type that is defined below. // TokenCreator is a contract type that is defined below.
@ -52,7 +52,7 @@ This means that cyclic creation dependencies are impossible.
// This is the constructor which registers the // This is the constructor which registers the
// creator and the assigned name. // creator and the assigned name.
function OwnedToken(bytes32 _name) public { constructor(bytes32 _name) public {
// State variables are accessed via their name // State variables are accessed via their name
// and not via e.g. this.owner. This also applies // and not via e.g. this.owner. This also applies
// to functions and especially in the constructors, // to functions and especially in the constructors,
@ -301,7 +301,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
:: ::
pragma solidity ^0.4.11; pragma solidity ^0.4.22;
contract owned { contract owned {
function owned() public { owner = msg.sender; } function owned() public { owner = msg.sender; }
@ -315,7 +315,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
// function is executed and otherwise, an exception is // function is executed and otherwise, an exception is
// thrown. // thrown.
modifier onlyOwner { modifier onlyOwner {
require(msg.sender == owner); require(
msg.sender == owner,
"Only owner can call this function."
);
_; _;
} }
} }
@ -360,7 +363,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
contract Mutex { contract Mutex {
bool locked; bool locked;
modifier noReentrancy() { modifier noReentrancy() {
require(!locked); require(
!locked,
"Reentrant call."
);
locked = true; locked = true;
_; _;
locked = false; locked = false;
@ -679,7 +685,7 @@ candidate, resolution fails.
} }
} }
Calling ``f(50)`` would create a type error since ``250`` can be implicitly converted both to ``uint8`` Calling ``f(50)`` would create a type error since ``50`` can be implicitly converted both to ``uint8``
and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly
converted to ``uint8``. converted to ``uint8``.
@ -803,7 +809,7 @@ as topics. The event call above can be performed in the same way as
} }
where the long hexadecimal number is equal to where the long hexadecimal number is equal to
``keccak256("Deposit(address,hash256,uint256)")``, the signature of the event. ``keccak256("Deposit(address,bytes32,uint256)")``, the signature of the event.
Additional Resources for Understanding Events Additional Resources for Understanding Events
============================================== ==============================================
@ -976,8 +982,31 @@ virtual method lookup.
Constructors Constructors
============ ============
A constructor is an optional function with the same name as the contract 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``. Constructor functions can be either ``public`` or ``internal``. If there is no constructor, the contract will assume the
default constructor: ``contructor() public {}``.
::
pragma solidity ^0.4.22;
contract A {
uint public a;
constructor(uint _a) internal {
a = _a;
}
}
contract B is A(1) {
constructor() public {}
}
A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`.
.. note ::
Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax is now deprecated.
:: ::
@ -995,7 +1024,6 @@ Constructor functions can be either ``public`` or ``internal``.
function B() public {} function B() public {}
} }
A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`.
.. index:: ! base;constructor .. index:: ! base;constructor
@ -1009,12 +1037,15 @@ the base constructors. This can be done in two ways::
contract Base { contract Base {
uint x; uint x;
function Base(uint _x) public { x = _x; } constructor(uint _x) public { x = _x; }
} }
contract Derived is Base(7) { contract Derived1 is Base(7) {
function Derived(uint _y) Base(_y * _y) public { constructor(uint _y) public {}
} }
contract Derived2 is Base {
constructor(uint _y) Base(_y * _y) public {}
} }
One way is directly in the inheritance list (``is Base(7)``). The other is in One way is directly in the inheritance list (``is Base(7)``). The other is in
@ -1024,8 +1055,9 @@ do it is more convenient if the constructor argument is a
constant and defines the behaviour of the contract or constant and defines the behaviour of the contract or
describes it. The second way has to be used if the describes it. The second way has to be used if the
constructor arguments of the base depend on those of the constructor arguments of the base depend on those of the
derived contract. If, as in this silly example, both places derived contract. Arguments have to be given either in the
are used, the modifier-style argument takes precedence. inheritance list or in modifier-style in the derived constuctor.
Specifying arguments in both places is an error.
.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization .. index:: ! inheritance;multiple, ! linearization, ! C3 linearization
@ -1183,7 +1215,7 @@ more advanced example to implement a set).
:: ::
pragma solidity ^0.4.16; pragma solidity ^0.4.22;
library Set { library Set {
// We define a new struct datatype that will be used to // We define a new struct datatype that will be used to

View File

@ -55,8 +55,8 @@ However, if you are making a larger change, please consult with the `Solidity De
focused on compiler and language development instead of language use) first. focused on compiler and language development instead of language use) first.
Finally, please make sure you respect the `coding standards Finally, please make sure you respect the `coding style
<https://raw.githubusercontent.com/ethereum/cpp-ethereum/develop/CodingStandards.txt>`_ <https://raw.githubusercontent.com/ethereum/solidity/develop/CODING_STYLE.md>`_
for this project. Also, even though we do CI testing, please test your code and for this project. Also, even though we do CI testing, please test your code and
ensure that it builds locally before submitting a pull request. ensure that it builds locally before submitting a pull request.
@ -69,21 +69,158 @@ Solidity includes different types of tests. They are included in the application
called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode, called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode,
some others require ``libz3`` to be installed. some others require ``libz3`` to be installed.
To disable the z3 tests, use ``./build/test/soltest -- --no-smt`` and ``soltest`` reads test contracts that are annotated with expected results
to run a subset of the tests that do not require ``cpp-ethereum``, use ``./build/test/soltest -- --no-ipc``. stored in ``./test/libsolidity/syntaxTests``. In order for soltest to find these
tests the root test directory has to be specified using the ``--testpath`` command
line option, e.g. ``./build/test/soltest -- --testpath ./test``.
To disable the z3 tests, use ``./build/test/soltest -- --no-smt --testpath ./test`` and
to run a subset of the tests that do not require ``cpp-ethereum``, use
``./build/test/soltest -- --no-ipc --testpath ./test``.
For all other tests, you need to install `cpp-ethereum <https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth>`_ and run it in testing mode: ``eth --test -d /tmp/testeth``. For all other tests, you need to install `cpp-ethereum <https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth>`_ and run it in testing mode: ``eth --test -d /tmp/testeth``.
Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc``. Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test``.
To run a subset of tests, filters can be used: To run a subset of tests, filters can be used:
``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``. ``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc --testpath ./test``,
where ``TestName`` can be a wildcard ``*``.
Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests and runs Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests and runs
``cpp-ethereum`` automatically if it is in the path (but does not download it). ``cpp-ethereum`` automatically if it is in the path (but does not download it).
Travis CI even runs some additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target. Travis CI even runs some additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target.
Writing and running syntax tests
--------------------------------
As mentioned above, syntax tests are stored in individual contracts. These files must contain annotations, stating the expected result(s) of the respective test.
The test suite will compile and check them against the given expectations.
Example: ``./test/libsolidity/syntaxTests/double_stateVariable_declaration.sol``
::
contract test {
uint256 variable;
uint128 variable;
}
// ----
// DeclarationError: Identifier already declared.
A syntax test must contain at least the contract under test itself, followed by the seperator ``----``. The additional comments above are used to describe the
expected compiler errors or warnings. This section can be empty in case that the contract should compile without any errors or warnings.
In the above example, the state variable ``variable`` was declared twice, which is not allowed. This will result in a ``DeclarationError`` stating that the identifer was already declared.
The tool that is being used for those tests is called ``isoltest`` and can be found under ``./test/tools/``. It is an interactive tool which allows
editing of failing contracts using your prefered text editor. Let's try to break this test by removing the second declaration of ``variable``:
::
contract test {
uint256 variable;
}
// ----
// DeclarationError: Identifier already declared.
Running ``./test/isoltest`` again will result in a test failure:
::
syntaxTests/double_stateVariable_declaration.sol: FAIL
Contract:
contract test {
uint256 variable;
}
Expected result:
DeclarationError: Identifier already declared.
Obtained result:
Success
which prints the expected result next to the obtained result, but also provides a way to change edit / update / skip the current contract or to even quit.
``isoltest`` offers several options for failing tests:
- edit: ``isoltest`` will try to open the editor that was specified before using ``isoltest --editor /path/to/editor``. If no path was set, this will result in a runtime error. In case an editor was specified, this will open it such that the contract can be adjusted.
- update: Updates the contract under test. This will either remove the annotation which contains the exception not met or will add missing expectations. The test will then be run again.
- skip: Skips the execution of this particular test.
- quit: Quits ``isoltest``.
Automatically updating the test above will change it to
::
contract test {
uint256 variable;
}
// ----
and re-run the test. It will now pass again:
::
Re-running test case...
syntaxTests/double_stateVariable_declaration.sol: OK
.. note::
Please choose a name for the contract file, that is self-explainatory in the sense of what is been tested, e.g. ``double_variable_declaration.sol``.
Do not put more than one contract into a single file. ``isoltest`` is currently not able to recognize them individually.
Running the Fuzzer via AFL
==========================
Fuzzing is a technique that runs programs on more or less random inputs to find exceptional execution
states (segmentation faults, exceptions, etc). Modern fuzzers are clever and do a directed search
inside the input. We have a specialized binary called ``solfuzzer`` which takes source code as input
and fails whenever it encounters an internal compiler error, segmentation fault or similar, but
does not fail if e.g. the code contains an error. This way, internal problems in the compiler
can be found by fuzzing tools.
We mainly use `AFL <http://lcamtuf.coredump.cx/afl/>`_ for fuzzing. You need to download and
build AFL manually. Next, build Solidity (or just the ``solfuzzer`` binary) with AFL as your compiler:
::
cd build
# if needed
make clean
cmake .. -DCMAKE_C_COMPILER=path/to/afl-gcc -DCMAKE_CXX_COMPILER=path/to/afl-g++
make solfuzzer
Next, you need some example source files. This will make it much easer for the fuzzer
to find errors. You can either copy some files from the syntax tests or extract test files
from the documentation or the other tests:
::
mkdir /tmp/test_cases
cd /tmp/test_cases
# extract from tests:
path/to/solidity/scripts/isolate_tests.py path/to/solidity/test/libsolidity/SolidityEndToEndTest.cpp
# extract from documentation:
path/to/solidity/scripts/isolate_tests.py path/to/solidity/docs docs
The AFL documentation states that the corpus (the initial input files) should not be
too large. The files themselves should not be larger than 1 kB and there should be
at most one input file per functionality, so better start with a small number of
input files. There is also a tool called ``afl-cmin`` that can trim input files
that result in similar behaviour of the binary.
Now run the fuzzer (the ``-m`` extends the size of memory to 60 MB):
::
afl-fuzz -m 60 -i /tmp/test_cases -o /tmp/fuzzer_reports -- /path/to/solfuzzer
The fuzzer will create source files that lead to failures in ``/tmp/fuzzer_reports``.
Often it finds many similar source files that produce the same error. You can
use the tool ``scripts/uniqueErrors.sh`` to filter out the unique errors.
Whiskers Whiskers
======== ========

View File

@ -284,10 +284,12 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di
} }
function g() public { function g() public {
// Declares and assigns the variables. Specifying the type explicitly is not possible. // Variables declared with type
var (x, b, y) = f(); uint x;
// Assigns to a pre-existing variable. bool b;
(x, y) = (2, 7); uint y;
// Tuple values can be assigned to these pre-existing variables
(x, b, y) = f();
// Common trick to swap values -- does not work for non-value storage types. // Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x); (x, y) = (y, x);
// Components can be left out (also for variable declarations). // Components can be left out (also for variable declarations).
@ -453,8 +455,9 @@ The ``require`` function should be used to ensure valid conditions, such as inpu
If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix. If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.
There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and
revert the current call. In the future it might be possible to also include details about the error revert the current call. It is possible to provide a string message containing details about the error
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``. that will be passed back to the caller.
The deprecated keyword ``throw`` can also be used as an alternative to ``revert()`` (but only without error message).
.. note:: .. note::
From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future. From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future.
@ -469,13 +472,16 @@ of an exception instead of "bubbling up".
Catching exceptions is not yet possible. Catching exceptions is not yet possible.
In the following example, you can see how ``require`` can be used to easily check conditions on inputs In the following example, you can see how ``require`` can be used to easily check conditions on inputs
and how ``assert`` can be used for internal error checking:: and how ``assert`` can be used for internal error checking. Note that you can optionally provide
a message string for ``require``, but not for ``assert``.
pragma solidity ^0.4.0; ::
pragma solidity ^0.4.22;
contract Sharer { contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) { function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0); // Only allow even numbers require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = this.balance; uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2); addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and // Since transfer throws an exception on failure and
@ -513,3 +519,33 @@ the EVM to revert all changes made to the state. The reason for reverting is tha
did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction
(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while (or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while
``require``-style exceptions will not consume any gas starting from the Metropolis release. ``require``-style exceptions will not consume any gas starting from the Metropolis release.
The following example shows how an error string can be used together with revert and require:
::
pragma solidity ^0.4.22;
contract VendingMachine {
function buy(uint amount) payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Alternative way to do it:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// Perform the purchase.
}
}
The provided string will be :ref:`abi-encoded <ABI>` as if it were a call to a function ``Error(string)``.
In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be
set as error return data:
.. code::
0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data

View File

@ -19,7 +19,7 @@ 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) ';' UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{' StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}' ( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block ModifierDefinition = 'modifier' Identifier ParameterList? Block
ModifierInvocation = Identifier ( '(' ExpressionList? ')' )? ModifierInvocation = Identifier ( '(' ExpressionList? ')' )?

View File

@ -39,6 +39,7 @@ This documentation is translated into several languages by community volunteers,
* `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress) * `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress)
* `Spanish <https://solidity-es.readthedocs.io>`_ * `Spanish <https://solidity-es.readthedocs.io>`_
* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated) * `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
Useful links Useful links

View File

@ -122,7 +122,6 @@ We will re-add the pre-built bottles soon.
brew upgrade brew upgrade
brew tap ethereum/ethereum brew tap ethereum/ethereum
brew install solidity brew install solidity
brew linkapps solidity
If you need a specific version of Solidity you can install a If you need a specific version of Solidity you can install a
Homebrew formula directly from Github. Homebrew formula directly from Github.

View File

@ -326,7 +326,13 @@ EVM bytecode and executed. The output of this execution is
permanently stored as the code of the contract. permanently stored as the code of the contract.
This means that in order to create a contract, you do not This means that in order to create a contract, you do not
send the actual code of the contract, but in fact code that send the actual code of the contract, but in fact code that
returns that code. returns that code when executed.
.. note::
While a contract is being created, its code is still empty.
Because of that, you should not call back into the
contract under construction until its constructor has
finished executing.
.. index:: ! gas, ! gas price .. index:: ! gas, ! gas price

View File

@ -64,12 +64,15 @@ The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint25
Layout in Memory Layout in Memory
**************** ****************
Solidity reserves three 256-bit slots: Solidity reserves four 32 byte slots:
- 0 - 64: scratch space for hashing methods - ``0x00`` - ``0x3f``: scratch space for hashing methods
- 64 - 96: currently allocated memory size (aka. free memory pointer) - ``0x40`` - ``0x5f``: currently allocated memory size (aka. free memory pointer)
- ``0x60`` - ``0x7f``: zero slot
Scratch space can be used between statements (ie. within inline assembly). Scratch space can be used between statements (ie. within inline assembly). The zero slot
is used as initial value for dynamic memory arrays and should never be written to
(the free memory pointer points to ``0x80`` initially).
Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future). Solidity always places new objects at the free memory pointer and memory is never freed (this might change in the future).
@ -314,7 +317,12 @@ The following is the order of precedence for operators, listed in order of evalu
Global Variables Global Variables
================ ================
- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks - ``abi.encode(...) returns (bytes)``: :ref:`ABI <ABI>`-encodes the given arguments
- ``abi.encodePacked(...) returns (bytes)``: Performes :ref:`packed encoding <abi_packed_mode>` of the given arguments
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)``: :ref:`ABI <ABI>`-encodes the given arguments
starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string signature, ...) returns (bytes)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(signature), ...)```
- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by ``blockhash(uint blockNumber)``.
- ``block.coinbase`` (``address``): current block miner's address - ``block.coinbase`` (``address``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty - ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit - ``block.gaslimit`` (``uint``): current block gaslimit
@ -330,7 +338,10 @@ Global Variables
- ``tx.origin`` (``address``): sender of the transaction (full call chain) - ``tx.origin`` (``address``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) - ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component) - ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
- ``require(bool condition, string message)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``revert()``: abort execution and revert state changes - ``revert()``: abort execution and revert state changes
- ``revert(string message)``: abort execution and revert state changes providing an explanatory string
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the :ref:`(tightly packed) arguments <abi_packed_mode>` - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the :ref:`(tightly packed) arguments <abi_packed_mode>`
- ``sha3(...) returns (bytes32)``: an alias to ``keccak256`` - ``sha3(...) returns (bytes32)``: an alias to ``keccak256``
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the :ref:`(tightly packed) arguments <abi_packed_mode>` - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the :ref:`(tightly packed) arguments <abi_packed_mode>`
@ -341,11 +352,28 @@ Global Variables
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address`` - ``this`` (current contract's type): the current contract, explicitly convertible to ``address``
- ``super``: the contract one level higher in the inheritance hierarchy - ``super``: the contract one level higher in the inheritance hierarchy
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address - ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
- ``suicide(address recipient)``: an alias to ``selfdestruct`` - ``suicide(address recipient)``: a deprecated alias to ``selfdestruct``
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure - ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree.
Bad actors in the mining community can for example run a casino payout function on a chosen hash
and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block,
but the only guarantee is that it will be somewhere between the timestamps of two
consecutive blocks in the canonical chain.
.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
.. index:: visibility, public, private, external, internal .. index:: visibility, public, private, external, internal
Function Visibility Specifiers Function Visibility Specifiers

View File

@ -36,7 +36,7 @@ of votes.
:: ::
pragma solidity ^0.4.16; pragma solidity ^0.4.22;
/// @title Voting with delegation. /// @title Voting with delegation.
contract Ballot { contract Ballot {
@ -87,18 +87,25 @@ of votes.
// Give `voter` the right to vote on this ballot. // Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`. // May only be called by `chairperson`.
function giveRightToVote(address voter) public { function giveRightToVote(address voter) public {
// If the argument of `require` evaluates to `false`, // If the first argument of `require` evaluates
// it terminates and reverts all changes to // to `false`, execution terminates and all
// the state and to Ether balances. It is often // changes to the state and to Ether balances
// a good idea to use this if functions are // are reverted.
// called incorrectly. But watch out, this // This used to consume all gas in old EVM versions, but
// will currently also consume all provided gas // not anymore.
// (this is planned to change in the future). // It is often a good idea to use `require` to check if
// functions are called correctly.
// As a second argument, you can also provide an
// explanation about what went wrong.
require( require(
(msg.sender == chairperson) && msg.sender == chairperson,
!voters[voter].voted && "Only chairperson can give right to vote."
(voters[voter].weight == 0)
); );
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1; voters[voter].weight = 1;
} }
@ -106,10 +113,9 @@ of votes.
function delegate(address to) public { function delegate(address to) public {
// assigns reference // assigns reference
Voter storage sender = voters[msg.sender]; Voter storage sender = voters[msg.sender];
require(!sender.voted); require(!sender.voted, "You already voted.");
// Self-delegation is not allowed. require(to != msg.sender, "Self-delegation is disallowed.");
require(to != msg.sender);
// Forward the delegation as long as // Forward the delegation as long as
// `to` also delegated. // `to` also delegated.
@ -123,7 +129,7 @@ of votes.
to = voters[to].delegate; to = voters[to].delegate;
// We found a loop in the delegation, not allowed. // We found a loop in the delegation, not allowed.
require(to != msg.sender); require(to != msg.sender, "Found loop in delegation.");
} }
// Since `sender` is a reference, this // Since `sender` is a reference, this
@ -146,7 +152,7 @@ of votes.
/// to proposal `proposals[proposal].name`. /// to proposal `proposals[proposal].name`.
function vote(uint proposal) public { function vote(uint proposal) public {
Voter storage sender = voters[msg.sender]; Voter storage sender = voters[msg.sender];
require(!sender.voted); require(!sender.voted, "Already voted.");
sender.voted = true; sender.voted = true;
sender.vote = proposal; sender.vote = proposal;
@ -219,7 +225,7 @@ activate themselves.
:: ::
pragma solidity ^0.4.21; pragma solidity ^0.4.22;
contract SimpleAuction { contract SimpleAuction {
// Parameters of the auction. Times are either // Parameters of the auction. Times are either
@ -271,11 +277,17 @@ activate themselves.
// Revert the call if the bidding // Revert the call if the bidding
// period is over. // period is over.
require(now <= auctionEnd); require(
now <= auctionEnd,
"Auction already ended."
);
// If the bid is not higher, send the // If the bid is not higher, send the
// money back. // money back.
require(msg.value > highestBid); require(
msg.value > highestBid,
"There already is a higher bid."
);
if (highestBid != 0) { if (highestBid != 0) {
// Sending back the money by simply using // Sending back the money by simply using
@ -325,8 +337,8 @@ activate themselves.
// external contracts. // external contracts.
// 1. Conditions // 1. Conditions
require(now >= auctionEnd); // auction did not yet end require(now >= auctionEnd, "Auction not yet ended.");
require(!ended); // this function has already been called require(!ended, "auctionEnd has already been called.");
// 2. Effects // 2. Effects
ended = true; ended = true;
@ -376,7 +388,7 @@ high or low invalid bids.
:: ::
pragma solidity ^0.4.21; pragma solidity ^0.4.22;
contract BlindAuction { contract BlindAuction {
struct Bid { struct Bid {
@ -529,7 +541,7 @@ Safe Remote Purchase
:: ::
pragma solidity ^0.4.21; pragma solidity ^0.4.22;
contract Purchase { contract Purchase {
uint public value; uint public value;
@ -544,7 +556,7 @@ Safe Remote Purchase
function Purchase() public payable { function Purchase() public payable {
seller = msg.sender; seller = msg.sender;
value = msg.value / 2; value = msg.value / 2;
require((2 * value) == msg.value); require((2 * value) == msg.value, "Value has to be even.");
} }
modifier condition(bool _condition) { modifier condition(bool _condition) {
@ -553,17 +565,26 @@ Safe Remote Purchase
} }
modifier onlyBuyer() { modifier onlyBuyer() {
require(msg.sender == buyer); require(
msg.sender == buyer,
"Only buyer can call this."
);
_; _;
} }
modifier onlySeller() { modifier onlySeller() {
require(msg.sender == seller); require(
msg.sender == seller,
"Only seller can call this."
);
_; _;
} }
modifier inState(State _state) { modifier inState(State _state) {
require(state == _state); require(
state == _state,
"Invalid state."
);
_; _;
} }

View File

@ -62,13 +62,16 @@ Function modifiers can be used to amend the semantics of functions in a declarat
:: ::
pragma solidity ^0.4.11; pragma solidity ^0.4.22;
contract Purchase { contract Purchase {
address public seller; address public seller;
modifier onlySeller() { // Modifier modifier onlySeller() { // Modifier
require(msg.sender == seller); require(
msg.sender == seller,
"Only seller can call this."
);
_; _;
} }

View File

@ -904,7 +904,7 @@ Constants
========= =========
Constants should be named with all capital letters with underscores separating Constants should be named with all capital letters with underscores separating
words. Examples: ``MAX_BLOCKS``, `TOKEN_NAME`, ``TOKEN_TICKER``, ``CONTRACT_VERSION``. words. Examples: ``MAX_BLOCKS``, ``TOKEN_NAME``, ``TOKEN_TICKER``, ``CONTRACT_VERSION``.
Modifier Names Modifier Names

View File

@ -81,7 +81,7 @@ Fixed Point Numbers
``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represents the number of bits taken by ``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represents the number of bits taken by
the type and ``N`` represents how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive. the type and ``N`` represents how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive.
``ufixed`` and ``fixed`` are aliases for ``ufixed128x19`` and ``fixed128x19``, respectively. ``ufixed`` and ``fixed`` are aliases for ``ufixed128x18`` and ``fixed128x18``, respectively.
Operators: Operators:
@ -179,8 +179,8 @@ All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-lev
The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``.
.. note:: .. note::
All contracts inherit the members of address, so it is possible to query the balance of the All contracts can be converted to ``address`` type, so it is possible to query the balance of the
current contract using ``this.balance``. current contract using ``address(this).balance``.
.. note:: .. note::
The use of ``callcode`` is discouraged and will be removed in the future. The use of ``callcode`` is discouraged and will be removed in the future.
@ -470,7 +470,7 @@ Example that shows how to use internal function types::
Another example that uses external function types:: Another example that uses external function types::
pragma solidity ^0.4.21; pragma solidity ^0.4.22;
contract Oracle { contract Oracle {
struct Request { struct Request {
@ -495,7 +495,10 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse); oracle.query("USD", this.oracleResponse);
} }
function oracleResponse(bytes response) public { function oracleResponse(bytes response) public {
require(msg.sender == address(oracle)); require(
msg.sender == address(oracle),
"Only oracle can call this."
);
// Use the data // Use the data
} }
} }

View File

@ -44,15 +44,16 @@ Special Variables and Functions
=============================== ===============================
There are special variables and functions which always exist in the global There are special variables and functions which always exist in the global
namespace and are mainly used to provide information about the blockchain. namespace and are mainly used to provide information about the blockchain
or are general-use utility functions.
.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin .. index:: abi, block, coinbase, difficulty, encode, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin
Block and Transaction Properties Block and Transaction Properties
-------------------------------- --------------------------------
- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks excluding current - ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent, excluding current, blocks - deprecated in version 0.4.22 and replaced by ``blockhash(uint blockNumber)``.
- ``block.coinbase`` (``address``): current block miner's address - ``block.coinbase`` (``address``): current block miner's address
- ``block.difficulty`` (``uint``): current block difficulty - ``block.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit - ``block.gaslimit`` (``uint``): current block gaslimit
@ -74,7 +75,7 @@ Block and Transaction Properties
This includes calls to library functions. This includes calls to library functions.
.. note:: .. note::
Do not rely on ``block.timestamp``, ``now`` and ``block.blockhash`` as a source of randomness, Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
unless you know what you are doing. unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree. Both the timestamp and the block hash can be influenced by miners to some degree.
@ -90,6 +91,26 @@ Block and Transaction Properties
You can only access the hashes of the most recent 256 blocks, all other You can only access the hashes of the most recent 256 blocks, all other
values will be zero. values will be zero.
.. index:: abi, encoding, packed
ABI Encoding Functions
----------------------
- ``abi.encode(...) returns (bytes)``: ABI-encodes the given arguments
- ``abi.encodePacked(...) returns (bytes)``: Performes packed encoding of the given arguments
- ``abi.encodeWithSelector(bytes4 selector, ...) returns (bytes)``: ABI-encodes the given arguments
starting from the second and prepends the given four-byte selector
- ``abi.encodeWithSignature(string signature, ...) returns (bytes)``: Equivalent to ``abi.encodeWithSelector(bytes4(keccak256(signature), ...)```
.. note::
These encoding functions can be used to craft data for function calls without actually
calling a function. Furthermore, ``keccak256(abi.encodePacked(a, b))`` is a more
explicit way to compute ``keccak256(a, b)``, which will be deprecated in future
versions.
See the documentation about the :ref:`ABI <ABI>` and the
:ref:`tightly packed encoding <abi_packed_mode>` for details about the encoding.
.. index:: assert, revert, require .. index:: assert, revert, require
Error Handling Error Handling
@ -99,8 +120,12 @@ Error Handling
throws if the condition is not met - to be used for internal errors. throws if the condition is not met - to be used for internal errors.
``require(bool condition)``: ``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components. throws 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.
``revert()``: ``revert()``:
abort execution and revert state changes abort execution and revert state changes
``revert(string reason)``:
abort execution and revert state changes, providing an explanatory string
.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, .. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,
@ -168,6 +193,13 @@ For more information, see the section on :ref:`address`.
to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better:
Use a pattern where the recipient withdraws the money. Use a pattern where the recipient withdraws the money.
.. note::
If storage variables are accessed via a low-level delegatecall, the storage layout of the two contracts
must align in order for the called contract to correctly access the storage variables of the calling contract by name.
This is of course not the case if storage pointers are passed as function arguments as in the case for
the high-level libraries.
.. note:: .. note::
The use of ``callcode`` is discouraged and will be removed in the future. The use of ``callcode`` is discouraged and will be removed in the future.
@ -183,7 +215,7 @@ Contract Related
destroy the current contract, sending its funds to the given :ref:`address` destroy the current contract, sending its funds to the given :ref:`address`
``suicide(address recipient)``: ``suicide(address recipient)``:
alias to ``selfdestruct`` deprecated alias to ``selfdestruct``
Furthermore, all functions of the current contract are callable directly including the current function. Furthermore, all functions of the current contract are callable directly including the current function.

76
libdevcore/Algorithms.h Normal file
View File

@ -0,0 +1,76 @@
/*
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 <functional>
#include <set>
namespace dev
{
/**
* Detector for cycles in directed graphs. It returns the first
* vertex on the path towards a cycle or a nullptr if there is
* no reachable cycle starting from a given vertex.
*/
template <typename V>
class CycleDetector
{
public:
/// Initializes the cycle detector
/// @param _visit function that is given the current vertex
/// and is supposed to call @a run on all
/// adjacent vertices.
explicit CycleDetector(std::function<void(V const&, CycleDetector&)> _visit):
m_visit(std::move(_visit))
{ }
/// Recursively perform cycle detection starting
/// (or continuing) with @param _vertex
/// @returns the first vertex on the path towards a cycle from @a _vertex
/// or nullptr if no cycle is reachable from @a _vertex.
V const* run(V const& _vertex)
{
if (m_firstCycleVertex)
return m_firstCycleVertex;
if (m_processed.count(&_vertex))
return nullptr;
else if (m_processing.count(&_vertex))
return m_firstCycleVertex = &_vertex;
m_processing.insert(&_vertex);
m_depth++;
m_visit(_vertex, *this);
m_depth--;
if (m_firstCycleVertex && m_depth == 1)
m_firstCycleVertex = &_vertex;
m_processing.erase(&_vertex);
m_processed.insert(&_vertex);
return m_firstCycleVertex;
}
private:
std::function<void(V const&, CycleDetector&)> m_visit;
std::set<V const*> m_processing;
std::set<V const*> m_processed;
size_t m_depth = 0;
V const* m_firstCycleVertex = nullptr;
};
}

View File

@ -2,9 +2,7 @@ file(GLOB sources "*.cpp")
file(GLOB headers "*.h") file(GLOB headers "*.h")
add_library(devcore ${sources} ${headers}) add_library(devcore ${sources} ${headers})
target_link_libraries(devcore PRIVATE ${JSONCPP_LIBRARY} ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(devcore PRIVATE jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}") target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}")
target_include_directories(devcore PUBLIC "${JSONCPP_INCLUDE_DIR}") target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
add_dependencies(devcore jsoncpp)
add_dependencies(devcore solidity_BuildInfo.h) add_dependencies(devcore solidity_BuildInfo.h)

View File

@ -27,6 +27,7 @@
#if defined(_WIN32) #if defined(_WIN32)
#include <windows.h> #include <windows.h>
#else #else
#include <unistd.h>
#include <termios.h> #include <termios.h>
#endif #endif
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
@ -118,3 +119,71 @@ void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDe
} }
} }
} }
#if defined(_WIN32)
class DisableConsoleBuffering
{
public:
DisableConsoleBuffering()
{
m_stdin = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(m_stdin, &m_oldMode);
SetConsoleMode(m_stdin, m_oldMode & (~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT)));
}
~DisableConsoleBuffering()
{
SetConsoleMode(m_stdin, m_oldMode);
}
private:
HANDLE m_stdin;
DWORD m_oldMode;
};
#else
class DisableConsoleBuffering
{
public:
DisableConsoleBuffering()
{
tcgetattr(0, &m_termios);
m_termios.c_lflag &= ~ICANON;
m_termios.c_lflag &= ~ECHO;
m_termios.c_cc[VMIN] = 1;
m_termios.c_cc[VTIME] = 0;
tcsetattr(0, TCSANOW, &m_termios);
}
~DisableConsoleBuffering()
{
m_termios.c_lflag |= ICANON;
m_termios.c_lflag |= ECHO;
tcsetattr(0, TCSADRAIN, &m_termios);
}
private:
struct termios m_termios;
};
#endif
int dev::readStandardInputChar()
{
DisableConsoleBuffering disableConsoleBuffering;
return cin.get();
}
boost::filesystem::path dev::weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path)
{
if (boost::filesystem::exists(_path))
return boost::filesystem::canonical(_path);
else
{
boost::filesystem::path head(_path);
boost::filesystem::path tail;
for (auto it = --_path.end(); !head.empty(); --it)
{
if (boost::filesystem::exists(head))
break;
tail = (*it) / tail;
head.remove_filename();
}
head = boost::filesystem::canonical(head);
return head / tail;
}
}

View File

@ -25,6 +25,7 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <boost/filesystem.hpp>
#include "Common.h" #include "Common.h"
namespace dev namespace dev
@ -37,6 +38,9 @@ std::string readFileAsString(std::string const& _file);
/// Retrieve and returns the contents of standard input (until EOF). /// Retrieve and returns the contents of standard input (until EOF).
std::string readStandardInput(); std::string readStandardInput();
/// Retrieve and returns a character from standard input (without waiting for EOL).
int readStandardInputChar();
/// Write the given binary data into the given file, replacing the file if it pre-exists. /// Write the given binary data into the given file, replacing the file if it pre-exists.
/// Throws exception on error. /// Throws exception on error.
/// @param _writeDeleteRename useful not to lose any data: If set, first writes to another file in /// @param _writeDeleteRename useful not to lose any data: If set, first writes to another file in
@ -54,4 +58,8 @@ std::string toString(_T const& _t)
return o.str(); return o.str();
} }
/// Partial implementation of boost::filesystem::weakly_canonical (available in boost>=1.60).
/// Should be replaced by the boost implementation as soon as support for boost<1.60 can be dropped.
boost::filesystem::path weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path);
} }

View File

@ -438,13 +438,15 @@ map<u256, u256> Assembly::optimiseInternal(
// function types that can be stored in storage. // function types that can be stored in storage.
AssemblyItems optimisedItems; AssemblyItems optimisedItems;
bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem(Instruction::MSIZE)) != m_items.end());
auto iter = m_items.begin(); auto iter = m_items.begin();
while (iter != m_items.end()) while (iter != m_items.end())
{ {
KnownState emptyState; KnownState emptyState;
CommonSubexpressionEliminator eliminator(emptyState); CommonSubexpressionEliminator eliminator(emptyState);
auto orig = iter; auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end()); iter = eliminator.feedItems(iter, m_items.end(), usesMSize);
bool shouldReplace = false; bool shouldReplace = false;
AssemblyItems optimisedChunk; AssemblyItems optimisedChunk;
try try

View File

@ -65,8 +65,9 @@ public:
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
/// item that must be fed into a new instance of the eliminator. /// item that must be fed into a new instance of the eliminator.
/// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect
template <class _AssemblyItemIterator> template <class _AssemblyItemIterator>
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant);
/// @returns the resulting items after optimization. /// @returns the resulting items after optimization.
AssemblyItems getOptimizedItems(); AssemblyItems getOptimizedItems();
@ -168,11 +169,12 @@ private:
template <class _AssemblyItemIterator> template <class _AssemblyItemIterator>
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( _AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _iterator, _AssemblyItemIterator _iterator,
_AssemblyItemIterator _end _AssemblyItemIterator _end,
bool _msizeImportant
) )
{ {
assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator."); assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator.");
for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator) for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator, _msizeImportant); ++_iterator)
feedItem(*_iterator); feedItem(*_iterator);
if (_iterator != _end) if (_iterator != _end)
m_breakingItem = &(*_iterator++); m_breakingItem = &(*_iterator++);

View File

@ -199,7 +199,7 @@ static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } }, { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } },
{ Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } }, { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } },
{ Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } }, { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } },
{ Instruction::KECCAK256, { "KECCAK256", 0, 2, 1, false, Tier::Special } }, { Instruction::KECCAK256, { "KECCAK256", 0, 2, 1, true, Tier::Special } },
{ Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } }, { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
{ Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } }, { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } },
{ Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } }, { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },

View File

@ -154,6 +154,51 @@ struct DoublePush: SimplePeepholeOptimizerMethod<DoublePush, 2>
} }
}; };
struct CommutativeSwap: SimplePeepholeOptimizerMethod<CommutativeSwap, 2>
{
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
{
// Remove SWAP1 if following instruction is commutative
if (
_swap.type() == Operation &&
_swap.instruction() == Instruction::SWAP1 &&
SemanticInformation::isCommutativeOperation(_op)
)
{
*_out = _op;
return true;
}
else
return false;
}
};
struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
{
static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator<AssemblyItems> _out)
{
map<Instruction, Instruction> swappableOps{
{ Instruction::LT, Instruction::GT },
{ Instruction::GT, Instruction::LT },
{ Instruction::SLT, Instruction::SGT },
{ Instruction::SGT, Instruction::SLT }
};
if (
_swap.type() == Operation &&
_swap.instruction() == Instruction::SWAP1 &&
_op.type() == Operation &&
swappableOps.count(_op.instruction())
)
{
*_out = swappableOps.at(_op.instruction());
return true;
}
else
return false;
}
};
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3> struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
{ {
static size_t applySimple( static size_t applySimple(
@ -260,7 +305,7 @@ bool PeepholeOptimiser::optimise()
{ {
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
while (state.i < m_items.size()) while (state.i < m_items.size())
applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity()); applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), JumpToNext(), UnreachableCode(), TagConjunctions(), Identity());
if (m_optimisedItems.size() < m_items.size() || ( if (m_optimisedItems.size() < m_items.size() || (
m_optimisedItems.size() == m_items.size() && ( m_optimisedItems.size() == m_items.size() && (
eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) || eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) ||

View File

@ -89,6 +89,16 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList(
u256 mask = (u256(1) << testBit) - 1; u256 mask = (u256(1) << testBit) - 1;
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
}, false}, }, false},
{{Instruction::SHL, {A, B}}, [=]{
if (A.d() > 255)
return u256(0);
return u256(bigint(B.d()) << unsigned(A.d()));
}, false},
{{Instruction::SHR, {A, B}}, [=]{
if (A.d() > 255)
return u256(0);
return B.d() >> unsigned(A.d());
}, false},
// invariants involving known constants // invariants involving known constants
{{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false},

View File

@ -28,7 +28,7 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::eth; using namespace dev::eth;
bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant)
{ {
switch (_item.type()) switch (_item.type())
{ {
@ -59,6 +59,11 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item)
return false; return false;
if (_item.instruction() == Instruction::MSTORE) if (_item.instruction() == Instruction::MSTORE)
return false; return false;
if (!_msizeImportant && (
_item.instruction() == Instruction::MLOAD ||
_item.instruction() == Instruction::KECCAK256
))
return false;
//@todo: We do not handle the following memory instructions for now: //@todo: We do not handle the following memory instructions for now:
// calldatacopy, codecopy, extcodecopy, mstore8, // calldatacopy, codecopy, extcodecopy, mstore8,
// msize (note that msize also depends on memory read access) // msize (note that msize also depends on memory read access)

View File

@ -38,7 +38,8 @@ class AssemblyItem;
struct SemanticInformation struct SemanticInformation
{ {
/// @returns true if the given items starts a new block for common subexpression analysis. /// @returns true if the given items starts a new block for common subexpression analysis.
static bool breaksCSEAnalysisBlock(AssemblyItem const& _item); /// @param _msizeImportant if false, consider an operation non-breaking if its only side-effect is that it modifies msize.
static bool breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant);
/// @returns true if the item is a two-argument operation whose value does not depend on the /// @returns true if the item is a two-argument operation whose value does not depend on the
/// order of its arguments. /// order of its arguments.
static bool isCommutativeOperation(AssemblyItem const& _item); static bool isCommutativeOperation(AssemblyItem const& _item);

View File

@ -0,0 +1,48 @@
/*(
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/>.
*/
/**
* Optimisation stage that replaces expressions known to be the current value of a variable
* in scope by a reference to that variable.
*/
#include <libjulia/optimiser/CommonSubexpressionEliminator.h>
#include <libjulia/optimiser/Metrics.h>
#include <libjulia/optimiser/SyntacticalEquality.h>
#include <libsolidity/inlineasm/AsmData.h>
using namespace std;
using namespace dev;
using namespace dev::julia;
void CommonSubexpressionEliminator::visit(Expression& _e)
{
// Single exception for substitution: We do not substitute one variable for another.
if (_e.type() != typeid(Identifier))
// TODO this search rather inefficient.
for (auto const& var: m_value)
{
solAssert(var.second, "");
if (SyntacticalEqualityChecker::equal(_e, *var.second))
{
_e = Identifier{locationOf(_e), var.first};
break;
}
}
DataFlowAnalyzer::visit(_e);
}

View File

@ -0,0 +1,45 @@
/*
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/>.
*/
/**
* Optimisation stage that replaces expressions known to be the current value of a variable
* in scope by a reference to that variable.
*/
#pragma once
#include <libjulia/optimiser/DataFlowAnalyzer.h>
namespace dev
{
namespace julia
{
/**
* Optimisation stage that replaces expressions known to be the current value of a variable
* in scope by a reference to that variable.
*
* Prerequisite: Disambiguator
*/
class CommonSubexpressionEliminator: public DataFlowAnalyzer
{
protected:
using ASTModifier::visit;
virtual void visit(Expression& _e) override;
};
}
}

View File

@ -87,6 +87,12 @@ void ConstantEvaluator::endVisit(Identifier const& _identifier)
setType(_identifier, type(*value)); setType(_identifier, type(*value));
} }
void ConstantEvaluator::endVisit(TupleExpression const& _tuple)
{
if (!_tuple.isInlineArray() && _tuple.components().size() == 1)
setType(_tuple, type(*_tuple.components().front()));
}
void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type) void ConstantEvaluator::setType(ASTNode const& _node, TypePointer const& _type)
{ {
if (_type && _type->category() == Type::Category::RationalNumber) if (_type && _type->category() == Type::Category::RationalNumber)

View File

@ -56,6 +56,7 @@ private:
virtual void endVisit(UnaryOperation const& _operation); virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal); virtual void endVisit(Literal const& _literal);
virtual void endVisit(Identifier const& _identifier); virtual void endVisit(Identifier const& _identifier);
virtual void endVisit(TupleExpression const& _tuple);
void setType(ASTNode const& _node, TypePointer const& _type); void setType(ASTNode const& _node, TypePointer const& _type);
TypePointer type(ASTNode const& _node); TypePointer type(ASTNode const& _node);

View File

@ -45,7 +45,8 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if ( if (
dynamic_cast<FunctionDefinition const*>(&_declaration) || dynamic_cast<FunctionDefinition const*>(&_declaration) ||
dynamic_cast<EventDefinition const*>(&_declaration) dynamic_cast<EventDefinition const*>(&_declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(&_declaration)
) )
{ {
// check that all other declarations with the same name are functions or a public state variable or events. // check that all other declarations with the same name are functions or a public state variable or events.
@ -68,6 +69,11 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
!dynamic_cast<EventDefinition const*>(declaration) !dynamic_cast<EventDefinition const*>(declaration)
) )
return declaration; return declaration;
if (
dynamic_cast<MagicVariableDeclaration const*>(&_declaration) &&
!dynamic_cast<MagicVariableDeclaration const*>(declaration)
)
return declaration;
// Or, continue. // Or, continue.
} }
} }

View File

@ -35,9 +35,11 @@ namespace solidity
GlobalContext::GlobalContext(): GlobalContext::GlobalContext():
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("abi", make_shared<MagicType>(MagicType::Kind::ABI)),
make_shared<MagicVariableDeclaration>("addmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("addmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)),
make_shared<MagicVariableDeclaration>("blockhash", make_shared<FunctionType>(strings{"uint256"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)),
make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)),
make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
@ -50,7 +52,9 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("mulmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("mulmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)), make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),

View File

@ -47,7 +47,9 @@ NameAndTypeResolver::NameAndTypeResolver(
if (!m_scopes[nullptr]) if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer()); m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals) for (Declaration const* declaration: _globals)
m_scopes[nullptr]->registerDeclaration(*declaration); {
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
}
} }
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope) bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
@ -202,8 +204,9 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert( solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) || dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) || dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration), dynamic_cast<VariableDeclaration const*>(declaration) ||
"Found overloading involving something not a function or a variable." dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
); );
FunctionTypePointer functionType { declaration->functionType(false) }; FunctionTypePointer functionType { declaration->functionType(false) };

View File

@ -21,6 +21,8 @@
#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/ErrorReporter.h>
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <libdevcore/Algorithms.h>
#include <boost/range/adaptor/map.hpp> #include <boost/range/adaptor/map.hpp>
#include <memory> #include <memory>
@ -47,7 +49,7 @@ void PostTypeChecker::endVisit(ContractDefinition const&)
{ {
solAssert(!m_currentConstVariable, ""); solAssert(!m_currentConstVariable, "");
for (auto declaration: m_constVariables) for (auto declaration: m_constVariables)
if (auto identifier = findCycle(declaration)) if (auto identifier = findCycle(*declaration))
m_errorReporter.typeError( m_errorReporter.typeError(
declaration->location(), declaration->location(),
"The value of the constant " + declaration->name() + "The value of the constant " + declaration->name() +
@ -87,20 +89,24 @@ bool PostTypeChecker::visit(Identifier const& _identifier)
return true; return true;
} }
VariableDeclaration const* PostTypeChecker::findCycle( VariableDeclaration const* PostTypeChecker::findCycle(VariableDeclaration const& _startingFrom)
VariableDeclaration const* _startingFrom,
set<VariableDeclaration const*> const& _seen
)
{ {
if (_seen.count(_startingFrom)) auto visitor = [&](VariableDeclaration const& _variable, CycleDetector<VariableDeclaration>& _cycleDetector)
return _startingFrom;
else if (m_constVariableDependencies.count(_startingFrom))
{ {
set<VariableDeclaration const*> seen(_seen); // Iterating through the dependencies needs to be deterministic and thus cannot
seen.insert(_startingFrom); // depend on the memory layout.
for (auto v: m_constVariableDependencies[_startingFrom]) // Because of that, we sort by AST node id.
if (findCycle(v, seen)) vector<VariableDeclaration const*> dependencies(
return v; m_constVariableDependencies[&_variable].begin(),
} m_constVariableDependencies[&_variable].end()
return nullptr; );
sort(dependencies.begin(), dependencies.end(), [](VariableDeclaration const* _a, VariableDeclaration const* _b) -> bool
{
return _a->id() < _b->id();
});
for (auto v: dependencies)
if (_cycleDetector.run(*v))
return;
};
return CycleDetector<VariableDeclaration>(visitor).run(_startingFrom);
} }

View File

@ -55,10 +55,7 @@ private:
virtual bool visit(Identifier const& _identifier) override; virtual bool visit(Identifier const& _identifier) override;
VariableDeclaration const* findCycle( VariableDeclaration const* findCycle(VariableDeclaration const& _startingFrom);
VariableDeclaration const* _startingFrom,
std::set<VariableDeclaration const*> const& _seen = std::set<VariableDeclaration const*>{}
);
ErrorReporter& m_errorReporter; ErrorReporter& m_errorReporter;

View File

@ -21,6 +21,7 @@
*/ */
#include <libsolidity/analysis/StaticAnalyzer.h> #include <libsolidity/analysis/StaticAnalyzer.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h> #include <libsolidity/interface/ErrorReporter.h>
#include <memory> #include <memory>
@ -50,6 +51,16 @@ void StaticAnalyzer::endVisit(ContractDefinition const&)
bool StaticAnalyzer::visit(FunctionDefinition const& _function) bool StaticAnalyzer::visit(FunctionDefinition const& _function)
{ {
const bool isInterface = m_currentContract->contractKind() == ContractDefinition::ContractKind::Interface;
if (_function.noVisibilitySpecified())
m_errorReporter.warning(
_function.location(),
"No visibility specified. Defaulting to \"" +
Declaration::visibilityToString(_function.visibility()) +
"\". " +
(isInterface ? "In interfaces it defaults to external." : "")
);
if (_function.isImplemented()) if (_function.isImplemented())
m_currentFunction = &_function; m_currentFunction = &_function;
else else
@ -68,13 +79,13 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount) for (auto const& var: m_localVarUseCount)
if (var.second == 0) if (var.second == 0)
{ {
if (var.first->isCallableParameter()) if (var.first.second->isCallableParameter())
m_errorReporter.warning( m_errorReporter.warning(
var.first->location(), var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning." "Unused function parameter. Remove or comment out the variable name to silence this warning."
); );
else else
m_errorReporter.warning(var.first->location(), "Unused local variable."); m_errorReporter.warning(var.first.second->location(), "Unused local variable.");
} }
m_localVarUseCount.clear(); m_localVarUseCount.clear();
@ -87,7 +98,7 @@ bool StaticAnalyzer::visit(Identifier const& _identifier)
{ {
solAssert(!var->name().empty(), ""); solAssert(!var->name().empty(), "");
if (var->isLocalVariable()) if (var->isLocalVariable())
m_localVarUseCount[var] += 1; m_localVarUseCount[make_pair(var->id(), var)] += 1;
} }
return true; return true;
} }
@ -99,7 +110,7 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
solAssert(_variable.isLocalVariable(), ""); solAssert(_variable.isLocalVariable(), "");
if (_variable.name() != "") if (_variable.name() != "")
// This is not a no-op, the entry might pre-exist. // This is not a no-op, the entry might pre-exist.
m_localVarUseCount[&_variable] += 0; m_localVarUseCount[make_pair(_variable.id(), &_variable)] += 0;
} }
else if (_variable.isStateVariable()) else if (_variable.isStateVariable())
{ {
@ -122,7 +133,7 @@ bool StaticAnalyzer::visit(Return const& _return)
if (m_currentFunction && _return.expression()) if (m_currentFunction && _return.expression())
for (auto const& var: m_currentFunction->returnParameters()) for (auto const& var: m_currentFunction->returnParameters())
if (!var->name().empty()) if (!var->name().empty())
m_localVarUseCount[var.get()] += 1; m_localVarUseCount[make_pair(var->id(), var.get())] += 1;
return true; return true;
} }
@ -142,6 +153,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); bool const v050 = m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
{
if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas") if (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
{ {
if (v050) if (v050)
@ -155,6 +167,20 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
"\"msg.gas\" has been deprecated in favor of \"gasleft()\"" "\"msg.gas\" has been deprecated in favor of \"gasleft()\""
); );
} }
if (type->kind() == MagicType::Kind::Block && _memberAccess.memberName() == "blockhash")
{
if (v050)
m_errorReporter.typeError(
_memberAccess.location(),
"\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
);
else
m_errorReporter.warning(
_memberAccess.location(),
"\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
);
}
}
if (m_nonPayablePublic && !m_library) if (m_nonPayablePublic && !m_library)
if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get())) if (MagicType const* type = dynamic_cast<MagicType const*>(_memberAccess.expression().annotation().type.get()))
@ -180,10 +206,32 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
); );
} }
if (m_constructor && m_currentContract) if (m_constructor)
if (ContractType const* type = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type.get())) {
if (type->contractDefinition() == *m_currentContract) auto const* expr = &_memberAccess.expression();
m_errorReporter.warning(_memberAccess.location(), "\"this\" used in constructor."); while(expr)
{
if (auto id = dynamic_cast<Identifier const*>(expr))
{
if (id->name() == "this")
m_errorReporter.warning(
id->location(),
"\"this\" used in constructor. "
"Note that external functions of a contract "
"cannot be called while it is being constructed.");
break;
}
else if (auto tuple = dynamic_cast<TupleExpression const*>(expr))
{
if (tuple->components().size() == 1)
expr = tuple->components().front().get();
else
break;
}
else
break;
}
}
return true; return true;
} }
@ -199,13 +247,54 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
{ {
solAssert(!var->name().empty(), ""); solAssert(!var->name().empty(), "");
if (var->isLocalVariable()) if (var->isLocalVariable())
m_localVarUseCount[var] += 1; m_localVarUseCount[make_pair(var->id(), var)] += 1;
} }
} }
return true; return true;
} }
bool StaticAnalyzer::visit(BinaryOperation const& _operation)
{
if (
_operation.rightExpression().annotation().isPure &&
(_operation.getOperator() == Token::Div || _operation.getOperator() == Token::Mod)
)
if (auto rhs = dynamic_pointer_cast<RationalNumberType const>(
ConstantEvaluator(m_errorReporter).evaluate(_operation.rightExpression())
))
if (rhs->isZero())
m_errorReporter.typeError(
_operation.location(),
(_operation.getOperator() == Token::Div) ? "Division by zero." : "Modulo zero."
);
return true;
}
bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
{
if (_functionCall.annotation().kind == FunctionCallKind::FunctionCall)
{
auto functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
solAssert(functionType, "");
if (functionType->kind() == FunctionType::Kind::AddMod || functionType->kind() == FunctionType::Kind::MulMod)
{
solAssert(_functionCall.arguments().size() == 3, "");
if (_functionCall.arguments()[2]->annotation().isPure)
if (auto lastArg = dynamic_pointer_cast<RationalNumberType const>(
ConstantEvaluator(m_errorReporter).evaluate(*(_functionCall.arguments())[2])
))
if (lastArg->isZero())
m_errorReporter.typeError(
_functionCall.location(),
"Arithmetic modulo zero."
);
}
}
return true;
}
bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen) bigint StaticAnalyzer::structureSizeEstimate(Type const& _type, set<StructDefinition const*>& _structsSeen)
{ {
switch (_type.category()) switch (_type.category())

View File

@ -64,6 +64,8 @@ private:
virtual bool visit(Return const& _return) override; virtual bool visit(Return const& _return) override;
virtual bool visit(MemberAccess const& _memberAccess) override; virtual bool visit(MemberAccess const& _memberAccess) override;
virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override;
virtual bool visit(BinaryOperation const& _operation) override;
virtual bool visit(FunctionCall const& _functionCall) override;
/// @returns the size of this type in storage, including all sub-types. /// @returns the size of this type in storage, including all sub-types.
static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen); static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen);
@ -77,7 +79,9 @@ private:
bool m_nonPayablePublic = false; bool m_nonPayablePublic = false;
/// Number of uses of each (named) local variable in a function, counter is initialized with zero. /// Number of uses of each (named) local variable in a function, counter is initialized with zero.
std::map<VariableDeclaration const*, int> m_localVarUseCount; /// Pairs of AST ids and pointers are used as keys to ensure a deterministic order
/// when traversing.
std::map<std::pair<size_t, VariableDeclaration const*>, int> m_localVarUseCount;
FunctionDefinition const* m_currentFunction = nullptr; FunctionDefinition const* m_currentFunction = nullptr;

View File

@ -214,18 +214,31 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{ {
bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050); bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (_function.noVisibilitySpecified()) if (v050 && _function.noVisibilitySpecified())
m_errorReporter.syntaxError(_function.location(), "No visibility specified.");
if (_function.isOldStyleConstructor())
{ {
if (v050) if (v050)
m_errorReporter.syntaxError(_function.location(), "No visibility specified."); m_errorReporter.syntaxError(
_function.location(),
"Functions are not allowed to have the same name as the contract. "
"If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it."
);
else else
m_errorReporter.warning( m_errorReporter.warning(
_function.location(), _function.location(),
"No visibility specified. Defaulting to \"" + "Defining constructors as functions with the same name as the contract is deprecated. "
Declaration::visibilityToString(_function.visibility()) + "Use \"constructor(...) { ... }\" instead."
"\"."
); );
} }
if (!_function.isImplemented() && !_function.modifiers().empty())
{
if (v050)
m_errorReporter.syntaxError(_function.location(), "Functions without implementation cannot have modifiers.");
else
m_errorReporter.warning( _function.location(), "Modifiers of functions without implementation are ignored." );
}
return true; return true;
} }
@ -255,3 +268,17 @@ bool SyntaxChecker::visit(VariableDeclaration const& _declaration)
} }
return true; return true;
} }
bool SyntaxChecker::visit(StructDefinition const& _struct)
{
bool const v050 = m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
if (_struct.members().empty())
{
if (v050)
m_errorReporter.syntaxError(_struct.location(), "Defining empty structs is disallowed.");
else
m_errorReporter.warning(_struct.location(), "Defining empty structs is deprecated.");
}
return true;
}

View File

@ -71,6 +71,8 @@ private:
virtual bool visit(VariableDeclaration const& _declaration) override; virtual bool visit(VariableDeclaration const& _declaration) override;
virtual bool visit(StructDefinition const& _struct) override;
ErrorReporter& m_errorReporter; ErrorReporter& m_errorReporter;
/// Flag that indicates whether a function modifier actually contains '_'. /// Flag that indicates whether a function modifier actually contains '_'.

View File

@ -60,17 +60,7 @@ bool typeSupportedByOldABIEncoder(Type const& _type)
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract) bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
{ {
try _contract.accept(*this);
{
_contract.accept(*this);
}
catch (FatalError const&)
{
// We got a fatal error which required to stop further type checking, but we can
// continue normally from here.
if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
}
return Error::containsOnlyWarnings(m_errorReporter.errors()); return Error::containsOnlyWarnings(m_errorReporter.errors());
} }
@ -101,7 +91,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractDuplicateEvents(_contract); checkContractDuplicateEvents(_contract);
checkContractIllegalOverrides(_contract); checkContractIllegalOverrides(_contract);
checkContractAbstractFunctions(_contract); checkContractAbstractFunctions(_contract);
checkContractAbstractConstructors(_contract); checkContractBaseConstructorArguments(_contract);
FunctionDefinition const* function = _contract.constructor(); FunctionDefinition const* function = _contract.constructor();
if (function) if (function)
@ -291,42 +281,108 @@ void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _cont
} }
} }
void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _contract) void TypeChecker::checkContractBaseConstructorArguments(ContractDefinition const& _contract)
{ {
set<ContractDefinition const*> argumentsNeeded; bool const v050 = _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
// check that we get arguments for all base constructors that need it.
// If not mark the contract as abstract (not fully implemented)
vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts; vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
for (ContractDefinition const* contract: bases)
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
argumentsNeeded.insert(contract);
// Determine the arguments that are used for the base constructors.
for (ContractDefinition const* contract: bases) for (ContractDefinition const* contract: bases)
{ {
if (FunctionDefinition const* constructor = contract->constructor()) if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers()) for (auto const& modifier: constructor->modifiers())
{ {
auto baseContract = dynamic_cast<ContractDefinition const*>( auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(*modifier->name()));
&dereference(*modifier->name()) if (modifier->arguments())
); {
if (baseContract) if (baseContract && baseContract->constructor())
argumentsNeeded.erase(baseContract); annotateBaseConstructorArguments(_contract, baseContract->constructor(), modifier.get());
}
else
{
if (v050)
m_errorReporter.declarationError(
modifier->location(),
"Modifier-style base constructor call without arguments."
);
else
m_errorReporter.warning(
modifier->location(),
"Modifier-style base constructor call without arguments."
);
}
} }
for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts()) for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts())
{ {
auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name())); auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name()));
solAssert(baseContract, ""); solAssert(baseContract, "");
if (!base->arguments().empty())
argumentsNeeded.erase(baseContract); if (baseContract->constructor() && base->arguments() && !base->arguments()->empty())
annotateBaseConstructorArguments(_contract, baseContract->constructor(), base.get());
} }
} }
if (!argumentsNeeded.empty())
for (ContractDefinition const* contract: argumentsNeeded) // check that we get arguments for all base constructors that need it.
_contract.annotation().unimplementedFunctions.push_back(contract->constructor()); // If not mark the contract as abstract (not fully implemented)
for (ContractDefinition const* contract: bases)
if (FunctionDefinition const* constructor = contract->constructor())
if (contract != &_contract && !constructor->parameters().empty())
if (!_contract.annotation().baseConstructorArguments.count(constructor))
_contract.annotation().unimplementedFunctions.push_back(constructor);
}
void TypeChecker::annotateBaseConstructorArguments(
ContractDefinition const& _currentContract,
FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode
)
{
bool const v050 = _currentContract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
solAssert(_baseConstructor, "");
solAssert(_argumentNode, "");
auto insertionResult = _currentContract.annotation().baseConstructorArguments.insert(
std::make_pair(_baseConstructor, _argumentNode)
);
if (!insertionResult.second)
{
ASTNode const* previousNode = insertionResult.first->second;
SourceLocation const* mainLocation = nullptr;
SecondarySourceLocation ssl;
if (
_currentContract.location().contains(previousNode->location()) ||
_currentContract.location().contains(_argumentNode->location())
)
{
mainLocation = &previousNode->location();
ssl.append("Second constructor call is here:", _argumentNode->location());
}
else
{
mainLocation = &_currentContract.location();
ssl.append("First constructor call is here: ", _argumentNode->location());
ssl.append("Second constructor call is here: ", previousNode->location());
}
if (v050)
m_errorReporter.declarationError(
*mainLocation,
ssl,
"Base constructor arguments given twice."
);
else
m_errorReporter.warning(
*mainLocation,
"Base constructor arguments given twice.",
ssl
);
}
} }
void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract)
@ -378,7 +434,16 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
function.annotation().superFunction = &super; function.annotation().superFunction = &super;
if (function.visibility() != super.visibility()) if (function.visibility() != super.visibility())
{
// visibility is enforced to be external in interfaces, but a contract can override that with public
if (
super.inContractKind() == ContractDefinition::ContractKind::Interface &&
function.inContractKind() != ContractDefinition::ContractKind::Interface &&
function.visibility() == FunctionDefinition::Visibility::Public
)
return;
overrideError(function, super, "Overriding function visibility differs."); overrideError(function, super, "Overriding function visibility differs.");
}
else if (function.stateMutability() != super.stateMutability()) else if (function.stateMutability() != super.stateMutability())
overrideError( overrideError(
@ -497,30 +562,46 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
// Interfaces do not have constructors, so there are zero parameters. // Interfaces do not have constructors, so there are zero parameters.
parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
if (!arguments.empty() && parameterTypes.size() != arguments.size()) if (arguments)
{ {
m_errorReporter.typeError( bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
"."
);
return;
}
for (size_t i = 0; i < arguments.size(); ++i) if (parameterTypes.size() != arguments->size())
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) {
m_errorReporter.typeError( if (arguments->size() == 0 && !v050)
arguments[i]->location(), m_errorReporter.warning(
"Invalid type for argument in constructor call. " _inheritance.location(),
"Invalid implicit conversion from " + "Wrong argument count for constructor call: " +
type(*arguments[i])->toString() + toString(arguments->size()) +
" to " + " arguments given but expected " +
parameterTypes[i]->toString() + toString(parameterTypes.size()) +
" requested." "."
); );
else
{
m_errorReporter.typeError(
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments->size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
"."
);
return;
}
}
for (size_t i = 0; i < arguments->size(); ++i)
if (!type(*(*arguments)[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
m_errorReporter.typeError(
(*arguments)[i]->location(),
"Invalid type for argument in constructor call. "
"Invalid implicit conversion from " +
type(*(*arguments)[i])->toString() +
" to " +
parameterTypes[i]->toString() +
" requested."
);
}
} }
void TypeChecker::endVisit(UsingForDirective const& _usingFor) void TypeChecker::endVisit(UsingForDirective const& _usingFor)
@ -731,7 +812,8 @@ void TypeChecker::visitManually(
vector<ContractDefinition const*> const& _bases vector<ContractDefinition const*> const& _bases
) )
{ {
std::vector<ASTPointer<Expression>> const& arguments = _modifier.arguments(); std::vector<ASTPointer<Expression>> const& arguments =
_modifier.arguments() ? *_modifier.arguments() : std::vector<ASTPointer<Expression>>();
for (ASTPointer<Expression> const& argument: arguments) for (ASTPointer<Expression> const& argument: arguments)
argument->accept(*this); argument->accept(*this);
_modifier.name()->accept(*this); _modifier.name()->accept(*this);
@ -769,7 +851,7 @@ void TypeChecker::visitManually(
); );
return; return;
} }
for (size_t i = 0; i < _modifier.arguments().size(); ++i) for (size_t i = 0; i < arguments.size(); ++i)
if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i]))) if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i])))
m_errorReporter.typeError( m_errorReporter.typeError(
arguments[i]->location(), arguments[i]->location(),
@ -1551,16 +1633,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
_functionCall.expression().annotation().isPure && _functionCall.expression().annotation().isPure &&
functionType->isPure(); functionType->isPure();
bool allowDynamicTypes = m_evmVersion.supportsReturndata();
if (!functionType) if (!functionType)
{ {
m_errorReporter.typeError(_functionCall.location(), "Type is not callable"); m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
_functionCall.annotation().type = make_shared<TupleType>(); _functionCall.annotation().type = make_shared<TupleType>();
return false; return false;
} }
else if (functionType->returnParameterTypes().size() == 1)
_functionCall.annotation().type = functionType->returnParameterTypes().front(); auto returnTypes =
allowDynamicTypes ?
functionType->returnParameterTypes() :
functionType->returnParameterTypesWithoutDynamicTypes();
if (returnTypes.size() == 1)
_functionCall.annotation().type = returnTypes.front();
else else
_functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes()); _functionCall.annotation().type = make_shared<TupleType>(returnTypes);
if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression())) if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{ {
@ -1600,7 +1688,19 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
} }
} }
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size()) if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
{
solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
m_errorReporter.typeError(
_functionCall.location(),
"Need at least " +
toString(parameterTypes.size()) +
" arguments for function call, but provided only " +
toString(arguments.size()) +
"."
);
}
else if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
{ {
bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall; bool isStructConstructorCall = _functionCall.annotation().kind == FunctionCallKind::StructConstructorCall;
@ -1623,15 +1723,36 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
} }
else if (isPositionalCall) else if (isPositionalCall)
{ {
// call by positional arguments bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
for (size_t i = 0; i < arguments.size(); ++i) for (size_t i = 0; i < arguments.size(); ++i)
{ {
auto const& argType = type(*arguments[i]); auto const& argType = type(*arguments[i]);
if (functionType->takesArbitraryParameters()) if (functionType->takesArbitraryParameters() && i >= parameterTypes.size())
{ {
bool errored = false;
if (auto t = dynamic_cast<RationalNumberType const*>(argType.get())) if (auto t = dynamic_cast<RationalNumberType const*>(argType.get()))
if (!t->mobileType()) if (!t->mobileType())
{
m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero)."); m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
errored = true;
}
if (!errored)
{
TypePointer encodingType;
if (
argType->mobileType() &&
argType->mobileType()->interfaceType(false) &&
argType->mobileType()->interfaceType(false)->encodingType()
)
encodingType = argType->mobileType()->interfaceType(false)->encodingType();
// Structs are fine as long as ABIV2 is activated and we do not do packed encoding.
if (!encodingType || (
dynamic_cast<StructType const*>(encodingType.get()) &&
!(abiEncodeV2 && functionType->padArguments())
))
m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded.");
}
} }
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
m_errorReporter.typeError( m_errorReporter.typeError(
@ -1867,7 +1988,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
m_errorReporter.warning( m_errorReporter.warning(
_memberAccess.location(), _memberAccess.location(),
"Using contract member \"" + memberName +"\" inherited from the address type is deprecated." + "Using contract member \"" + memberName +"\" inherited from the address type is deprecated." +
" Convert the contract to \"address\" type to access the member." " Convert the contract to \"address\" type to access the member,"
" for example use \"address(contract)." + memberName + "\" instead."
); );
} }
@ -2025,10 +2147,9 @@ bool TypeChecker::visit(Identifier const& _identifier)
for (Declaration const* declaration: annotation.overloadedDeclarations) for (Declaration const* declaration: annotation.overloadedDeclarations)
{ {
TypePointer function = declaration->type(); FunctionTypePointer functionType = declaration->functionType(true);
solAssert(!!function, "Requested type not present."); solAssert(!!functionType, "Requested type not present.");
auto const* functionType = dynamic_cast<FunctionType const*>(function.get()); if (functionType->canTakeArguments(*annotation.argumentTypes))
if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration); candidates.push_back(declaration);
} }
if (candidates.empty()) if (candidates.empty())

View File

@ -73,7 +73,12 @@ private:
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super); void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message); void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
void checkContractAbstractFunctions(ContractDefinition const& _contract); void checkContractAbstractFunctions(ContractDefinition const& _contract);
void checkContractAbstractConstructors(ContractDefinition const& _contract); void checkContractBaseConstructorArguments(ContractDefinition const& _contract);
void annotateBaseConstructorArguments(
ContractDefinition const& _currentContract,
FunctionDefinition const* _baseConstructor,
ASTNode const* _argumentNode
);
/// Checks that different functions with external visibility end up having different /// Checks that different functions with external visibility end up having different
/// external argument types (i.e. different signature). /// external argument types (i.e. different signature).
void checkContractExternalTypeClashes(ContractDefinition const& _contract); void checkContractExternalTypeClashes(ContractDefinition const& _contract);

View File

@ -305,10 +305,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
mutability = StateMutability::View; mutability = StateMutability::View;
break; break;
case Type::Category::Magic: case Type::Category::Magic:
{
// we can ignore the kind of magic and only look at the name of the member // we can ignore the kind of magic and only look at the name of the member
if (member != "data" && member != "sig" && member != "blockhash") set<string> static const pureMembers{
"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash"
};
if (!pureMembers.count(member))
mutability = StateMutability::View; mutability = StateMutability::View;
break; break;
}
case Type::Category::Struct: case Type::Category::Struct:
{ {
if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage)) if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))

View File

@ -290,7 +290,14 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const
return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation); return dynamic_cast<TypeDeclarationAnnotation&>(*m_annotation);
} }
shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
{
auto contractDef = dynamic_cast<ContractDefinition const*>(scope());
solAssert(contractDef, "Enclosing Scope of FunctionDefinition was not set.");
return contractDef->contractKind();
}
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
{ {
@ -331,6 +338,7 @@ shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
TypePointer FunctionDefinition::type() const TypePointer FunctionDefinition::type() const
{ {
solAssert(visibility() != Declaration::Visibility::External, "");
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
} }
@ -372,7 +380,7 @@ TypePointer EventDefinition::type() const
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
} }
std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const FunctionTypePointer EventDefinition::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
@ -477,7 +485,7 @@ TypePointer VariableDeclaration::type() const
return annotation().type; return annotation().type;
} }
shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
return {}; return {};

View File

@ -203,6 +203,7 @@ public:
bool isPublic() const { return visibility() >= Visibility::Public; } bool isPublic() const { return visibility() >= Visibility::Public; }
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; } virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; } bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); } std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
@ -217,7 +218,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const { return {}; } virtual FunctionTypePointer functionType(bool /*_internal*/) const { return {}; }
protected: protected:
virtual Visibility defaultVisibility() const { return Visibility::Public; } virtual Visibility defaultVisibility() const { return Visibility::Public; }
@ -424,19 +425,22 @@ public:
InheritanceSpecifier( InheritanceSpecifier(
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<UserDefinedTypeName> const& _baseName, ASTPointer<UserDefinedTypeName> const& _baseName,
std::vector<ASTPointer<Expression>> _arguments std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
): ):
ASTNode(_location), m_baseName(_baseName), m_arguments(_arguments) {} ASTNode(_location), m_baseName(_baseName), m_arguments(std::move(_arguments)) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
UserDefinedTypeName const& name() const { return *m_baseName; } UserDefinedTypeName const& name() const { return *m_baseName; }
std::vector<ASTPointer<Expression>> const& arguments() const { return m_arguments; } // Returns nullptr if no argument list was given (``C``).
// If an argument list is given (``C(...)``), the arguments are returned
// as a vector of expressions. Note that this vector can be empty (``C()``).
std::vector<ASTPointer<Expression>> const* arguments() const { return m_arguments.get(); }
private: private:
ASTPointer<UserDefinedTypeName> m_baseName; ASTPointer<UserDefinedTypeName> m_baseName;
std::vector<ASTPointer<Expression>> m_arguments; std::unique_ptr<std::vector<ASTPointer<Expression>>> m_arguments;
}; };
/** /**
@ -606,7 +610,8 @@ public:
StateMutability stateMutability() const { return m_stateMutability; } StateMutability stateMutability() const { return m_stateMutability; }
bool isConstructor() const { return m_isConstructor; } bool isConstructor() const { return m_isConstructor; }
bool isFallback() const { return name().empty(); } bool isOldStyleConstructor() const { return m_isConstructor && !name().empty(); }
bool isFallback() const { return !m_isConstructor && name().empty(); }
bool isPayable() const { return m_stateMutability == StateMutability::Payable; } bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; } std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); } std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
@ -623,11 +628,13 @@ public:
/// arguments separated by commas all enclosed in parentheses without any spaces. /// arguments separated by commas all enclosed in parentheses without any spaces.
std::string externalSignature() const; std::string externalSignature() const;
ContractDefinition::ContractKind inContractKind() const;
virtual TypePointer type() const override; virtual TypePointer type() const override;
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual FunctionDefinitionAnnotation& annotation() const override; virtual FunctionDefinitionAnnotation& annotation() const override;
@ -696,7 +703,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual VariableDeclarationAnnotation& annotation() const override; virtual VariableDeclarationAnnotation& annotation() const override;
@ -755,19 +762,22 @@ public:
ModifierInvocation( ModifierInvocation(
SourceLocation const& _location, SourceLocation const& _location,
ASTPointer<Identifier> const& _name, ASTPointer<Identifier> const& _name,
std::vector<ASTPointer<Expression>> _arguments std::unique_ptr<std::vector<ASTPointer<Expression>>> _arguments
): ):
ASTNode(_location), m_modifierName(_name), m_arguments(_arguments) {} ASTNode(_location), m_modifierName(_name), m_arguments(std::move(_arguments)) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
ASTPointer<Identifier> const& name() const { return m_modifierName; } ASTPointer<Identifier> const& name() const { return m_modifierName; }
std::vector<ASTPointer<Expression>> const& arguments() const { return m_arguments; } // Returns nullptr if no argument list was given (``mod``).
// If an argument list is given (``mod(...)``), the arguments are returned
// as a vector of expressions. Note that this vector can be empty (``mod()``).
std::vector<ASTPointer<Expression>> const* arguments() const { return m_arguments.get(); }
private: private:
ASTPointer<Identifier> m_modifierName; ASTPointer<Identifier> m_modifierName;
std::vector<ASTPointer<Expression>> m_arguments; std::unique_ptr<std::vector<ASTPointer<Expression>>> m_arguments;
}; };
/** /**
@ -795,7 +805,7 @@ public:
bool isAnonymous() const { return m_anonymous; } bool isAnonymous() const { return m_anonymous; }
virtual TypePointer type() const override; virtual TypePointer type() const override;
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual EventDefinitionAnnotation& annotation() const override; virtual EventDefinitionAnnotation& annotation() const override;
@ -821,6 +831,11 @@ public:
solAssert(false, "MagicVariableDeclaration used inside real AST."); solAssert(false, "MagicVariableDeclaration used inside real AST.");
} }
virtual FunctionTypePointer functionType(bool) const override
{
solAssert(m_type->category() == Type::Category::Function, "");
return std::dynamic_pointer_cast<FunctionType const>(m_type);
}
virtual TypePointer type() const override { return m_type; } virtual TypePointer type() const override { return m_type; }
private: private:

View File

@ -90,6 +90,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
/// List of contracts this contract creates, i.e. which need to be compiled first. /// List of contracts this contract creates, i.e. which need to be compiled first.
/// Also includes all contracts from @a linearizedBaseContracts. /// Also includes all contracts from @a linearizedBaseContracts.
std::set<ContractDefinition const*> contractDependencies; std::set<ContractDefinition const*> contractDependencies;
/// Mapping containing the nodes that define the arguments for base constructors.
/// These can either be inheritance specifiers or modifier invocations.
std::map<FunctionDefinition const*, ASTNode const*> baseConstructorArguments;
}; };
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation

View File

@ -134,10 +134,10 @@ string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePat
return boost::algorithm::join(_namePath, "."); return boost::algorithm::join(_namePath, ".");
} }
Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp) Json::Value ASTJsonConverter::typePointerToJson(TypePointer _tp, bool _short)
{ {
Json::Value typeDescriptions(Json::objectValue); Json::Value typeDescriptions(Json::objectValue);
typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString()) : Json::nullValue; typeDescriptions["typeString"] = _tp ? Json::Value(_tp->toString(_short)) : Json::nullValue;
typeDescriptions["typeIdentifier"] = _tp ? Json::Value(_tp->identifier()) : Json::nullValue; typeDescriptions["typeIdentifier"] = _tp ? Json::Value(_tp->identifier()) : Json::nullValue;
return typeDescriptions; return typeDescriptions;
@ -268,7 +268,7 @@ bool ASTJsonConverter::visit(InheritanceSpecifier const& _node)
{ {
setJsonNode(_node, "InheritanceSpecifier", { setJsonNode(_node, "InheritanceSpecifier", {
make_pair("baseName", toJson(_node.name())), make_pair("baseName", toJson(_node.name())),
make_pair("arguments", toJson(_node.arguments())) make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue)
}); });
return false; return false;
} }
@ -354,7 +354,7 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
make_pair("visibility", Declaration::visibilityToString(_node.visibility())), make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue), make_pair("value", _node.value() ? toJson(*_node.value()) : Json::nullValue),
make_pair("scope", idOrNull(_node.scope())), make_pair("scope", idOrNull(_node.scope())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}; };
if (m_inEvent) if (m_inEvent)
attributes.push_back(make_pair("indexed", _node.isIndexed())); attributes.push_back(make_pair("indexed", _node.isIndexed()));
@ -378,7 +378,7 @@ bool ASTJsonConverter::visit(ModifierInvocation const& _node)
{ {
setJsonNode(_node, "ModifierInvocation", { setJsonNode(_node, "ModifierInvocation", {
make_pair("modifierName", toJson(*_node.name())), make_pair("modifierName", toJson(*_node.name())),
make_pair("arguments", toJson(_node.arguments())) make_pair("arguments", _node.arguments() ? toJson(*_node.arguments()) : Json::nullValue)
}); });
return false; return false;
} }
@ -399,7 +399,7 @@ bool ASTJsonConverter::visit(ElementaryTypeName const& _node)
{ {
setJsonNode(_node, "ElementaryTypeName", { setJsonNode(_node, "ElementaryTypeName", {
make_pair("name", _node.typeName().toString()), make_pair("name", _node.typeName().toString()),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}); });
return false; return false;
} }
@ -410,7 +410,7 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node)
make_pair("name", namePathToString(_node.namePath())), make_pair("name", namePathToString(_node.namePath())),
make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)), make_pair("referencedDeclaration", idOrNull(_node.annotation().referencedDeclaration)),
make_pair("contractScope", idOrNull(_node.annotation().contractScope)), make_pair("contractScope", idOrNull(_node.annotation().contractScope)),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}); });
return false; return false;
} }
@ -425,7 +425,7 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node)
make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View), make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
make_pair("parameterTypes", toJson(*_node.parameterTypeList())), make_pair("parameterTypes", toJson(*_node.parameterTypeList())),
make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())), make_pair("returnParameterTypes", toJson(*_node.returnParameterTypeList())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}); });
return false; return false;
} }
@ -435,7 +435,7 @@ bool ASTJsonConverter::visit(Mapping const& _node)
setJsonNode(_node, "Mapping", { setJsonNode(_node, "Mapping", {
make_pair("keyType", toJson(_node.keyType())), make_pair("keyType", toJson(_node.keyType())),
make_pair("valueType", toJson(_node.valueType())), make_pair("valueType", toJson(_node.valueType())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}); });
return false; return false;
} }
@ -445,7 +445,7 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node)
setJsonNode(_node, "ArrayTypeName", { setJsonNode(_node, "ArrayTypeName", {
make_pair("baseType", toJson(_node.baseType())), make_pair("baseType", toJson(_node.baseType())),
make_pair("length", toJsonOrNull(_node.length())), make_pair("length", toJsonOrNull(_node.length())),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type)) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
}); });
return false; return false;
} }

View File

@ -152,7 +152,7 @@ private:
} }
return tmp; return tmp;
} }
static Json::Value typePointerToJson(TypePointer _tp); static Json::Value typePointerToJson(TypePointer _tp, bool _short = false);
static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps); static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
void appendExpressionAttributes( void appendExpressionAttributes(
std::vector<std::pair<std::string, Json::Value>> &_attributes, std::vector<std::pair<std::string, Json::Value>> &_attributes,

View File

@ -94,7 +94,8 @@ void InheritanceSpecifier::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_baseName->accept(_visitor); m_baseName->accept(_visitor);
listAccept(m_arguments, _visitor); if (m_arguments)
listAccept(*m_arguments, _visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
@ -104,7 +105,8 @@ void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_baseName->accept(_visitor); m_baseName->accept(_visitor);
listAccept(m_arguments, _visitor); if (m_arguments)
listAccept(*m_arguments, _visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
@ -262,7 +264,8 @@ void ModifierInvocation::accept(ASTVisitor& _visitor)
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_modifierName->accept(_visitor); m_modifierName->accept(_visitor);
listAccept(m_arguments, _visitor); if (m_arguments)
listAccept(*m_arguments, _visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }
@ -272,7 +275,8 @@ void ModifierInvocation::accept(ASTConstVisitor& _visitor) const
if (_visitor.visit(*this)) if (_visitor.visit(*this))
{ {
m_modifierName->accept(_visitor); m_modifierName->accept(_visitor);
listAccept(m_arguments, _visitor); if (m_arguments)
listAccept(*m_arguments, _visitor);
} }
_visitor.endVisit(*this); _visitor.endVisit(*this);
} }

View File

@ -28,6 +28,7 @@
#include <libdevcore/CommonData.h> #include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h> #include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h> #include <libdevcore/UTF8.h>
#include <libdevcore/Algorithms.h>
#include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
@ -43,6 +44,85 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
namespace
{
unsigned int mostSignificantBit(bigint const& _number)
{
#if BOOST_VERSION < 105500
solAssert(_number > 0, "");
bigint number = _number;
unsigned int result = 0;
while (number != 0)
{
number >>= 1;
++result;
}
return --result;
#else
return boost::multiprecision::msb(_number);
#endif
}
/// Check whether (_base ** _exp) fits into 4096 bits.
bool fitsPrecisionExp(bigint const& _base, bigint const& _exp)
{
if (_base == 0)
return true;
solAssert(_base > 0, "");
size_t const bitsMax = 4096;
unsigned mostSignificantBaseBit = mostSignificantBit(_base);
if (mostSignificantBaseBit == 0) // _base == 1
return true;
if (mostSignificantBaseBit > bitsMax) // _base >= 2 ^ 4096
return false;
bigint bitsNeeded = _exp * (mostSignificantBaseBit + 1);
return bitsNeeded <= bitsMax;
}
/// Checks whether _mantissa * (X ** _exp) fits into 4096 bits,
/// where X is given indirectly via _log2OfBase = log2(X).
bool fitsPrecisionBaseX(
bigint const& _mantissa,
double _log2OfBase,
uint32_t _exp
)
{
if (_mantissa == 0)
return true;
solAssert(_mantissa > 0, "");
size_t const bitsMax = 4096;
unsigned mostSignificantMantissaBit = mostSignificantBit(_mantissa);
if (mostSignificantMantissaBit > bitsMax) // _mantissa >= 2 ^ 4096
return false;
bigint bitsNeeded = mostSignificantMantissaBit + bigint(floor(double(_exp) * _log2OfBase)) + 1;
return bitsNeeded <= bitsMax;
}
/// Checks whether _mantissa * (10 ** _expBase10) fits into 4096 bits.
bool fitsPrecisionBase10(bigint const& _mantissa, uint32_t _expBase10)
{
double const log2Of10AwayFromZero = 3.3219280948873624;
return fitsPrecisionBaseX(_mantissa, log2Of10AwayFromZero, _expBase10);
}
/// Checks whether _mantissa * (2 ** _expBase10) fits into 4096 bits.
bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2)
{
return fitsPrecisionBaseX(_mantissa, 1.0, _expBase2);
}
}
void StorageOffsets::computeOffsets(TypePointers const& _types) void StorageOffsets::computeOffsets(TypePointers const& _types)
{ {
bigint slotOffset = 0; bigint slotOffset = 0;
@ -208,9 +288,9 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
case Token::UInt: case Token::UInt:
return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned); return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned);
case Token::Fixed: case Token::Fixed:
return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed); return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed);
case Token::UFixed: case Token::UFixed:
return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned); return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Byte: case Token::Byte:
return make_shared<FixedBytesType>(1); return make_shared<FixedBytesType>(1);
case Token::Address: case Token::Address:
@ -232,11 +312,22 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
TypePointer Type::fromElementaryTypeName(string const& _name) TypePointer Type::fromElementaryTypeName(string const& _name)
{ {
string name = _name;
DataLocation location = DataLocation::Storage;
if (boost::algorithm::ends_with(name, " memory"))
{
name = name.substr(0, name.length() - 7);
location = DataLocation::Memory;
}
unsigned short firstNum; unsigned short firstNum;
unsigned short secondNum; unsigned short secondNum;
Token::Value token; Token::Value token;
tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(_name); tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(name);
return fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum)); auto t = fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
if (auto* ref = dynamic_cast<ReferenceType const*>(t.get()))
return ref->copyForLocation(location, true);
else
return t;
} }
TypePointer Type::forLiteral(Literal const& _literal) TypePointer Type::forLiteral(Literal const& _literal)
@ -304,7 +395,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
); );
for (FunctionDefinition const* function: library.definedFunctions()) for (FunctionDefinition const* function: library.definedFunctions())
{ {
if (!function->isVisibleInDerivedContracts() || seenFunctions.count(function)) if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
continue; continue;
seenFunctions.insert(function); seenFunctions.insert(function);
FunctionType funType(*function, false); FunctionType funType(*function, false);
@ -327,7 +418,7 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType)) else if (IntegerType const* otherInt = dynamic_cast<decltype(otherInt)>(&_shiftAmountType))
return !otherInt->isAddress(); return !otherInt->isAddress();
else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType)) else if (RationalNumberType const* otherRat = dynamic_cast<decltype(otherRat)>(&_shiftAmountType))
return otherRat->integerType() && !otherRat->integerType()->isSigned(); return !otherRat->isFractional() && otherRat->integerType() && !otherRat->integerType()->isSigned();
else else
return false; return false;
} }
@ -677,31 +768,39 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
} }
else if (expPoint != _literal.value().end()) else if (expPoint != _literal.value().end())
{ {
// parse the exponent // Parse base and exponent. Checks numeric limit.
bigint exp = bigint(string(expPoint + 1, _literal.value().end())); bigint exp = bigint(string(expPoint + 1, _literal.value().end()));
if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min()) if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
return make_tuple(false, rational(0)); return make_tuple(false, rational(0));
// parse the base uint32_t expAbs = bigint(abs(exp)).convert_to<uint32_t>();
tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint)); tuple<bool, rational> base = parseRational(string(_literal.value().begin(), expPoint));
if (!get<0>(base)) if (!get<0>(base))
return make_tuple(false, rational(0)); return make_tuple(false, rational(0));
value = get<1>(base); value = get<1>(base);
if (exp < 0) if (exp < 0)
{ {
exp *= -1; if (!fitsPrecisionBase10(abs(value.denominator()), expAbs))
return make_tuple(false, rational(0));
value /= boost::multiprecision::pow( value /= boost::multiprecision::pow(
bigint(10), bigint(10),
exp.convert_to<int32_t>() expAbs
); );
} }
else else if (exp > 0)
{
if (!fitsPrecisionBase10(abs(value.numerator()), expAbs))
return make_tuple(false, rational(0));
value *= boost::multiprecision::pow( value *= boost::multiprecision::pow(
bigint(10), bigint(10),
exp.convert_to<int32_t>() expAbs
); );
}
} }
else else
{ {
@ -827,10 +926,10 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
{ {
if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint) if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint)
{ {
auto mobile = mobileType(); auto commonType = Type::commonType(shared_from_this(), _other);
if (!mobile) if (!commonType)
return TypePointer(); return TypePointer();
return mobile->binaryOperatorResult(_operator, _other); return commonType->binaryOperatorResult(_operator, _other);
} }
else if (_other->category() != category()) else if (_other->category() != category())
return TypePointer(); return TypePointer();
@ -900,16 +999,49 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
using boost::multiprecision::pow; using boost::multiprecision::pow;
if (other.isFractional()) if (other.isFractional())
return TypePointer(); return TypePointer();
else if (abs(other.m_value) > numeric_limits<uint32_t>::max()) solAssert(other.m_value.denominator() == 1, "");
return TypePointer(); // This will need too much memory to represent. bigint const& exp = other.m_value.numerator();
uint32_t exponent = abs(other.m_value).numerator().convert_to<uint32_t>();
bigint numerator = pow(m_value.numerator(), exponent); // x ** 0 = 1
bigint denominator = pow(m_value.denominator(), exponent); // for 0, 1 and -1 the size of the exponent doesn't have to be restricted
if (other.m_value >= 0) if (exp == 0)
value = rational(numerator, denominator); value = 1;
else if (m_value.numerator() == 0 || m_value == 1)
value = m_value;
else if (m_value == -1)
{
bigint isOdd = abs(exp) & bigint(1);
value = 1 - 2 * isOdd.convert_to<int>();
}
else else
// invert {
value = rational(denominator, numerator); if (abs(exp) > numeric_limits<uint32_t>::max())
return TypePointer(); // This will need too much memory to represent.
uint32_t absExp = bigint(abs(exp)).convert_to<uint32_t>();
// Limit size to 4096 bits
if (!fitsPrecisionExp(abs(m_value.numerator()), absExp) || !fitsPrecisionExp(abs(m_value.denominator()), absExp))
return TypePointer();
static auto const optimizedPow = [](bigint const& _base, uint32_t _exponent) -> bigint {
if (_base == 1)
return 1;
else if (_base == -1)
return 1 - 2 * int(_exponent & 1);
else
return pow(_base, _exponent);
};
bigint numerator = optimizedPow(m_value.numerator(), absExp);
bigint denominator = optimizedPow(m_value.denominator(), absExp);
if (exp >= 0)
value = rational(numerator, denominator);
else
// invert
value = rational(denominator, numerator);
}
break; break;
} }
case Token::SHL: case Token::SHL:
@ -921,28 +1053,48 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
return TypePointer(); return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max()) else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer(); return TypePointer();
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>(); if (m_value.numerator() == 0)
value = m_value.numerator() * pow(bigint(2), exponent); value = 0;
else
{
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
if (!fitsPrecisionBase2(abs(m_value.numerator()), exponent))
return TypePointer();
value = m_value.numerator() * pow(bigint(2), exponent);
}
break; break;
} }
// NOTE: we're using >> (SAR) to denote right shifting. The type of the LValue // NOTE: we're using >> (SAR) to denote right shifting. The type of the LValue
// determines the resulting type and the type of shift (SAR or SHR). // determines the resulting type and the type of shift (SAR or SHR).
case Token::SAR: case Token::SAR:
{ {
using boost::multiprecision::pow; namespace mp = boost::multiprecision;
if (fractional) if (fractional)
return TypePointer(); return TypePointer();
else if (other.m_value < 0) else if (other.m_value < 0)
return TypePointer(); return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max()) else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer(); return TypePointer();
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>(); if (m_value.numerator() == 0)
value = rational(m_value.numerator() / pow(bigint(2), exponent), 1); value = 0;
else
{
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
if (exponent > mostSignificantBit(mp::abs(m_value.numerator())))
value = 0;
else
value = rational(m_value.numerator() / mp::pow(bigint(2), exponent), 1);
}
break; break;
} }
default: default:
return TypePointer(); return TypePointer();
} }
// verify that numerator and denominator fit into 4096 bit after every operation
if (value.numerator() != 0 && max(mostSignificantBit(abs(value.numerator())), mostSignificantBit(abs(value.denominator()))) > 4096)
return TypePointer();
return make_shared<RationalNumberType>(value); return make_shared<RationalNumberType>(value);
} }
} }
@ -1262,6 +1414,8 @@ bool ContractType::isPayable() const
TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
{ {
if (isSuper())
return TypePointer{};
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer(); return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
} }
@ -1969,25 +2123,19 @@ bool StructType::recursive() const
{ {
if (!m_recursive.is_initialized()) if (!m_recursive.is_initialized())
{ {
set<StructDefinition const*> structsSeen; auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector)
function<bool(StructType const*)> check = [&](StructType const* t) -> bool
{ {
StructDefinition const* str = &t->structDefinition(); for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
if (structsSeen.count(str))
return true;
structsSeen.insert(str);
for (ASTPointer<VariableDeclaration> const& variable: str->members())
{ {
Type const* memberType = variable->annotation().type.get(); Type const* memberType = variable->annotation().type.get();
while (dynamic_cast<ArrayType const*>(memberType)) while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get(); memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType)) if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
if (check(innerStruct)) if (_cycleDetector.run(innerStruct->structDefinition()))
return true; return;
} }
return false;
}; };
m_recursive = check(this); m_recursive = (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
} }
return *m_recursive; return *m_recursive;
} }
@ -2311,6 +2459,18 @@ vector<string> FunctionType::parameterNames() const
return vector<string>(m_parameterNames.cbegin() + 1, m_parameterNames.cend()); return vector<string>(m_parameterNames.cbegin() + 1, m_parameterNames.cend());
} }
TypePointers FunctionType::returnParameterTypesWithoutDynamicTypes() const
{
TypePointers returnParameterTypes = m_returnParameterTypes;
if (m_kind == Kind::External || m_kind == Kind::CallCode || m_kind == Kind::DelegateCall)
for (auto& param: returnParameterTypes)
if (param->isDynamicallySized() && !param->dataStoredIn(DataLocation::Storage))
param = make_shared<InaccessibleDynamicType>();
return returnParameterTypes;
}
TypePointers FunctionType::parameterTypes() const TypePointers FunctionType::parameterTypes() const
{ {
if (!bound()) if (!bound())
@ -2355,7 +2515,11 @@ string FunctionType::richIdentifier() const
case Kind::ByteArrayPush: id += "bytearraypush"; break; case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; break; case Kind::ObjectCreation: id += "objectcreation"; break;
case Kind::Assert: id += "assert"; break; case Kind::Assert: id += "assert"; break;
case Kind::Require: id += "require";break; case Kind::Require: id += "require"; break;
case Kind::ABIEncode: id += "abiencode"; break;
case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
default: solAssert(false, "Unknown function location."); break; default: solAssert(false, "Unknown function location."); break;
} }
id += "_" + stateMutabilityToString(m_stateMutability); id += "_" + stateMutabilityToString(m_stateMutability);
@ -2772,18 +2936,9 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
kind = Kind::DelegateCall; kind = Kind::DelegateCall;
} }
TypePointers returnParameterTypes = m_returnParameterTypes;
if (kind != Kind::Internal)
{
// Alter dynamic types to be non-accessible.
for (auto& param: returnParameterTypes)
if (param->isDynamicallySized())
param = make_shared<InaccessibleDynamicType>();
}
return make_shared<FunctionType>( return make_shared<FunctionType>(
parameterTypes, parameterTypes,
returnParameterTypes, m_returnParameterTypes,
m_parameterNames, m_parameterNames,
m_returnParameterNames, m_returnParameterNames,
kind, kind,
@ -2875,7 +3030,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
} }
if (contract.isLibrary()) if (contract.isLibrary())
for (FunctionDefinition const* function: contract.definedFunctions()) for (FunctionDefinition const* function: contract.definedFunctions())
if (function->isVisibleInDerivedContracts()) if (function->isVisibleAsLibraryMember())
members.push_back(MemberList::Member( members.push_back(MemberList::Member(
function->name(), function->name(),
FunctionType(*function).asMemberFunction(true), FunctionType(*function).asMemberFunction(true),
@ -2985,6 +3140,8 @@ string MagicType::richIdentifier() const
return "t_magic_message"; return "t_magic_message";
case Kind::Transaction: case Kind::Transaction:
return "t_magic_transaction"; return "t_magic_transaction";
case Kind::ABI:
return "t_magic_abi";
default: default:
solAssert(false, "Unknown kind of magic"); solAssert(false, "Unknown kind of magic");
} }
@ -3025,6 +3182,45 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, {"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)},
{"gasprice", make_shared<IntegerType>(256)} {"gasprice", make_shared<IntegerType>(256)}
}); });
case Kind::ABI:
return MemberList::MemberMap({
{"encode", make_shared<FunctionType>(
TypePointers(),
TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
strings{},
strings{},
FunctionType::Kind::ABIEncode,
true,
StateMutability::Pure
)},
{"encodePacked", make_shared<FunctionType>(
TypePointers(),
TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
strings{},
strings{},
FunctionType::Kind::ABIEncodePacked,
true,
StateMutability::Pure
)},
{"encodeWithSelector", make_shared<FunctionType>(
TypePointers{make_shared<FixedBytesType>(4)},
TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
strings{},
strings{},
FunctionType::Kind::ABIEncodeWithSelector,
true,
StateMutability::Pure
)},
{"encodeWithSignature", make_shared<FunctionType>(
TypePointers{make_shared<ArrayType>(DataLocation::Memory, true)},
TypePointers{make_shared<ArrayType>(DataLocation::Memory)},
strings{},
strings{},
FunctionType::Kind::ABIEncodeWithSignature,
true,
StateMutability::Pure
)}
});
default: default:
solAssert(false, "Unknown kind of magic."); solAssert(false, "Unknown kind of magic.");
} }
@ -3040,6 +3236,8 @@ string MagicType::toString(bool) const
return "msg"; return "msg";
case Kind::Transaction: case Kind::Transaction:
return "tx"; return "tx";
case Kind::ABI:
return "abi";
default: default:
solAssert(false, "Unknown kind of magic."); solAssert(false, "Unknown kind of magic.");
} }

View File

@ -150,6 +150,7 @@ public:
/// @name Factory functions /// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type. /// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(ElementaryTypeNameToken const& _type); static TypePointer fromElementaryTypeName(ElementaryTypeNameToken const& _type);
/// Converts a given elementary type name with optional suffix " memory" to a type pointer.
static TypePointer fromElementaryTypeName(std::string const& _name); static TypePointer fromElementaryTypeName(std::string const& _name);
/// @} /// @}
@ -229,6 +230,9 @@ public:
/// i.e. it behaves differently in lvalue context and in value context. /// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; } virtual bool isValueType() const { return false; }
virtual unsigned sizeOnStack() const { return 1; } virtual unsigned sizeOnStack() const { return 1; }
/// If it is possible to initialize such a value in memory by just writing zeros
/// of the size memoryHeadSize().
virtual bool hasSimpleZeroValueInMemory() const { return true; }
/// @returns the mobile (in contrast to static) type corresponding to the given type. /// @returns the mobile (in contrast to static) type corresponding to the given type.
/// This returns the corresponding IntegerType or FixedPointType for RationalNumberType /// This returns the corresponding IntegerType or FixedPointType for RationalNumberType
/// and the pointer type for storage reference types. /// and the pointer type for storage reference types.
@ -399,7 +403,7 @@ private:
}; };
/** /**
* Integer and fixed point constants either literals or computed. * Integer and fixed point constants either literals or computed.
* Example expressions: 2, 3.14, 2+10.2, ~10. * Example expressions: 2, 3.14, 2+10.2, ~10.
* There is one distinct type per value. * There is one distinct type per value.
*/ */
@ -411,7 +415,7 @@ public:
/// @returns true if the literal is a valid integer. /// @returns true if the literal is a valid integer.
static std::tuple<bool, rational> isValidLiteral(Literal const& _literal); static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
explicit RationalNumberType(rational const& _value): explicit RationalNumberType(rational const& _value):
m_value(_value) m_value(_value)
{} {}
@ -432,7 +436,7 @@ public:
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible. /// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
std::shared_ptr<IntegerType const> integerType() const; std::shared_ptr<IntegerType const> integerType() const;
/// @returns the smallest fixed type that can hold the value or incurs the least precision loss. /// @returns the smallest fixed type that can hold the value or incurs the least precision loss.
/// If the integer part does not fit, returns an empty pointer. /// If the integer part does not fit, returns an empty pointer.
std::shared_ptr<FixedPointType const> fixedPointType() const; std::shared_ptr<FixedPointType const> fixedPointType() const;
@ -442,6 +446,9 @@ public:
/// @returns true if the value is negative. /// @returns true if the value is negative.
bool isNegative() const { return m_value < 0; } bool isNegative() const { return m_value < 0; }
/// @returns true if the value is zero.
bool isZero() const { return m_value == 0; }
private: private:
rational m_value; rational m_value;
@ -568,6 +575,7 @@ public:
virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); } virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); }
virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } virtual bool dataStoredIn(DataLocation _location) const override { return m_location == _location; }
virtual bool hasSimpleZeroValueInMemory() const override { return false; }
/// Storage references can be pointers or bound references. In general, local variables are of /// Storage references can be pointers or bound references. In general, local variables are of
/// pointer type, state variables are bound references. Assignments to pointers or deleting /// pointer type, state variables are bound references. Assignments to pointers or deleting
@ -692,22 +700,27 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded ) const override virtual unsigned calldataEncodedSize(bool _padded ) const override
{ {
solAssert(!isSuper(), "");
return encodingType()->calldataEncodedSize(_padded); return encodingType()->calldataEncodedSize(_padded);
} }
virtual unsigned storageBytes() const override { return 20; } virtual unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
virtual bool canLiveOutsideStorage() const override { return true; } virtual bool canLiveOutsideStorage() const override { return !isSuper(); }
virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; } virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return !isSuper(); }
virtual std::string toString(bool _short) const override; virtual std::string toString(bool _short) const override;
virtual std::string canonicalName() const override; virtual std::string canonicalName() const override;
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override virtual TypePointer encodingType() const override
{ {
if (isSuper())
return TypePointer{};
return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address);
} }
virtual TypePointer interfaceType(bool _inLibrary) const override virtual TypePointer interfaceType(bool _inLibrary) const override
{ {
if (isSuper())
return TypePointer{};
return _inLibrary ? shared_from_this() : encodingType(); return _inLibrary ? shared_from_this() : encodingType();
} }
@ -768,7 +781,7 @@ public:
virtual std::string canonicalName() const override; virtual std::string canonicalName() const override;
virtual std::string signatureInExternalFunction(bool _structsByName) const override; virtual std::string signatureInExternalFunction(bool _structsByName) const override;
/// @returns a function that peforms the type conversion between a list of struct members /// @returns a function that performs the type conversion between a list of struct members
/// and a memory struct of this type. /// and a memory struct of this type.
FunctionTypePointer constructorType() const; FunctionTypePointer constructorType() const;
@ -850,6 +863,7 @@ public:
virtual u256 storageSize() const override; virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override; virtual unsigned sizeOnStack() const override;
virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual TypePointer mobileType() const override; virtual TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching. /// Converts components to their temporary types and performs some wildcard matching.
virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override;
@ -903,6 +917,10 @@ public:
ObjectCreation, ///< array creation using new ObjectCreation, ///< array creation using new
Assert, ///< assert() Assert, ///< assert()
Require, ///< require() Require, ///< require()
ABIEncode,
ABIEncodePacked,
ABIEncodeWithSelector,
ABIEncodeWithSignature,
GasLeft ///< gasleft() GasLeft ///< gasleft()
}; };
@ -973,6 +991,9 @@ public:
TypePointers parameterTypes() const; TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const; std::vector<std::string> parameterNames() const;
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; } TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
/// @returns the list of return parameter types. All dynamically-sized types (this excludes
/// storage pointers) are replaced by InaccessibleDynamicType instances.
TypePointers returnParameterTypesWithoutDynamicTypes() const;
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; } std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
/// @returns the "self" parameter type for a bound function /// @returns the "self" parameter type for a bound function
TypePointer const& selfType() const; TypePointer const& selfType() const;
@ -991,6 +1012,7 @@ public:
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual unsigned sizeOnStack() const override; virtual unsigned sizeOnStack() const override;
virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override; virtual TypePointer encodingType() const override;
virtual TypePointer interfaceType(bool _inLibrary) const override; virtual TypePointer interfaceType(bool _inLibrary) const override;
@ -1024,7 +1046,7 @@ public:
return *m_declaration; return *m_declaration;
} }
bool hasDeclaration() const { return !!m_declaration; } bool hasDeclaration() const { return !!m_declaration; }
/// @returns true if the the result of this function only depends on its arguments /// @returns true if the result of this function only depends on its arguments
/// and it does not modify the state. /// and it does not modify the state.
/// Currently, this will only return true for internal functions like keccak and ecrecover. /// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const; bool isPure() const;
@ -1034,14 +1056,14 @@ public:
ASTPointer<ASTString> documentation() const; ASTPointer<ASTString> documentation() const;
/// true iff arguments are to be padded to multiples of 32 bytes for external calls /// 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); } 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; } bool takesArbitraryParameters() const { return m_arbitraryParameters; }
bool gasSet() const { return m_gasSet; } bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; } bool valueSet() const { return m_valueSet; }
bool bound() const { return m_bound; } bool bound() const { return m_bound; }
/// @returns a copy of this type, where gas or value are set manually. This will never set one /// @returns a copy of this type, where gas or value are set manually. This will never set one
/// of the parameters to fals. /// of the parameters to false.
TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const; TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const;
/// @returns a copy of this function type where all return parameters of dynamic size are /// @returns a copy of this function type where all return parameters of dynamic size are
@ -1096,6 +1118,8 @@ public:
return _inLibrary ? shared_from_this() : TypePointer(); return _inLibrary ? shared_from_this() : TypePointer();
} }
virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; } virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
/// Cannot be stored in memory, but just in case.
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
TypePointer const& keyType() const { return m_keyType; } TypePointer const& keyType() const { return m_keyType; }
TypePointer const& valueType() const { return m_valueType; } TypePointer const& valueType() const { return m_valueType; }
@ -1124,6 +1148,7 @@ public:
virtual u256 storageSize() const override; virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override; virtual unsigned sizeOnStack() const override;
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } virtual std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@ -1146,6 +1171,7 @@ public:
virtual u256 storageSize() const override; virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; } virtual unsigned sizeOnStack() const override { return 0; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string richIdentifier() const override; virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override; virtual std::string toString(bool _short) const override;
@ -1171,6 +1197,7 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; } virtual bool canLiveOutsideStorage() const override { return true; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; } virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -1187,7 +1214,7 @@ private:
class MagicType: public Type class MagicType: public Type
{ {
public: public:
enum class Kind { Block, Message, Transaction }; enum class Kind { Block, Message, Transaction, ABI };
virtual Category category() const override { return Category::Magic; } virtual Category category() const override { return Category::Magic; }
explicit MagicType(Kind _kind): m_kind(_kind) {} explicit MagicType(Kind _kind): m_kind(_kind) {}
@ -1201,6 +1228,7 @@ public:
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; } virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; } virtual bool canLiveOutsideStorage() const override { return true; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; } virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -1230,6 +1258,7 @@ public:
virtual bool canLiveOutsideStorage() const override { return false; } virtual bool canLiveOutsideStorage() const override { return false; }
virtual bool isValueType() const override { return true; } virtual bool isValueType() const override { return true; }
virtual unsigned sizeOnStack() const override { return 1; } virtual unsigned sizeOnStack() const override { return 1; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string toString(bool) const override { return "inaccessible dynamic type"; } virtual std::string toString(bool) const override { return "inaccessible dynamic type"; }
virtual TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); } virtual TypePointer decodingType() const override { return std::make_shared<IntegerType>(256); }
}; };

View File

@ -253,6 +253,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
templ("body", w.render()); templ("body", w.render());
break; break;
} }
case Type::Category::InaccessibleDynamic:
templ("body", "cleaned := 0");
break;
default: default:
solAssert(false, "Cleanup of type " + _type.identifier() + " requested."); solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
} }

View File

@ -741,10 +741,10 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
if (_type.isByteArray()) if (_type.isByteArray())
// For a "long" byte array, store length as 2*length+1 // For a "long" byte array, store length as 2*length+1
_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
_context<< Instruction::DUP4 << Instruction::SSTORE; _context << Instruction::DUP4 << Instruction::SSTORE;
// skip if size is not reduced // skip if size is not reduced
_context << Instruction::DUP2 << Instruction::DUP2 _context << Instruction::DUP2 << Instruction::DUP2
<< Instruction::ISZERO << Instruction::GT; << Instruction::GT << Instruction::ISZERO;
_context.appendConditionalJumpTo(resizeEnd); _context.appendConditionalJumpTo(resizeEnd);
// size reduced, clear the end of the array // size reduced, clear the end of the array
@ -774,6 +774,55 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
); );
} }
void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const
{
solAssert(_type.location() == DataLocation::Storage, "");
solAssert(_type.isDynamicallySized(), "");
if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
if (_type.isByteArray())
{
// We almost always just add 2 (length of byte arrays is shifted left by one)
// except for the case where we transition from a short byte array
// to a long byte array, there we have to copy.
// This happens if the length is exactly 31, which means that the
// lowest-order byte (we actually use a mask with fewer bits) must
// be (31*2+0) = 62
m_context.appendInlineAssembly(R"({
let data := sload(ref)
let shifted_length := and(data, 63)
// We have to copy if length is exactly 31, because that marks
// the transition between in-place and out-of-place storage.
switch shifted_length
case 62
{
mstore(0, ref)
let data_area := keccak256(0, 0x20)
sstore(data_area, and(data, not(0xff)))
// New length is 32, encoded as (32 * 2 + 1)
sstore(ref, 65)
// Replace ref variable by new length
ref := 32
}
default
{
sstore(ref, add(data, 2))
// Replace ref variable by new length
if iszero(and(data, 1)) { data := shifted_length }
ref := add(div(data, 2), 1)
}
})", {"ref"});
}
else
m_context.appendInlineAssembly(R"({
let new_length := add(sload(ref), 1)
sstore(ref, new_length)
ref := new_length
})", {"ref"});
}
void ArrayUtils::clearStorageLoop(TypePointer const& _type) const void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{ {
m_context.callLowLevelFunction( m_context.callLowLevelFunction(

View File

@ -67,6 +67,12 @@ public:
/// Stack pre: reference (excludes byte offset) new_length /// Stack pre: reference (excludes byte offset) new_length
/// Stack post: /// Stack post:
void resizeDynamicArray(ArrayType const& _type) const; void resizeDynamicArray(ArrayType const& _type) const;
/// Increments the size of a dynamic array by one.
/// Does not touch the new data element. In case of a byte array, this might move the
/// data.
/// Stack pre: reference (excludes byte offset)
/// Stack post: new_length
void incrementDynamicArraySize(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end). /// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref /// Stack pre: end_ref start_ref
/// Stack post: end_ref /// Stack post: end_ref

View File

@ -193,14 +193,22 @@ Declaration const* CompilerContext::nextFunctionToCompile() const
return m_functionCompilationQueue.nextFunctionToCompile(); return m_functionCompilationQueue.nextFunctionToCompile();
} }
ModifierDefinition const& CompilerContext::functionModifier(string const& _name) const ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier(
ModifierDefinition const& _modifier
) const
{ {
// Libraries do not allow inheritance and their functions can be inlined, so we should not
// search the inheritance hierarchy (which will be the wrong one in case the function
// is inlined).
if (auto scope = dynamic_cast<ContractDefinition const*>(_modifier.scope()))
if (scope->isLibrary())
return _modifier;
solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set.");
for (ContractDefinition const* contract: m_inheritanceHierarchy) for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers()) for (ModifierDefinition const* modifier: contract->functionModifiers())
if (modifier->name() == _name) if (modifier->name() == _modifier.name())
return *modifier; return *modifier;
solAssert(false, "Function modifier " + _name + " not found."); solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy.");
} }
unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const
@ -254,12 +262,20 @@ CompilerContext& CompilerContext::appendRevert()
return *this << u256(0) << u256(0) << Instruction::REVERT; return *this << u256(0) << u256(0) << Instruction::REVERT;
} }
CompilerContext& CompilerContext::appendConditionalRevert() CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
{ {
*this << Instruction::ISZERO; if (_forwardReturnData && m_evmVersion.supportsReturndata())
eth::AssemblyItem afterTag = appendConditionalJump(); appendInlineAssembly(R"({
appendRevert(); if condition {
*this << afterTag; returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
})", {"condition"});
else
appendInlineAssembly(R"({
if condition { revert(0, 0) }
})", {"condition"});
*this << Instruction::POP;
return *this; return *this;
} }

View File

@ -130,7 +130,7 @@ public:
void appendMissingLowLevelFunctions(); void appendMissingLowLevelFunctions();
ABIFunctions& abiFunctions() { return m_abiFunctions; } ABIFunctions& abiFunctions() { return m_abiFunctions; }
ModifierDefinition const& functionModifier(std::string const& _name) const; ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function). /// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns /// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@ -156,8 +156,11 @@ public:
CompilerContext& appendConditionalInvalid(); CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call /// Appends a REVERT(0, 0) call
CompilerContext& appendRevert(); CompilerContext& appendRevert();
/// Appends a conditional REVERT(0, 0) call /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
CompilerContext& appendConditionalRevert(); /// empty string. Consumes the condition.
/// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
/// the data.
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false);
/// Appends a JUMP to a specific tag /// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag. /// Appends pushing of a new tag and @returns the new tag.

View File

@ -21,12 +21,16 @@
*/ */
#include <libsolidity/codegen/CompilerUtils.h> #include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/codegen/ArrayUtils.h> #include <libsolidity/codegen/ArrayUtils.h>
#include <libsolidity/codegen/LValue.h> #include <libsolidity/codegen/LValue.h>
#include <libsolidity/codegen/ABIFunctions.h> #include <libsolidity/codegen/ABIFunctions.h>
#include <libevmasm/Instruction.h>
#include <libdevcore/Whiskers.h>
using namespace std; using namespace std;
namespace dev namespace dev
@ -36,11 +40,17 @@ namespace solidity
const unsigned CompilerUtils::dataStartOffset = 4; const unsigned CompilerUtils::dataStartOffset = 4;
const size_t CompilerUtils::freeMemoryPointer = 64; const size_t CompilerUtils::freeMemoryPointer = 64;
const size_t CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32;
const size_t CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32;
const unsigned CompilerUtils::identityContractAddress = 4; const unsigned CompilerUtils::identityContractAddress = 4;
static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area.");
static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer.");
static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area.");
void CompilerUtils::initialiseFreeMemoryPointer() void CompilerUtils::initialiseFreeMemoryPointer()
{ {
m_context << u256(freeMemoryPointer + 32); m_context << u256(generalPurposeMemoryStart);
storeFreeMemoryPointer(); storeFreeMemoryPointer();
} }
@ -68,6 +78,20 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
} }
void CompilerUtils::revertWithStringData(Type const& _argumentType)
{
solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), "");
fetchFreeMemoryPointer();
m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32));
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(4) << Instruction::ADD;
// Stack: <string data> <mem pos of encoding start>
abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
toSizeAfterFreeMemoryPointer();
m_context << Instruction::REVERT;
m_context.adjustStackOffset(_argumentType.sizeOnStack());
}
unsigned CompilerUtils::loadFromMemory( unsigned CompilerUtils::loadFromMemory(
unsigned _offset, unsigned _offset,
Type const& _type, Type const& _type,
@ -139,7 +163,6 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
) )
{ {
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
combineExternalFunctionType(true); combineExternalFunctionType(true);
m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD;
@ -159,6 +182,163 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
} }
} }
void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory, bool _revertOnOutOfBounds)
{
/// Stack: <source_offset> <length>
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
{
// Use the new JULIA-based decoding function
auto stackHeightBefore = m_context.stackHeight();
abiDecodeV2(_typeParameters, _fromMemory);
solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, "");
return;
}
//@todo this does not yet support nested dynamic arrays
if (_revertOnOutOfBounds)
{
size_t encodedSize = 0;
for (auto const& t: _typeParameters)
encodedSize += t->decodingType()->calldataEncodedSize(true);
m_context.appendInlineAssembly("{ if lt(len, " + to_string(encodedSize) + ") { revert(0, 0) } }", {"len"});
}
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1;
/// Stack: <input_end> <source_offset>
// Retain the offset pointer as base_offset, the point from which the data offsets are computed.
m_context << Instruction::DUP1;
for (TypePointer const& parameterType: _typeParameters)
{
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset
TypePointer type = parameterType->decodingType();
solUnimplementedAssert(type, "No decoding type found.");
if (type->category() == Type::Category::Array)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
solUnimplementedAssert(!arrayType.baseType()->isDynamicallyEncoded(), "Nested arrays not yet implemented.");
if (_fromMemory)
{
solUnimplementedAssert(
arrayType.baseType()->isValueType(),
"Nested memory arrays not yet implemented here."
);
// @todo If base type is an array or struct, it is still calldata-style encoded, so
// we would have to convert it like below.
solAssert(arrayType.location() == DataLocation::Memory, "");
if (arrayType.isDynamicallySized())
{
// compute data pointer
m_context << Instruction::DUP1 << Instruction::MLOAD;
if (_revertOnOutOfBounds)
{
// Check that the data pointer is valid and that length times
// item size is still inside the range.
Whiskers templ(R"({
if gt(ptr, 0x100000000) { revert(0, 0) }
ptr := add(ptr, base_offset)
let array_data_start := add(ptr, 0x20)
if gt(array_data_start, input_end) { revert(0, 0) }
let array_length := mload(ptr)
if or(
gt(array_length, 0x100000000),
gt(add(array_data_start, mul(array_length, <item_size>)), input_end)
) { revert(0, 0) }
})");
templ("item_size", to_string(arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true)));
m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr"});
}
else
m_context << Instruction::DUP3 << Instruction::ADD;
// stack: v1 v2 ... v(k-1) input_end base_offset current_offset v(k)
moveIntoStack(3);
m_context << u256(0x20) << Instruction::ADD;
}
else
{
// Size has already been checked for this one.
moveIntoStack(2);
m_context << Instruction::DUP3;
m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
}
}
else
{
// first load from calldata and potentially convert to memory if arrayType is memory
TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false);
if (calldataType->isDynamicallySized())
{
// put on stack: data_pointer length
loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
m_context << Instruction::SWAP1;
// stack: input_end base_offset next_pointer data_offset
if (_revertOnOutOfBounds)
m_context.appendInlineAssembly("{ if gt(data_offset, 0x100000000) { revert(0, 0) } }", {"data_offset"});
m_context << Instruction::DUP3 << Instruction::ADD;
// stack: input_end base_offset next_pointer array_head_ptr
if (_revertOnOutOfBounds)
m_context.appendInlineAssembly(
"{ if gt(add(array_head_ptr, 0x20), input_end) { revert(0, 0) } }",
{"input_end", "base_offset", "next_ptr", "array_head_ptr"}
);
// retrieve length
loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
// stack: input_end base_offset next_pointer array_length data_pointer
m_context << Instruction::SWAP2;
// stack: input_end base_offset data_pointer array_length next_pointer
if (_revertOnOutOfBounds)
{
unsigned itemSize = arrayType.isByteArray() ? 1 : arrayType.baseType()->calldataEncodedSize(true);
m_context.appendInlineAssembly(R"({
if or(
gt(array_length, 0x100000000),
gt(add(data_ptr, mul(array_length, )" + to_string(itemSize) + R"()), input_end)
) { revert(0, 0) }
})", {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"});
}
}
else
{
// size has already been checked
// stack: input_end base_offset data_offset
m_context << Instruction::DUP1;
m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
}
if (arrayType.location() == DataLocation::Memory)
{
// stack: input_end base_offset calldata_ref [length] next_calldata
// copy to memory
// move calldata type up again
moveIntoStack(calldataType->sizeOnStack());
convertType(*calldataType, arrayType, false, false, true);
// fetch next pointer again
moveToStackTop(arrayType.sizeOnStack());
}
// move input_end up
// stack: input_end base_offset calldata_ref [length] next_calldata
moveToStackTop(2 + arrayType.sizeOnStack());
m_context << Instruction::SWAP1;
// stack: base_offset calldata_ref [length] input_end next_calldata
moveToStackTop(2 + arrayType.sizeOnStack());
m_context << Instruction::SWAP1;
// stack: calldata_ref [length] input_end base_offset next_calldata
}
}
else
{
solAssert(!type->isDynamicallyEncoded(), "Unknown dynamically sized type: " + type->toString());
loadFromMemoryDynamic(*type, !_fromMemory, true);
// stack: v1 v2 ... v(k-1) input_end base_offset v(k) mem_offset
moveToStackTop(1, type->sizeOnStack());
moveIntoStack(3, type->sizeOnStack());
}
// stack: v1 v2 ... v(k-1) v(k) input_end base_offset next_offset
}
popStackSlots(3);
}
void CompilerUtils::encodeToMemory( void CompilerUtils::encodeToMemory(
TypePointers const& _givenTypes, TypePointers const& _givenTypes,
TypePointers const& _targetTypes, TypePointers const& _targetTypes,
@ -321,15 +501,13 @@ void CompilerUtils::abiEncodeV2(
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
{ {
// stack: <source_offset> // stack: <source_offset> <length> [stack top]
auto ret = m_context.pushNewTag(); auto ret = m_context.pushNewTag();
moveIntoStack(2);
// stack: <return tag> <source_offset> <length> [stack top]
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
if (_fromMemory) // stack: <return tag> <end> <start>
// TODO pass correct size for the memory case
m_context << (u256(1) << 63);
else
m_context << Instruction::CALLDATASIZE;
m_context << Instruction::SWAP1;
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
m_context.appendJumpTo(m_context.namedTag(decoderName)); m_context.appendJumpTo(m_context.namedTag(decoderName));
m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
@ -338,14 +516,34 @@ void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromM
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{ {
auto repeat = m_context.newTag(); if (_type.baseType()->hasSimpleZeroValueInMemory())
m_context << repeat; {
pushZeroValue(*_type.baseType()); solAssert(_type.baseType()->isValueType(), "");
storeInMemoryDynamic(*_type.baseType()); Whiskers templ(R"({
m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; let size := mul(length, <element_size>)
m_context << Instruction::SUB << Instruction::SWAP1; // cheap way of zero-initializing a memory range
m_context << Instruction::DUP2; codecopy(memptr, codesize(), size)
m_context.appendConditionalJumpTo(repeat); memptr := add(memptr, size)
})");
templ("element_size", to_string(_type.baseType()->memoryHeadSize()));
m_context.appendInlineAssembly(templ.render(), {"length", "memptr"});
}
else
{
// TODO: Potential optimization:
// When we create a new multi-dimensional dynamic array, each element
// is initialized to an empty array. It actually does not hurt
// to re-use exactly the same empty array for all elements. Currently,
// a new one is created each time.
auto repeat = m_context.newTag();
m_context << repeat;
pushZeroValue(*_type.baseType());
storeInMemoryDynamic(*_type.baseType());
m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1;
m_context << Instruction::SUB << Instruction::SWAP1;
m_context << Instruction::DUP2;
m_context.appendConditionalJumpTo(repeat);
}
m_context << Instruction::SWAP1 << Instruction::POP; m_context << Instruction::SWAP1 << Instruction::POP;
} }
@ -427,7 +625,7 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
leftShiftNumberOnStack(64); leftShiftNumberOnStack(64);
} }
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly)
{ {
m_context << m_context.functionEntryLabel(_function).pushTag(); m_context << m_context.functionEntryLabel(_function).pushTag();
// If there is a runtime context, we have to merge both labels into the same // If there is a runtime context, we have to merge both labels into the same
@ -435,9 +633,10 @@ void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
if (CompilerContext* rtc = m_context.runtimeContext()) if (CompilerContext* rtc = m_context.runtimeContext())
{ {
leftShiftNumberOnStack(32); leftShiftNumberOnStack(32);
m_context << if (_runtimeOnly)
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << m_context <<
Instruction::OR; rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
} }
} }
@ -485,19 +684,17 @@ void CompilerUtils::convertType(
// clear for conversion to longer bytes // clear for conversion to longer bytes
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType); FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded) if (typeOnStack.numBytes() == 0 || targetType.numBytes() == 0)
m_context << Instruction::POP << u256(0);
else if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded)
{ {
if (typeOnStack.numBytes() == 0) int bytes = min(typeOnStack.numBytes(), targetType.numBytes());
m_context << Instruction::POP << u256(0); m_context << ((u256(1) << (256 - bytes * 8)) - 1);
else m_context << Instruction::NOT << Instruction::AND;
{
m_context << ((u256(1) << (256 - typeOnStack.numBytes() * 8)) - 1);
m_context << Instruction::NOT << Instruction::AND;
}
} }
} }
}
break; break;
}
case Type::Category::Enum: case Type::Category::Enum:
solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, "");
if (enumOverflowCheckPending) if (enumOverflowCheckPending)
@ -506,6 +703,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding) if (_asPartOfArgumentDecoding)
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
else else
m_context.appendConditionalInvalid(); m_context.appendConditionalInvalid();
@ -598,8 +796,9 @@ void CompilerUtils::convertType(
bytesConstRef data(value); bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes) if (targetTypeCategory == Type::Category::FixedBytes)
{ {
int const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
solAssert(data.size() <= 32, ""); solAssert(data.size() <= 32, "");
m_context << h256::Arith(h256(data, h256::AlignLeft)); m_context << (h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes))));
} }
else if (targetTypeCategory == Type::Category::Array) else if (targetTypeCategory == Type::Category::Array)
{ {
@ -873,6 +1072,13 @@ void CompilerUtils::pushZeroValue(Type const& _type)
return; return;
} }
solAssert(referenceType->location() == DataLocation::Memory, ""); solAssert(referenceType->location() == DataLocation::Memory, "");
if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
if (arrayType->isDynamicallySized())
{
// Push a memory location that is (hopefully) always zero.
pushZeroPointer();
return;
}
TypePointer type = _type.shared_from_this(); TypePointer type = _type.shared_from_this();
m_context.callLowLevelFunction( m_context.callLowLevelFunction(
@ -893,13 +1099,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
} }
else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get())) else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
{ {
if (arrayType->isDynamicallySized()) solAssert(!arrayType->isDynamicallySized(), "");
{ if (arrayType->length() > 0)
// zero length
_context << u256(0);
utils.storeInMemoryDynamic(IntegerType(256));
}
else if (arrayType->length() > 0)
{ {
_context << arrayType->length() << Instruction::SWAP1; _context << arrayType->length() << Instruction::SWAP1;
// stack: items_to_do memory_pos // stack: items_to_do memory_pos
@ -916,6 +1117,11 @@ void CompilerUtils::pushZeroValue(Type const& _type)
); );
} }
void CompilerUtils::pushZeroPointer()
{
m_context << u256(zeroPointer);
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
{ {
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable)); unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable));

View File

@ -54,6 +54,13 @@ public:
/// Stack post: <size> <mem_start> /// Stack post: <size> <mem_start>
void toSizeAfterFreeMemoryPointer(); void toSizeAfterFreeMemoryPointer();
/// Appends code that performs a revert, providing the given string data.
/// Will also append an error signature corresponding to Error(string).
/// @param _argumentType the type of the string argument, will be converted to memory string.
/// Stack pre: string data
/// Stack post:
void revertWithStringData(Type const& _argumentType);
/// Loads data from memory to the stack. /// Loads data from memory to the stack.
/// @param _offset offset in memory (or calldata) /// @param _offset offset in memory (or calldata)
/// @param _type data type to load /// @param _type data type to load
@ -88,6 +95,15 @@ public:
/// Stack post: (memory_offset+length) /// Stack post: (memory_offset+length)
void storeInMemoryDynamic(Type const& _type, bool _padToWords = true); void storeInMemoryDynamic(Type const& _type, bool _padToWords = true);
/// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data.
/// Calls revert if @a _revertOnOutOfBounds is true and the supplied size is shorter
/// than the static data requirements or if dynamic data pointers reach outside of the
/// area. Also has a hard cap of 0x100000000 for any given length/offset field.
/// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen>
void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false, bool _revertOnOutOfBounds = false);
/// Copies values (of types @a _givenTypes) given on the stack to a location in memory given /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
/// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
/// Removes the values from the stack and leaves the updated memory pointer. /// Removes the values from the stack and leaves the updated memory pointer.
@ -149,7 +165,7 @@ public:
/// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,
/// the data is taken from memory instead of from calldata. /// the data is taken from memory instead of from calldata.
/// Can allocate memory. /// Can allocate memory.
/// Stack pre: <source_offset> /// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen> /// Stack post: <value0> <value1> ... <valuen>
void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false);
@ -179,7 +195,8 @@ public:
/// Appends code that combines the construction-time (if available) and runtime function /// Appends code that combines the construction-time (if available) and runtime function
/// entry label of the given function into a single stack slot. /// entry label of the given function into a single stack slot.
/// Note: This might cause the compilation queue of the runtime context to be extended. /// Note: This might cause the compilation queue of the runtime context to be extended.
void pushCombinedFunctionEntryLabel(Declaration const& _function); /// If @a _runtimeOnly, the entry label will include the runtime assembly tag.
void pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly = true);
/// Appends code for an implicit or explicit type conversion. This includes erasing higher /// Appends code for an implicit or explicit type conversion. This includes erasing higher
/// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory
@ -200,6 +217,9 @@ public:
/// Creates a zero-value for the given type and puts it onto the stack. This might allocate /// Creates a zero-value for the given type and puts it onto the stack. This might allocate
/// memory for memory references. /// memory for memory references.
void pushZeroValue(Type const& _type); void pushZeroValue(Type const& _type);
/// Pushes a pointer to the stack that points to a (potentially shared) location in memory
/// that always contains a zero. It is not allowed to write there.
void pushZeroPointer();
/// Moves the value that is at the top of the stack to a stack variable. /// Moves the value that is at the top of the stack to a stack variable.
void moveToStackVariable(VariableDeclaration const& _variable); void moveToStackVariable(VariableDeclaration const& _variable);
@ -245,6 +265,10 @@ public:
/// Position of the free-memory-pointer in memory; /// Position of the free-memory-pointer in memory;
static const size_t freeMemoryPointer; static const size_t freeMemoryPointer;
/// Position of the memory slot that is always zero.
static const size_t zeroPointer;
/// Starting offset for memory available to the user (aka the contract).
static const size_t generalPurposeMemoryStart;
private: private:
/// Address of the precompiled identity contract. /// Address of the precompiled identity contract.

View File

@ -128,6 +128,7 @@ void ContractCompiler::appendCallValueCheck()
{ {
// Throw if function is not payable but call contained ether. // Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE; m_context << Instruction::CALLVALUE;
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
} }
@ -135,33 +136,13 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
{ {
solAssert(!_contract.isLibrary(), "Tried to initialize library."); solAssert(!_contract.isLibrary(), "Tried to initialize library.");
CompilerContext::LocationSetter locationSetter(m_context, _contract); CompilerContext::LocationSetter locationSetter(m_context, _contract);
// Determine the arguments that are used for the base constructors.
std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
for (ContractDefinition const* contract: bases)
{
if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers())
{
auto baseContract = dynamic_cast<ContractDefinition const*>(
modifier->name()->annotation().referencedDeclaration);
if (baseContract)
if (m_baseArguments.count(baseContract->constructor()) == 0)
m_baseArguments[baseContract->constructor()] = &modifier->arguments();
}
for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts()) m_baseArguments = &_contract.annotation().baseConstructorArguments;
{
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
base->name().annotation().referencedDeclaration
);
solAssert(baseContract, "");
if (m_baseArguments.count(baseContract->constructor()) == 0)
m_baseArguments[baseContract->constructor()] = &base->arguments();
}
}
// Initialization of state variables in base-to-derived order. // Initialization of state variables in base-to-derived order.
for (ContractDefinition const* contract: boost::adaptors::reverse(bases)) for (ContractDefinition const* contract: boost::adaptors::reverse(
_contract.annotation().linearizedBaseContracts
))
initializeStateVariables(*contract); initializeStateVariables(*contract);
if (FunctionDefinition const* constructor = _contract.constructor()) if (FunctionDefinition const* constructor = _contract.constructor())
@ -235,9 +216,16 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc
FunctionType constructorType(_constructor); FunctionType constructorType(_constructor);
if (!constructorType.parameterTypes().empty()) if (!constructorType.parameterTypes().empty())
{ {
solAssert(m_baseArguments.count(&_constructor), ""); solAssert(m_baseArguments, "");
std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor]; solAssert(m_baseArguments->count(&_constructor), "");
std::vector<ASTPointer<Expression>> const* arguments = nullptr;
ASTNode const* baseArgumentNode = m_baseArguments->at(&_constructor);
if (auto inheritanceSpecifier = dynamic_cast<InheritanceSpecifier const*>(baseArgumentNode))
arguments = inheritanceSpecifier->arguments();
else if (auto modifierInvocation = dynamic_cast<ModifierInvocation const*>(baseArgumentNode))
arguments = modifierInvocation->arguments();
solAssert(arguments, ""); solAssert(arguments, "");
solAssert(arguments->size() == constructorType.parameterTypes().size(), "");
for (unsigned i = 0; i < arguments->size(); ++i) for (unsigned i = 0; i < arguments->size(); ++i)
compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]); compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]);
} }
@ -278,9 +266,10 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
m_context.appendProgramSize(); m_context.appendProgramSize();
m_context << Instruction::DUP4 << Instruction::CODECOPY; m_context << Instruction::DUP4 << Instruction::CODECOPY;
m_context << Instruction::DUP2 << Instruction::ADD; m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::DUP1;
CompilerUtils(m_context).storeFreeMemoryPointer(); CompilerUtils(m_context).storeFreeMemoryPointer();
// stack: <memptr> // stack: <memptr>
appendCalldataUnpacker(FunctionType(_constructor).parameterTypes(), true); CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
} }
_constructor.accept(*this); _constructor.accept(*this);
} }
@ -339,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP; m_context << Instruction::STOP;
} }
else else
// TODO: error message here?
m_context.appendRevert(); m_context.appendRevert();
for (auto const& it: interfaceFunctions) for (auto const& it: interfaceFunctions)
@ -367,7 +357,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
{ {
// Parameter for calldataUnpacker // Parameter for calldataUnpacker
m_context << CompilerUtils::dataStartOffset; m_context << CompilerUtils::dataStartOffset;
appendCalldataUnpacker(functionType->parameterTypes()); m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB;
CompilerUtils(m_context).abiDecode(functionType->parameterTypes());
} }
m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration())); m_context.appendJumpTo(m_context.functionEntryLabel(functionType->declaration()));
m_context << returnTag; m_context << returnTag;
@ -382,105 +373,6 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
} }
} }
void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory)
{
// We do not check the calldata size, everything is zero-padded
if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
{
// Use the new JULIA-based decoding function
auto stackHeightBefore = m_context.stackHeight();
CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory);
solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, "");
return;
}
//@todo this does not yet support nested dynamic arrays
// Retain the offset pointer as base_offset, the point from which the data offsets are computed.
m_context << Instruction::DUP1;
for (TypePointer const& parameterType: _typeParameters)
{
// stack: v1 v2 ... v(k-1) base_offset current_offset
TypePointer type = parameterType->decodingType();
solUnimplementedAssert(type, "No decoding type found.");
if (type->category() == Type::Category::Array)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
if (_fromMemory)
{
solUnimplementedAssert(
arrayType.baseType()->isValueType(),
"Nested memory arrays not yet implemented here."
);
// @todo If base type is an array or struct, it is still calldata-style encoded, so
// we would have to convert it like below.
solAssert(arrayType.location() == DataLocation::Memory, "");
if (arrayType.isDynamicallySized())
{
// compute data pointer
m_context << Instruction::DUP1 << Instruction::MLOAD;
m_context << Instruction::DUP3 << Instruction::ADD;
m_context << Instruction::SWAP2 << Instruction::SWAP1;
m_context << u256(0x20) << Instruction::ADD;
}
else
{
m_context << Instruction::SWAP1 << Instruction::DUP2;
m_context << u256(arrayType.calldataEncodedSize(true)) << Instruction::ADD;
}
}
else
{
// first load from calldata and potentially convert to memory if arrayType is memory
TypePointer calldataType = arrayType.copyForLocation(DataLocation::CallData, false);
if (calldataType->isDynamicallySized())
{
// put on stack: data_pointer length
CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
// stack: base_offset data_offset next_pointer
m_context << Instruction::SWAP1 << Instruction::DUP3 << Instruction::ADD;
// stack: base_offset next_pointer data_pointer
// retrieve length
CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
// stack: base_offset next_pointer length data_pointer
m_context << Instruction::SWAP2;
// stack: base_offset data_pointer length next_pointer
}
else
{
// leave the pointer on the stack
m_context << Instruction::DUP1;
m_context << u256(calldataType->calldataEncodedSize()) << Instruction::ADD;
}
if (arrayType.location() == DataLocation::Memory)
{
// stack: base_offset calldata_ref [length] next_calldata
// copy to memory
// move calldata type up again
CompilerUtils(m_context).moveIntoStack(calldataType->sizeOnStack());
CompilerUtils(m_context).convertType(*calldataType, arrayType, false, false, true);
// fetch next pointer again
CompilerUtils(m_context).moveToStackTop(arrayType.sizeOnStack());
}
// move base_offset up
CompilerUtils(m_context).moveToStackTop(1 + arrayType.sizeOnStack());
m_context << Instruction::SWAP1;
}
}
else
{
solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString());
CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true);
CompilerUtils(m_context).moveToStackTop(1 + type->sizeOnStack());
m_context << Instruction::SWAP1;
}
// stack: v1 v2 ... v(k-1) v(k) base_offset mem_offset
}
m_context << Instruction::POP << Instruction::POP;
}
void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary) void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary)
{ {
CompilerUtils utils(m_context); CompilerUtils utils(m_context);
@ -1002,15 +894,21 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode(); appendModifierOrFunctionCode();
else else
{ {
ModifierDefinition const& modifier = m_context.functionModifier(modifierInvocation->name()->name()); ModifierDefinition const& nonVirtualModifier = dynamic_cast<ModifierDefinition const&>(
*modifierInvocation->name()->annotation().referencedDeclaration
);
ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier);
CompilerContext::LocationSetter locationSetter(m_context, modifier); CompilerContext::LocationSetter locationSetter(m_context, modifier);
solAssert(modifier.parameters().size() == modifierInvocation->arguments().size(), ""); std::vector<ASTPointer<Expression>> const& modifierArguments =
modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector<ASTPointer<Expression>>();
solAssert(modifier.parameters().size() == modifierArguments.size(), "");
for (unsigned i = 0; i < modifier.parameters().size(); ++i) for (unsigned i = 0; i < modifier.parameters().size(); ++i)
{ {
m_context.addVariable(*modifier.parameters()[i]); m_context.addVariable(*modifier.parameters()[i]);
addedVariables.push_back(modifier.parameters()[i].get()); addedVariables.push_back(modifier.parameters()[i].get());
compileExpression( compileExpression(
*modifierInvocation->arguments()[i], *modifierArguments[i],
modifier.parameters()[i]->annotation().type modifier.parameters()[i]->annotation().type
); );
} }

View File

@ -90,10 +90,6 @@ private:
void appendDelegatecallCheck(); void appendDelegatecallCheck();
void appendFunctionSelector(ContractDefinition const& _contract); void appendFunctionSelector(ContractDefinition const& _contract);
void appendCallValueCheck(); void appendCallValueCheck();
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data.
/// Expects source offset on the stack, which is removed.
void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false);
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
void registerStateVariables(ContractDefinition const& _contract); void registerStateVariables(ContractDefinition const& _contract);
@ -139,7 +135,7 @@ private:
FunctionDefinition const* m_currentFunction = nullptr; FunctionDefinition const* m_currentFunction = nullptr;
unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order // arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments; std::map<FunctionDefinition const*, ASTNode const*> const* m_baseArguments;
}; };
} }

View File

@ -33,6 +33,8 @@
#include <libsolidity/codegen/LValue.h> #include <libsolidity/codegen/LValue.h>
#include <libevmasm/GasMeter.h> #include <libevmasm/GasMeter.h>
#include <libdevcore/Whiskers.h>
using namespace std; using namespace std;
namespace dev namespace dev
@ -139,8 +141,8 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
utils().popStackSlots(paramTypes.size() - 1); utils().popStackSlots(paramTypes.size() - 1);
} }
unsigned retSizeOnStack = 0; unsigned retSizeOnStack = 0;
solAssert(accessorType.returnParameterTypes().size() >= 1, ""); auto returnTypes = accessorType.returnParameterTypes();
auto const& returnTypes = accessorType.returnParameterTypes(); solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
{ {
// remove offset // remove offset
@ -518,7 +520,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[i]->accept(*this); arguments[i]->accept(*this);
utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]); utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]);
} }
_functionCall.expression().accept(*this);
{
bool shortcutTaken = false;
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{
// Do not directly visit the identifier, because this way, we can avoid
// the runtime entry label to be created at the creation time context.
CompilerContext::LocationSetter locationSetter2(m_context, *identifier);
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false);
shortcutTaken = true;
}
if (!shortcutTaken)
_functionCall.expression().accept(*this);
}
unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes()); unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes());
if (function.bound()) if (function.bound())
{ {
@ -592,7 +610,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE; m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalRevert(); // TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
if (function.valueSet()) if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP; m_context << swapInstruction(1) << Instruction::POP;
break; break;
@ -654,8 +673,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer) if (function.kind() == FunctionType::Kind::Transfer)
{ {
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
// TODO: bubble up here, but might also be different error.
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(); m_context.appendConditionalRevert(true);
} }
break; break;
case FunctionType::Kind::Selfdestruct: case FunctionType::Kind::Selfdestruct:
@ -664,8 +684,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT; m_context << Instruction::SELFDESTRUCT;
break; break;
case FunctionType::Kind::Revert: case FunctionType::Kind::Revert:
m_context.appendRevert(); {
if (!arguments.empty())
{
// function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, "");
arguments.front()->accept(*this);
utils().revertWithStringData(*arguments.front()->annotation().type);
}
else
m_context.appendRevert();
break; break;
}
case FunctionType::Kind::SHA3: case FunctionType::Kind::SHA3:
{ {
TypePointers argumentTypes; TypePointers argumentTypes;
@ -805,24 +836,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
function.kind() == FunctionType::Kind::ArrayPush ? function.kind() == FunctionType::Kind::ArrayPush ?
make_shared<ArrayType>(DataLocation::Storage, paramType) : make_shared<ArrayType>(DataLocation::Storage, paramType) :
make_shared<ArrayType>(DataLocation::Storage); make_shared<ArrayType>(DataLocation::Storage);
// get the current length
ArrayUtils(m_context).retrieveLength(*arrayType);
m_context << Instruction::DUP1;
// stack: ArrayReference currentLength currentLength
m_context << u256(1) << Instruction::ADD;
// stack: ArrayReference currentLength newLength
m_context << Instruction::DUP3 << Instruction::DUP2;
ArrayUtils(m_context).resizeDynamicArray(*arrayType);
m_context << Instruction::SWAP2 << Instruction::SWAP1;
// stack: newLength ArrayReference oldLength
ArrayUtils(m_context).accessIndex(*arrayType, false);
// stack: newLength storageSlot slotOffset // stack: ArrayReference
arguments[0]->accept(*this); arguments[0]->accept(*this);
TypePointer const& argType = arguments[0]->annotation().type;
// stack: ArrayReference argValue
utils().moveToStackTop(argType->sizeOnStack(), 1);
// stack: argValue ArrayReference
m_context << Instruction::DUP1;
ArrayUtils(m_context).incrementDynamicArraySize(*arrayType);
// stack: argValue ArrayReference newLength
m_context << Instruction::SWAP1;
// stack: argValue newLength ArrayReference
m_context << u256(1) << Instruction::DUP3 << Instruction::SUB;
// stack: argValue newLength ArrayReference (newLength-1)
ArrayUtils(m_context).accessIndex(*arrayType, false);
// stack: argValue newLength storageSlot slotOffset
utils().moveToStackTop(3, argType->sizeOnStack());
// stack: newLength storageSlot slotOffset argValue // stack: newLength storageSlot slotOffset argValue
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
solAssert(type, ""); solAssert(type, "");
utils().convertType(*arguments[0]->annotation().type, *type); utils().convertType(*argType, *type);
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset // stack: newLength argValue storageSlot slotOffset
@ -834,8 +868,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
} }
case FunctionType::Kind::ObjectCreation: case FunctionType::Kind::ObjectCreation:
{ {
// Will allocate at the end of memory (MSIZE) and not write at all unless the base
// type is dynamically sized.
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type); ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_functionCall.annotation().type);
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
solAssert(arguments.size() == 1, ""); solAssert(arguments.size() == 1, "");
@ -845,15 +877,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().convertType(*arguments[0]->annotation().type, IntegerType(256)); utils().convertType(*arguments[0]->annotation().type, IntegerType(256));
// Stack: requested_length // Stack: requested_length
// Allocate at max(MSIZE, freeMemoryPointer)
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
m_context << Instruction::DUP1 << Instruction::MSIZE;
m_context << Instruction::LT;
auto initialise = m_context.appendConditionalJump();
// Free memory pointer does not point to empty memory, use MSIZE.
m_context << Instruction::POP;
m_context << Instruction::MSIZE;
m_context << initialise;
// Stack: requested_length memptr // Stack: requested_length memptr
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
@ -878,13 +902,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Check if length is zero // Check if length is zero
m_context << Instruction::DUP1 << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::ISZERO;
auto skipInit = m_context.appendConditionalJump(); auto skipInit = m_context.appendConditionalJump();
// Always initialize because the free memory pointer might point at
// We only have to initialise if the base type is a not a value type. // a dirty memory area.
if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get())) m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
{ utils().zeroInitialiseMemoryArray(arrayType);
m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
utils().zeroInitialiseMemoryArray(arrayType);
}
m_context << skipInit; m_context << skipInit;
m_context << Instruction::POP; m_context << Instruction::POP;
break; break;
@ -894,16 +915,130 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
arguments.front()->accept(*this); arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
if (arguments.size() > 1)
{
// Users probably expect the second argument to be evaluated
// even if the condition is false, as would be the case for an actual
// function call.
solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, "");
arguments.at(1)->accept(*this);
utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack());
}
// Stack: <error string (unconverted)> <condition>
// jump if condition was met // jump if condition was met
m_context << Instruction::ISZERO << Instruction::ISZERO; m_context << Instruction::ISZERO << Instruction::ISZERO;
auto success = m_context.appendConditionalJump(); auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert) if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error // condition was not met, flag an error
m_context.appendInvalid(); m_context.appendInvalid();
else if (arguments.size() > 1)
utils().revertWithStringData(*arguments.at(1)->annotation().type);
else else
m_context.appendRevert(); m_context.appendRevert();
// the success branch // the success branch
m_context << success; m_context << success;
if (arguments.size() > 1)
utils().popStackElement(*arguments.at(1)->annotation().type);
break;
}
case FunctionType::Kind::ABIEncode:
case FunctionType::Kind::ABIEncodePacked:
case FunctionType::Kind::ABIEncodeWithSelector:
case FunctionType::Kind::ABIEncodeWithSignature:
{
bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked;
bool const hasSelectorOrSignature =
function.kind() == FunctionType::Kind::ABIEncodeWithSelector ||
function.kind() == FunctionType::Kind::ABIEncodeWithSignature;
TypePointers argumentTypes;
TypePointers targetTypes;
for (unsigned i = 0; i < arguments.size(); ++i)
{
arguments[i]->accept(*this);
// Do not keep the selector as part of the ABI encoded args
if (!hasSelectorOrSignature || i > 0)
argumentTypes.push_back(arguments[i]->annotation().type);
}
utils().fetchFreeMemoryPointer();
// stack now: [<selector>] <arg1> .. <argN> <free_mem>
// adjust by 32(+4) bytes to accommodate the length(+selector)
m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD;
// stack now: [<selector>] <arg1> .. <argN> <data_encoding_area_start>
if (isPacked)
{
solAssert(!function.padArguments(), "");
utils().packedEncode(argumentTypes, TypePointers());
}
else
{
solAssert(function.padArguments(), "");
utils().abiEncode(argumentTypes, TypePointers());
}
utils().fetchFreeMemoryPointer();
// stack: [<selector>] <data_encoding_area_end> <bytes_memory_ptr>
// size is end minus start minus length slot
m_context.appendInlineAssembly(R"({
mstore(mem_ptr, sub(sub(mem_end, mem_ptr), 0x20))
})", {"mem_end", "mem_ptr"});
m_context << Instruction::SWAP1;
utils().storeFreeMemoryPointer();
// stack: [<selector>] <memory ptr>
if (hasSelectorOrSignature)
{
// stack: <selector> <memory pointer>
solAssert(arguments.size() >= 1, "");
TypePointer const& selectorType = arguments[0]->annotation().type;
utils().moveIntoStack(selectorType->sizeOnStack());
TypePointer dataOnStack = selectorType;
// stack: <memory pointer> <selector>
if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature)
{
// hash the signature
if (auto const* stringType = dynamic_cast<StringLiteralType const*>(selectorType.get()))
{
FixedHash<4> hash(dev::keccak256(stringType->value()));
m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32));
dataOnStack = make_shared<FixedBytesType>(4);
}
else
{
utils().fetchFreeMemoryPointer();
// stack: <memory pointer> <selector> <free mem ptr>
utils().packedEncode(TypePointers{selectorType}, TypePointers());
utils().toSizeAfterFreeMemoryPointer();
m_context << Instruction::KECCAK256;
// stack: <memory pointer> <hash>
dataOnStack = make_shared<FixedBytesType>(32);
}
}
else
{
solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, "");
}
utils().convertType(*dataOnStack, FixedBytesType(4), true);
// stack: <memory pointer> <selector>
// load current memory, mask and combine the selector
string mask = formatNumber((u256(-1) >> 32));
m_context.appendInlineAssembly(R"({
let data_start := add(mem_ptr, 0x20)
let data := mload(data_start)
let mask := )" + mask + R"(
mstore(data_start, or(and(data, mask), selector))
})", {"mem_ptr", "selector"});
m_context << Instruction::POP;
}
// stack now: <memory pointer>
break; break;
} }
case FunctionType::Kind::GasLeft: case FunctionType::Kind::GasLeft:
@ -1147,6 +1282,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
else if (member == "sig") else if (member == "sig")
m_context << u256(0) << Instruction::CALLDATALOAD m_context << u256(0) << Instruction::CALLDATALOAD
<< (u256(0xffffffff) << (256 - 32)) << Instruction::AND; << (u256(0xffffffff) << (256 - 32)) << Instruction::AND;
else if (member == "blockhash")
{
}
else else
solAssert(false, "Unknown magic member."); solAssert(false, "Unknown magic member.");
break; break;
@ -1356,6 +1494,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
} }
} }
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
// If the identifier is called right away, this code is executed in visit(FunctionCall...), because
// we want to avoid having a reference to the runtime function entry point in the
// constructor context, since this would force the compiler to include unreferenced
// internal functions in the runtime contex.
utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration)) else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier)); appendVariable(*variable, static_cast<Expression const&>(_identifier));
@ -1615,15 +1757,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
m_context.experimentalFeatureActive(ExperimentalFeature::V050) && m_context.experimentalFeatureActive(ExperimentalFeature::V050) &&
m_context.evmVersion().hasStaticCall(); m_context.evmVersion().hasStaticCall();
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0; unsigned retSize = 0;
TypePointers returnTypes;
if (returnSuccessCondition) if (returnSuccessCondition)
retSize = 0; // return value actually is success condition retSize = 0; // return value actually is success condition
else if (haveReturndatacopy)
returnTypes = _functionType.returnParameterTypes();
else else
for (auto const& retType: _functionType.returnParameterTypes()) returnTypes = _functionType.returnParameterTypesWithoutDynamicTypes();
bool dynamicReturnSize = false;
for (auto const& retType: returnTypes)
if (retType->isDynamicallyEncoded())
{ {
solAssert(!retType->isDynamicallySized(), "Unable to return dynamic type from external call."); solAssert(haveReturndatacopy, "");
retSize += retType->calldataEncodedSize(); dynamicReturnSize = true;
retSize = 0;
break;
} }
else
retSize += retType->calldataEncodedSize();
// Evaluate arguments. // Evaluate arguments.
TypePointers argumentTypes; TypePointers argumentTypes;
@ -1755,6 +1909,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{ {
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
existenceChecked = true; existenceChecked = true;
} }
@ -1797,7 +1952,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{ {
//Propagate error condition (if CALL pushes 0 on stack). //Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(); m_context.appendConditionalRevert(true);
} }
utils().popStackSlots(remainsSize); utils().popStackSlots(remainsSize);
@ -1821,20 +1976,42 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
m_context << Instruction::SUB << Instruction::MLOAD; m_context << Instruction::SUB << Instruction::MLOAD;
} }
else if (!_functionType.returnParameterTypes().empty()) else if (!returnTypes.empty())
{ {
utils().fetchFreeMemoryPointer(); utils().fetchFreeMemoryPointer();
bool memoryNeeded = false; // Stack: return_data_start
for (auto const& retType: _functionType.returnParameterTypes())
{ // The old decoder did not allocate any memory (i.e. did not touch the free
utils().loadFromMemoryDynamic(*retType, false, true, true); // memory pointer), but kept references to the return data for
if (dynamic_cast<ReferenceType const*>(retType.get())) // (statically-sized) arrays
memoryNeeded = true; bool needToUpdateFreeMemoryPtr = false;
} if (dynamicReturnSize || m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
if (memoryNeeded) needToUpdateFreeMemoryPtr = true;
utils().storeFreeMemoryPointer();
else else
m_context << Instruction::POP; for (auto const& retType: returnTypes)
if (dynamic_cast<ReferenceType const*>(retType.get()))
needToUpdateFreeMemoryPtr = true;
// Stack: return_data_start
if (dynamicReturnSize)
{
solAssert(haveReturndatacopy, "");
m_context.appendInlineAssembly("{ returndatacopy(return_data_start, 0, returndatasize()) }", {"return_data_start"});
}
else
solAssert(retSize > 0, "");
// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
// This ensures it can catch badly formatted input from external calls.
m_context << (haveReturndatacopy ? eth::AssemblyItem(Instruction::RETURNDATASIZE) : u256(retSize));
// Stack: return_data_start return_data_size
if (needToUpdateFreeMemoryPtr)
m_context.appendInlineAssembly(R"({
// round size to the next multiple of 32
let newMem := add(start, and(add(size, 0x1f), not(0x1f)))
mstore(0x40, newMem)
})", {"start", "size"});
utils().abiDecode(returnTypes, true, true);
} }
} }

View File

@ -205,7 +205,7 @@ void SMTChecker::endVisit(Assignment const& _assignment)
_assignment.location(), _assignment.location(),
"Assertion checker does not yet implement compound assignment." "Assertion checker does not yet implement compound assignment."
); );
else if (_assignment.annotation().type->category() != Type::Category::Integer) else if (!SSAVariable::isSupportedType(_assignment.annotation().type->category()))
m_errorReporter.warning( m_errorReporter.warning(
_assignment.location(), _assignment.location(),
"Assertion checker does not yet implement type " + _assignment.annotation().type->toString() "Assertion checker does not yet implement type " + _assignment.annotation().type->toString()
@ -266,14 +266,15 @@ void SMTChecker::endVisit(UnaryOperation const& _op)
{ {
case Token::Not: // ! case Token::Not: // !
{ {
solAssert(_op.annotation().type->category() == Type::Category::Bool, ""); solAssert(SSAVariable::isBool(_op.annotation().type->category()), "");
defineExpr(_op, !expr(_op.subExpression())); defineExpr(_op, !expr(_op.subExpression()));
break; break;
} }
case Token::Inc: // ++ (pre- or postfix) case Token::Inc: // ++ (pre- or postfix)
case Token::Dec: // -- (pre- or postfix) case Token::Dec: // -- (pre- or postfix)
{ {
solAssert(_op.annotation().type->category() == Type::Category::Integer, "");
solAssert(SSAVariable::isInteger(_op.annotation().type->category()), "");
solAssert(_op.subExpression().annotation().lValueRequested, ""); solAssert(_op.subExpression().annotation().lValueRequested, "");
if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression())) if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
{ {
@ -370,7 +371,7 @@ void SMTChecker::endVisit(Identifier const& _identifier)
{ {
// Will be translated as part of the node that requested the lvalue. // Will be translated as part of the node that requested the lvalue.
} }
else if (SSAVariable::supportedType(_identifier.annotation().type.get())) else if (SSAVariable::isSupportedType(_identifier.annotation().type->category()))
defineExpr(_identifier, currentValue(*decl)); defineExpr(_identifier, currentValue(*decl));
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{ {
@ -444,21 +445,37 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
void SMTChecker::compareOperation(BinaryOperation const& _op) void SMTChecker::compareOperation(BinaryOperation const& _op)
{ {
solAssert(_op.annotation().commonType, ""); solAssert(_op.annotation().commonType, "");
if (_op.annotation().commonType->category() == Type::Category::Integer) if (SSAVariable::isSupportedType(_op.annotation().commonType->category()))
{ {
smt::Expression left(expr(_op.leftExpression())); smt::Expression left(expr(_op.leftExpression()));
smt::Expression right(expr(_op.rightExpression())); smt::Expression right(expr(_op.rightExpression()));
Token::Value op = _op.getOperator(); Token::Value op = _op.getOperator();
smt::Expression value = ( shared_ptr<smt::Expression> value;
op == Token::Equal ? (left == right) : if (SSAVariable::isInteger(_op.annotation().commonType->category()))
op == Token::NotEqual ? (left != right) : {
op == Token::LessThan ? (left < right) : value = make_shared<smt::Expression>(
op == Token::LessThanOrEqual ? (left <= right) : op == Token::Equal ? (left == right) :
op == Token::GreaterThan ? (left > right) : op == Token::NotEqual ? (left != right) :
/*op == Token::GreaterThanOrEqual*/ (left >= right) op == Token::LessThan ? (left < right) :
); op == Token::LessThanOrEqual ? (left <= right) :
op == Token::GreaterThan ? (left > right) :
/*op == Token::GreaterThanOrEqual*/ (left >= right)
);
}
else // Bool
{
solAssert(SSAVariable::isBool(_op.annotation().commonType->category()), "");
value = make_shared<smt::Expression>(
op == Token::Equal ? (left == right) :
op == Token::NotEqual ? (left != right) :
op == Token::LessThan ? (!left && right) :
op == Token::LessThanOrEqual ? (!left || right) :
op == Token::GreaterThan ? (left && !right) :
/*op == Token::GreaterThanOrEqual*/ (left || !right)
);
}
// TODO: check that other values for op are not possible. // TODO: check that other values for op are not possible.
defineExpr(_op, value); defineExpr(_op, *value);
} }
else else
m_errorReporter.warning( m_errorReporter.warning(
@ -728,10 +745,10 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm
bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{ {
if (SSAVariable::supportedType(_varDecl.type().get())) if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{ {
solAssert(m_variables.count(&_varDecl) == 0, ""); solAssert(m_variables.count(&_varDecl) == 0, "");
m_variables.emplace(&_varDecl, SSAVariable(&_varDecl, *m_interface)); m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
return true; return true;
} }
else else

View File

@ -17,6 +17,7 @@
#include <libsolidity/formal/SSAVariable.h> #include <libsolidity/formal/SSAVariable.h>
#include <libsolidity/formal/SymbolicBoolVariable.h>
#include <libsolidity/formal/SymbolicIntVariable.h> #include <libsolidity/formal/SymbolicIntVariable.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
@ -26,23 +27,35 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
SSAVariable::SSAVariable( SSAVariable::SSAVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
) )
{ {
resetIndex(); resetIndex();
if (dynamic_cast<IntegerType const*>(_decl->type().get())) if (isInteger(_decl.type()->category()))
m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface); m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface);
else if (isBool(_decl.type()->category()))
m_symbolicVar = make_shared<SymbolicBoolVariable>(_decl, _interface);
else else
{ {
solAssert(false, ""); solAssert(false, "");
} }
} }
bool SSAVariable::supportedType(Type const* _decl) bool SSAVariable::isSupportedType(Type::Category _category)
{ {
return dynamic_cast<IntegerType const*>(_decl); return isInteger(_category) || isBool(_category);
}
bool SSAVariable::isInteger(Type::Category _category)
{
return _category == Type::Category::Integer;
}
bool SSAVariable::isBool(Type::Category _category)
{
return _category == Type::Category::Bool;
} }
void SSAVariable::resetIndex() void SSAVariable::resetIndex()

View File

@ -37,7 +37,7 @@ public:
/// @param _decl Used to determine the type and forwarded to the symbolic var. /// @param _decl Used to determine the type and forwarded to the symbolic var.
/// @param _interface Forwarded to the symbolic var such that it can give constraints to the solver. /// @param _interface Forwarded to the symbolic var such that it can give constraints to the solver.
SSAVariable( SSAVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
); );
@ -68,8 +68,10 @@ public:
void setZeroValue(); void setZeroValue();
void setUnknownValue(); void setUnknownValue();
/// So far Int is supported. /// So far Int and Bool are supported.
static bool supportedType(Type const* _decl); static bool isSupportedType(Type::Category _category);
static bool isInteger(Type::Category _category);
static bool isBool(Type::Category _category);
private: private:
smt::Expression valueAtSequence(int _seq) const smt::Expression valueAtSequence(int _seq) const

View File

@ -46,7 +46,8 @@ enum class Sort
{ {
Int, Int,
Bool, Bool,
IntIntFun // Function of one Int returning a single Int IntIntFun, // Function of one Int returning a single Int
IntBoolFun // Function of one Int returning a single Bool
}; };
/// C++ representation of an SMTLIB2 expression. /// C++ representation of an SMTLIB2 expression.
@ -132,10 +133,22 @@ public:
Expression operator()(Expression _a) const Expression operator()(Expression _a) const
{ {
solAssert( solAssert(
sort == Sort::IntIntFun && arguments.empty(), arguments.empty(),
"Attempted function application to non-function." "Attempted function application to non-function."
); );
return Expression(name, _a, Sort::Int); switch (sort)
{
case Sort::IntIntFun:
return Expression(name, _a, Sort::Int);
case Sort::IntBoolFun:
return Expression(name, _a, Sort::Bool);
default:
solAssert(
false,
"Attempted function application to invalid type."
);
break;
}
} }
std::string const name; std::string const name;
@ -167,9 +180,18 @@ public:
virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain) virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
{ {
solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported."); solAssert(_domain == Sort::Int, "Function sort not supported.");
// Subclasses should do something here // Subclasses should do something here
return Expression(std::move(_name), {}, Sort::IntIntFun); switch (_codomain)
{
case Sort::Int:
return Expression(std::move(_name), {}, Sort::IntIntFun);
case Sort::Bool:
return Expression(std::move(_name), {}, Sort::IntBoolFun);
default:
solAssert(false, "Function sort not supported.");
break;
}
} }
virtual Expression newInteger(std::string _name) virtual Expression newInteger(std::string _name)
{ {

View File

@ -0,0 +1,47 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#include <libsolidity/formal/SymbolicBoolVariable.h>
#include <libsolidity/ast/AST.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
SymbolicBoolVariable::SymbolicBoolVariable(
Declaration const& _decl,
smt::SolverInterface&_interface
):
SymbolicVariable(_decl, _interface)
{
solAssert(m_declaration.type()->category() == Type::Category::Bool, "");
}
smt::Expression SymbolicBoolVariable::valueAtSequence(int _seq) const
{
return m_interface.newBool(uniqueSymbol(_seq));
}
void SymbolicBoolVariable::setZeroValue(int _seq)
{
m_interface.addAssertion(valueAtSequence(_seq) == smt::Expression(false));
}
void SymbolicBoolVariable::setUnknownValue(int)
{
}

View File

@ -0,0 +1,50 @@
/*
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/formal/SymbolicVariable.h>
#include <libsolidity/ast/Types.h>
namespace dev
{
namespace solidity
{
/**
* Specialization of SymbolicVariable for Bool
*/
class SymbolicBoolVariable: public SymbolicVariable
{
public:
SymbolicBoolVariable(
Declaration const& _decl,
smt::SolverInterface& _interface
);
/// Sets the var to false.
void setZeroValue(int _seq);
/// Does nothing since the SMT solver already knows the valid values.
void setUnknownValue(int _seq);
protected:
smt::Expression valueAtSequence(int _seq) const;
};
}
}

View File

@ -24,13 +24,17 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
SymbolicIntVariable::SymbolicIntVariable( SymbolicIntVariable::SymbolicIntVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
): ):
SymbolicVariable(_decl, _interface) SymbolicVariable(_decl, _interface)
{ {
solAssert(m_declaration->type()->category() == Type::Category::Integer, ""); solAssert(m_declaration.type()->category() == Type::Category::Integer, "");
m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int)); }
smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const
{
return m_interface.newInteger(uniqueSymbol(_seq));
} }
void SymbolicIntVariable::setZeroValue(int _seq) void SymbolicIntVariable::setZeroValue(int _seq)
@ -40,7 +44,7 @@ void SymbolicIntVariable::setZeroValue(int _seq)
void SymbolicIntVariable::setUnknownValue(int _seq) void SymbolicIntVariable::setUnknownValue(int _seq)
{ {
auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration->type()); auto const& intType = dynamic_cast<IntegerType const&>(*m_declaration.type());
m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType)); m_interface.addAssertion(valueAtSequence(_seq) >= minValue(intType));
m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType)); m_interface.addAssertion(valueAtSequence(_seq) <= maxValue(intType));
} }

View File

@ -33,7 +33,7 @@ class SymbolicIntVariable: public SymbolicVariable
{ {
public: public:
SymbolicIntVariable( SymbolicIntVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
); );
@ -44,6 +44,9 @@ public:
static smt::Expression minValue(IntegerType const& _t); static smt::Expression minValue(IntegerType const& _t);
static smt::Expression maxValue(IntegerType const& _t); static smt::Expression maxValue(IntegerType const& _t);
protected:
smt::Expression valueAtSequence(int _seq) const;
}; };
} }

View File

@ -24,7 +24,7 @@ using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
SymbolicVariable::SymbolicVariable( SymbolicVariable::SymbolicVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
): ):
m_declaration(_decl), m_declaration(_decl),
@ -32,9 +32,9 @@ SymbolicVariable::SymbolicVariable(
{ {
} }
string SymbolicVariable::uniqueSymbol() const string SymbolicVariable::uniqueSymbol(int _seq) const
{ {
return m_declaration->name() + "_" + to_string(m_declaration->id()); return m_declaration.name() + "_" + to_string(m_declaration.id()) + "_" + to_string(_seq);
} }

View File

@ -37,7 +37,7 @@ class SymbolicVariable
{ {
public: public:
SymbolicVariable( SymbolicVariable(
Declaration const* _decl, Declaration const& _decl,
smt::SolverInterface& _interface smt::SolverInterface& _interface
); );
@ -46,7 +46,7 @@ public:
return valueAtSequence(_seq); return valueAtSequence(_seq);
} }
std::string uniqueSymbol() const; std::string uniqueSymbol(int _seq) const;
/// Sets the var to the default value of its type. /// Sets the var to the default value of its type.
virtual void setZeroValue(int _seq) = 0; virtual void setZeroValue(int _seq) = 0;
@ -55,13 +55,9 @@ public:
virtual void setUnknownValue(int _seq) = 0; virtual void setUnknownValue(int _seq) = 0;
protected: protected:
smt::Expression valueAtSequence(int _seq) const virtual smt::Expression valueAtSequence(int _seq) const = 0;
{
return (*m_expression)(_seq);
}
Declaration const* m_declaration; Declaration const& m_declaration;
std::shared_ptr<smt::Expression> m_expression = nullptr;
smt::SolverInterface& m_interface; smt::SolverInterface& m_interface;
}; };

View File

@ -164,85 +164,94 @@ bool CompilerStack::analyze()
resolveImports(); resolveImports();
bool noErrors = true; bool noErrors = true;
SyntaxChecker syntaxChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
DocStringAnalyser docStringAnalyser(m_errorReporter); try {
for (Source const* source: m_sourceOrder) SyntaxChecker syntaxChecker(m_errorReporter);
if (!docStringAnalyser.analyseDocStrings(*source->ast)) for (Source const* source: m_sourceOrder)
noErrors = false; if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
m_globalContext = make_shared<GlobalContext>(); DocStringAnalyser docStringAnalyser(m_errorReporter);
NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter); for (Source const* source: m_sourceOrder)
for (Source const* source: m_sourceOrder) if (!docStringAnalyser.analyseDocStrings(*source->ast))
if (!resolver.registerDeclarations(*source->ast)) noErrors = false;
return false;
map<string, SourceUnit const*> sourceUnitsByName; m_globalContext = make_shared<GlobalContext>();
for (auto& source: m_sources) NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
sourceUnitsByName[source.first] = source.second.ast.get(); for (Source const* source: m_sourceOrder)
for (Source const* source: m_sourceOrder) if (!resolver.registerDeclarations(*source->ast))
if (!resolver.performImports(*source->ast, sourceUnitsByName)) return false;
return false;
for (Source const* source: m_sourceOrder) map<string, SourceUnit const*> sourceUnitsByName;
for (ASTPointer<ASTNode> const& node: source->ast->nodes()) for (auto& source: m_sources)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) sourceUnitsByName[source.first] = source.second.ast.get();
{ for (Source const* source: m_sourceOrder)
m_globalContext->setCurrentContract(*contract); if (!resolver.performImports(*source->ast, sourceUnitsByName))
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false; return false;
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
if (!resolver.resolveNamesAndTypes(*contract)) return false;
// Note that we now reference contracts by their fully qualified names, and for (Source const* source: m_sourceOrder)
// thus contracts can only conflict if declared in the same source file. This for (ASTPointer<ASTNode> const& node: source->ast->nodes())
// already causes a double-declaration error elsewhere, so we do not report if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
// an error here and instead silently drop any additional contracts we find. {
m_globalContext->setCurrentContract(*contract);
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
if (!resolver.resolveNamesAndTypes(*contract)) return false;
if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end()) // Note that we now reference contracts by their fully qualified names, and
m_contracts[contract->fullyQualifiedName()].contract = contract; // thus contracts can only conflict if declared in the same source file. This
} // already causes a double-declaration error elsewhere, so we do not report
// an error here and instead silently drop any additional contracts we find.
TypeChecker typeChecker(m_evmVersion, m_errorReporter); if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
for (Source const* source: m_sourceOrder) m_contracts[contract->fullyQualifiedName()].contract = contract;
for (ASTPointer<ASTNode> const& node: source->ast->nodes()) }
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!typeChecker.checkTypeRequirements(*contract)) TypeChecker typeChecker(m_evmVersion, m_errorReporter);
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
if (!typeChecker.checkTypeRequirements(*contract))
noErrors = false;
if (noErrors)
{
PostTypeChecker postTypeChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!postTypeChecker.check(*source->ast))
noErrors = false; noErrors = false;
}
if (noErrors) if (noErrors)
{ {
PostTypeChecker postTypeChecker(m_errorReporter); StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
if (!postTypeChecker.check(*source->ast)) if (!staticAnalyzer.analyze(*source->ast))
noErrors = false;
}
if (noErrors)
{
vector<ASTPointer<ASTNode>> ast;
for (Source const* source: m_sourceOrder)
ast.push_back(source->ast);
if (!ViewPureChecker(ast, m_errorReporter).check())
noErrors = false; noErrors = false;
}
if (noErrors)
{
SMTChecker smtChecker(m_errorReporter, m_smtQuery);
for (Source const* source: m_sourceOrder)
smtChecker.analyze(*source->ast);
}
} }
catch(FatalError const&)
if (noErrors)
{ {
StaticAnalyzer staticAnalyzer(m_errorReporter); if (m_errorReporter.errors().empty())
for (Source const* source: m_sourceOrder) throw; // Something is weird here, rather throw again.
if (!staticAnalyzer.analyze(*source->ast)) noErrors = false;
noErrors = false;
}
if (noErrors)
{
vector<ASTPointer<ASTNode>> ast;
for (Source const* source: m_sourceOrder)
ast.push_back(source->ast);
if (!ViewPureChecker(ast, m_errorReporter).check())
noErrors = false;
}
if (noErrors)
{
SMTChecker smtChecker(m_errorReporter, m_smtQuery);
for (Source const* source: m_sourceOrder)
smtChecker.analyze(*source->ast);
} }
if (noErrors) if (noErrors)

View File

@ -49,7 +49,7 @@ public:
static boost::optional<EVMVersion> fromString(std::string const& _version) static boost::optional<EVMVersion> fromString(std::string const& _version)
{ {
for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium()}) for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium(), constantinople()})
if (_version == v.name()) if (_version == v.name())
return v; return v;
return {}; return {};

View File

@ -61,6 +61,9 @@ void ErrorReporter::warning(
void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description) void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
{ {
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type); auto err = make_shared<Error>(_type);
*err << *err <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
@ -71,6 +74,9 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, st
void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description) void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
{ {
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type); auto err = make_shared<Error>(_type);
*err << *err <<
errinfo_sourceLocation(_location) << errinfo_sourceLocation(_location) <<
@ -80,6 +86,37 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
m_errorList.push_back(err); m_errorList.push_back(err);
} }
bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
{
if (_type == Error::Type::Warning)
{
m_warningCount++;
if (m_warningCount == c_maxWarningsAllowed)
{
auto err = make_shared<Error>(Error::Type::Warning);
*err << errinfo_comment("There are more than 256 warnings. Ignoring the rest.");
m_errorList.push_back(err);
}
if (m_warningCount >= c_maxWarningsAllowed)
return true;
}
else
{
m_errorCount++;
if (m_errorCount > c_maxErrorsAllowed)
{
auto err = make_shared<Error>(Error::Type::Warning);
*err << errinfo_comment("There are more than 256 errors. Aborting.");
m_errorList.push_back(err);
BOOST_THROW_EXCEPTION(FatalError());
}
}
return false;
}
void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description) void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description)
{ {

View File

@ -102,7 +102,16 @@ private:
SourceLocation const& _location = SourceLocation(), SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string()); std::string const& _description = std::string());
// @returns true if error shouldn't be stored
bool checkForExcessiveErrors(Error::Type _type);
ErrorList& m_errorList; ErrorList& m_errorList;
unsigned m_errorCount = 0;
unsigned m_warningCount = 0;
const unsigned c_maxWarningsAllowed = 256;
const unsigned c_maxErrorsAllowed = 256;
}; };

View File

@ -136,12 +136,19 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation(
ExpressionClasses& classes = state->expressionClasses(); ExpressionClasses& classes = state->expressionClasses();
using Id = ExpressionClasses::Id; using Id = ExpressionClasses::Id;
using Ids = vector<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 hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(_signature)))));
Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))}); Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))});
classes.forceEqual(hashValue, Instruction::DIV, Ids{ classes.forceEqual(hashValue, Instruction::DIV, Ids{
calldata, calldata,
classes.find(u256(1) << (8 * 28)) classes.find(u256(1) << 224)
}); });
// lt(calldatasize(), 4) equals to 0 (ignore the shortcut for fallback functions)
classes.forceEqual(
classes.find(u256(0)),
Instruction::LT,
Ids{classes.find(Instruction::CALLDATASIZE), classes.find(u256(4))}
);
} }
PathGasMeter meter(_items, m_evmVersion); PathGasMeter meter(_items, m_evmVersion);

View File

@ -119,21 +119,17 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
return _end; return _end;
} }
auto nameEndPos = firstSpaceOrTab(nameStartPos, _end); auto nameEndPos = firstSpaceOrTab(nameStartPos, _end);
if (nameEndPos == _end)
{
appendError("End of param name not found: " + string(nameStartPos, _end));
return _end;
}
auto paramName = string(nameStartPos, nameEndPos); auto paramName = string(nameStartPos, nameEndPos);
auto descStartPos = skipWhitespace(nameEndPos, _end); auto descStartPos = skipWhitespace(nameEndPos, _end);
if (descStartPos == _end) auto nlPos = find(descStartPos, _end, '\n');
if (descStartPos == nlPos)
{ {
appendError("No description given for param " + paramName); appendError("No description given for param " + paramName);
return _end; return _end;
} }
auto nlPos = find(descStartPos, _end, '\n');
auto paramDesc = string(descStartPos, nlPos); auto paramDesc = string(descStartPos, nlPos);
newTag("param"); newTag("param");
m_lastTag->paramName = paramName; m_lastTag->paramName = paramName;

View File

@ -238,7 +238,10 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition(Token::Value _exp
Token::Value currentTokenValue = m_scanner->currentToken(); Token::Value currentTokenValue = m_scanner->currentToken();
if (currentTokenValue == Token::RBrace) if (currentTokenValue == Token::RBrace)
break; break;
else if (currentTokenValue == Token::Function) else if (
currentTokenValue == Token::Function ||
(currentTokenValue == Token::Identifier && m_scanner->currentLiteral() == "constructor")
)
// This can be a function or a state variable of function type (especially // This can be a function or a state variable of function type (especially
// complicated to distinguish fallback function from function type state variable) // complicated to distinguish fallback function from function type state variable)
subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get()));
@ -283,17 +286,17 @@ ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName()); ASTPointer<UserDefinedTypeName> name(parseUserDefinedTypeName());
vector<ASTPointer<Expression>> arguments; unique_ptr<vector<ASTPointer<Expression>>> arguments;
if (m_scanner->currentToken() == Token::LParen) if (m_scanner->currentToken() == Token::LParen)
{ {
m_scanner->next(); m_scanner->next();
arguments = parseFunctionCallListArguments(); arguments.reset(new vector<ASTPointer<Expression>>(parseFunctionCallListArguments()));
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RParen); expectToken(Token::RParen);
} }
else else
nodeFactory.setEndPositionFromNode(name); nodeFactory.setEndPositionFromNode(name);
return nodeFactory.createNode<InheritanceSpecifier>(name, arguments); return nodeFactory.createNode<InheritanceSpecifier>(name, std::move(arguments));
} }
Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token)
@ -329,15 +332,31 @@ StateMutability Parser::parseStateMutability(Token::Value _token)
return stateMutability; return stateMutability;
} }
Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(
bool _forceEmptyName,
bool _allowModifiers,
ASTString const* _contractName
)
{ {
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
FunctionHeaderParserResult result; FunctionHeaderParserResult result;
expectToken(Token::Function);
if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) result.isConstructor = false;
result.name = make_shared<ASTString>(); // anonymous function
if (m_scanner->currentToken() == Token::Identifier && m_scanner->currentLiteral() == "constructor")
result.isConstructor = true;
else if (m_scanner->currentToken() != Token::Function)
solAssert(false, "Function or constructor expected.");
m_scanner->next();
if (result.isConstructor || _forceEmptyName || m_scanner->currentToken() == Token::LParen)
result.name = make_shared<ASTString>();
else else
result.name = expectIdentifierToken(); result.name = expectIdentifierToken();
if (!result.name->empty() && _contractName && *result.name == *_contractName)
result.isConstructor = true;
VarDeclParserOptions options; VarDeclParserOptions options;
options.allowLocationSpecifier = true; options.allowLocationSpecifier = true;
result.parameters = parseParameterList(options); result.parameters = parseParameterList(options);
@ -346,12 +365,13 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
Token::Value token = m_scanner->currentToken(); Token::Value token = m_scanner->currentToken();
if (_allowModifiers && token == Token::Identifier) if (_allowModifiers && token == Token::Identifier)
{ {
// This can either be a modifier (function declaration) or the name of the // If the name is empty (and this is not a constructor),
// variable (function type name plus variable). // then this can either be a modifier (fallback function declaration)
if ( // or the name of the state variable (function type name plus variable).
if ((result.name->empty() && !result.isConstructor) && (
m_scanner->peekNextToken() == Token::Semicolon || m_scanner->peekNextToken() == Token::Semicolon ||
m_scanner->peekNextToken() == Token::Assign m_scanner->peekNextToken() == Token::Assign
) ))
// Variable declaration, break here. // Variable declaration, break here.
break; break;
else else
@ -361,6 +381,14 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN
{ {
if (result.visibility != Declaration::Visibility::Default) if (result.visibility != Declaration::Visibility::Default)
{ {
// There is the special case of a public state variable of function type.
// Detect this and return early.
if (
(result.visibility == Declaration::Visibility::External || result.visibility == Declaration::Visibility::Internal) &&
result.modifiers.empty() &&
(result.name->empty() && !result.isConstructor)
)
break;
parserError(string( parserError(string(
"Visibility already specified as \"" + "Visibility already specified as \"" +
Declaration::visibilityToString(result.visibility) + Declaration::visibilityToString(result.visibility) +
@ -407,9 +435,10 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
if (m_scanner->currentCommentLiteral() != "") if (m_scanner->currentCommentLiteral() != "")
docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral()); docstring = make_shared<ASTString>(m_scanner->currentCommentLiteral());
FunctionHeaderParserResult header = parseFunctionHeader(false, true); FunctionHeaderParserResult header = parseFunctionHeader(false, true, _contractName);
if ( if (
header.isConstructor ||
!header.modifiers.empty() || !header.modifiers.empty() ||
!header.name->empty() || !header.name->empty() ||
m_scanner->currentToken() == Token::Semicolon || m_scanner->currentToken() == Token::Semicolon ||
@ -426,12 +455,11 @@ ASTPointer<ASTNode> Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(A
} }
else else
m_scanner->next(); // just consume the ';' m_scanner->next(); // just consume the ';'
bool const c_isConstructor = (_contractName && *header.name == *_contractName);
return nodeFactory.createNode<FunctionDefinition>( return nodeFactory.createNode<FunctionDefinition>(
header.name, header.name,
header.visibility, header.visibility,
header.stateMutability, header.stateMutability,
c_isConstructor, header.isConstructor,
docstring, docstring,
header.parameters, header.parameters,
header.modifiers, header.modifiers,
@ -579,8 +607,10 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier) if (_options.allowEmptyName && m_scanner->currentToken() != Token::Identifier)
{ {
identifier = make_shared<ASTString>(""); identifier = make_shared<ASTString>("");
solAssert(type != nullptr, ""); solAssert(!_options.allowVar, ""); // allowEmptyName && allowVar makes no sense
nodeFactory.setEndPositionFromNode(type); if (type)
nodeFactory.setEndPositionFromNode(type);
// if type is null this has already caused an error
} }
else else
identifier = expectIdentifierToken(); identifier = expectIdentifierToken();
@ -683,17 +713,17 @@ ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
ASTPointer<Identifier> name(parseIdentifier()); ASTPointer<Identifier> name(parseIdentifier());
vector<ASTPointer<Expression>> arguments; unique_ptr<vector<ASTPointer<Expression>>> arguments;
if (m_scanner->currentToken() == Token::LParen) if (m_scanner->currentToken() == Token::LParen)
{ {
m_scanner->next(); m_scanner->next();
arguments = parseFunctionCallListArguments(); arguments.reset(new vector<ASTPointer<Expression>>(parseFunctionCallListArguments()));
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
expectToken(Token::RParen); expectToken(Token::RParen);
} }
else else
nodeFactory.setEndPositionFromNode(name); nodeFactory.setEndPositionFromNode(name);
return nodeFactory.createNode<ModifierInvocation>(name, arguments); return nodeFactory.createNode<ModifierInvocation>(name, move(arguments));
} }
ASTPointer<Identifier> Parser::parseIdentifier() ASTPointer<Identifier> Parser::parseIdentifier()
@ -776,6 +806,7 @@ ASTPointer<FunctionTypeName> Parser::parseFunctionType()
RecursionGuard recursionGuard(*this); RecursionGuard recursionGuard(*this);
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
FunctionHeaderParserResult header = parseFunctionHeader(true, false); FunctionHeaderParserResult header = parseFunctionHeader(true, false);
solAssert(!header.isConstructor, "Tried to parse type as constructor.");
return nodeFactory.createNode<FunctionTypeName>( return nodeFactory.createNode<FunctionTypeName>(
header.parameters, header.parameters,
header.returnParameters, header.returnParameters,

View File

@ -56,6 +56,7 @@ private:
/// This struct is shared for parsing a function header and a function type. /// This struct is shared for parsing a function header and a function type.
struct FunctionHeaderParserResult struct FunctionHeaderParserResult
{ {
bool isConstructor;
ASTPointer<ASTString> name; ASTPointer<ASTString> name;
ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> parameters;
ASTPointer<ParameterList> returnParameters; ASTPointer<ParameterList> returnParameters;
@ -73,7 +74,11 @@ private:
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier(); ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token);
StateMutability parseStateMutability(Token::Value _token); StateMutability parseStateMutability(Token::Value _token);
FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); FunctionHeaderParserResult parseFunctionHeader(
bool _forceEmptyName,
bool _allowModifiers,
ASTString const* _contractName = nullptr
);
ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer<ASTNode> parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName);
ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName); ASTPointer<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName);
ASTPointer<StructDefinition> parseStructDefinition(); ASTPointer<StructDefinition> parseStructDefinition();

View File

@ -53,7 +53,7 @@ namespace solidity
void ElementaryTypeNameToken::assertDetails(Token::Value _baseType, unsigned const& _first, unsigned const& _second) void ElementaryTypeNameToken::assertDetails(Token::Value _baseType, unsigned const& _first, unsigned const& _second)
{ {
solAssert(Token::isElementaryTypeName(_baseType), ""); solAssert(Token::isElementaryTypeName(_baseType), "Expected elementary type name: " + string(Token::toString(_baseType)));
if (_baseType == Token::BytesM) if (_baseType == Token::BytesM)
{ {
solAssert(_second == 0, "There should not be a second size argument to type bytesM."); solAssert(_second == 0, "There should not be a second size argument to type bytesM.");

17
scripts/cpp-ethereum/build.sh Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env sh
# Script to build the eth binary from latest develop
# for ubuntu trusty and ubuntu artful.
# Requires docker.
set -e
REPO_ROOT="$(dirname "$0")"/../..
for rel in artful trusty
do
docker build -t eth_$rel -f "$REPO_ROOT"/scripts/cpp-ethereum/eth_$rel.docker .
tmp_container=$(docker create eth_$rel sh)
echo "Built eth ($rel) at $REPO_ROOT/build/eth_$rel"
docker cp ${tmp_container}:/build/eth/eth "$REPO_ROOT"/build/eth_$rel
done

View File

@ -0,0 +1,7 @@
FROM ubuntu:artful
RUN apt update
RUN apt -y install libleveldb-dev cmake g++ git
RUN git clone --recursive https://github.com/ethereum/cpp-ethereum --branch develop --single-branch --depth 1
RUN mkdir /build && cd /build && cmake /cpp-ethereum -DCMAKE_BUILD_TYPE=RelWithDebInfo -DTOOLS=Off -DTESTS=Off
RUN cd /build && make eth

View File

@ -0,0 +1,13 @@
FROM ubuntu:trusty
RUN apt-get update
RUN apt-get -y install software-properties-common python-software-properties
RUN add-apt-repository ppa:ubuntu-toolchain-r/test
RUN apt-get update
RUN apt-get -y install gcc libleveldb-dev git curl make gcc-7 g++-7
RUN ln -sf /usr/bin/gcc-7 /usr/bin/gcc
RUN ln -sf /usr/bin/g++-7 /usr/bin/g++
RUN git clone --recursive https://github.com/ethereum/cpp-ethereum --branch develop --single-branch --depth 1
RUN ./cpp-ethereum/scripts/install_cmake.sh
RUN mkdir /build && cd /build && ~/.local/bin/cmake /cpp-ethereum -DCMAKE_BUILD_TYPE=RelWithDebInfo -DTOOLS=Off -DTESTS=Off
RUN cd /build && make eth

49
scripts/extract_test_cases.py Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/python
#
# This script reads C++ or RST source files and writes all
# multi-line strings into individual files.
# This can be used to extract the Solidity test cases
# into files for e.g. fuzz testing as
# scripts/isolate_tests.py test/libsolidity/*
import sys
import re
import os
import hashlib
from os.path import join
def extract_test_cases(path):
lines = open(path, 'rb').read().splitlines()
inside = False
delimiter = ''
test = ''
ctr = 1
test_name = ''
for l in lines:
if inside:
if l.strip().endswith(')' + delimiter + '";'):
open('%03d_%s.sol' % (ctr, test_name), 'wb').write(test)
ctr += 1
inside = False
test = ''
else:
l = re.sub('^\t\t', '', l)
l = l.replace('\t', ' ')
test += l + '\n'
else:
m = re.search(r'BOOST_AUTO_TEST_CASE\(([^(]*)\)', l.strip())
if m:
test_name = m.group(1)
m = re.search(r'R"([^(]*)\($', l.strip())
if m:
inside = True
delimiter = m.group(1)
if __name__ == '__main__':
path = sys.argv[1]
extract_test_cases(path)

View File

@ -86,10 +86,15 @@ if __name__ == '__main__':
for root, subdirs, files in os.walk(path): for root, subdirs, files in os.walk(path):
if '_build' in subdirs: if '_build' in subdirs:
subdirs.remove('_build') subdirs.remove('_build')
if 'compilationTests' in subdirs:
subdirs.remove('compilationTests')
for f in files: for f in files:
path = join(root, f) path = join(root, f)
if docs: if docs:
cases = extract_docs_cases(path) cases = extract_docs_cases(path)
else: else:
cases = extract_test_cases(path) if f.endswith(".sol"):
cases = [open(path, "r").read()]
else:
cases = extract_test_cases(path)
write_cases(cases) write_cases(cases)

6
scripts/isoltest.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
REPO_ROOT="$(dirname "$0")"/..
exec ${REPO_ROOT}/build/test/tools/isoltest --testpath ${REPO_ROOT}/test

43
scripts/soltest.sh Executable file
View File

@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -e
REPO_ROOT="$(dirname "$0")"/..
USE_DEBUGGER=0
DEBUGGER="gdb --args"
BOOST_OPTIONS=
SOLTEST_OPTIONS=
while [ $# -gt 0 ]
do
case "$1" in
--debugger)
shift
DEBUGGER="$1"
USE_DEBUGGER=1
;;
--debug)
USE_DEBUGGER=1
;;
--boost-options)
shift
BOOST_OPTIONS="${BOOST_OPTIONS} $1"
;;
-t)
shift
BOOST_OPTIONS="${BOOST_OPTIONS} -t $1"
;;
--show-progress | -p)
BOOST_OPTIONS="${BOOST_OPTIONS} $1"
;;
*)
SOLTEST_OPTIONS="${SOLTEST_OPTIONS} $1"
;;
esac
shift
done
if [ "$USE_DEBUGGER" -ne "0" ]; then
DEBUG_PREFIX=${DEBUGGER}
fi
exec ${DEBUG_PREFIX} ${REPO_ROOT}/build/test/soltest ${BOOST_OPTIONS} -- --testpath ${REPO_ROOT}/test ${SOLTEST_OPTIONS}

View File

@ -61,13 +61,13 @@ function download_eth()
mkdir -p /tmp/test mkdir -p /tmp/test
if grep -i trusty /etc/lsb-release >/dev/null 2>&1 if grep -i trusty /etc/lsb-release >/dev/null 2>&1
then then
# built from 1ecff3cac12f0fbbeea3e645f331d5ac026b24d3 at 2018-03-06 # built from 5ac09111bd0b6518365fe956e1bdb97a2db82af1 at 2018-04-05
ETH_BINARY=eth_byzantium_trusty ETH_BINARY=eth_2018-04-05_trusty
ETH_HASH="5432ea81c150e8a3547615bf597cd6dce9e1e27b" ETH_HASH="1e5e178b005e5b51f9d347df4452875ba9b53cc6"
else else
# built from ?? at 2018-02-13 ? # built from 5ac09111bd0b6518365fe956e1bdb97a2db82af1 at 2018-04-05
ETH_BINARY=eth_byzantium_artful ETH_BINARY=eth_2018-04-05_artful
ETH_HASH="e527dd3e3dc17b983529dd7dcfb74a0d3a5aed4e" ETH_HASH="eb2d0df022753bb2b442ba73e565a9babf6828d6"
fi fi
wget -q -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/$ETH_BINARY wget -q -O /tmp/test/eth https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/$ETH_BINARY
test "$(shasum /tmp/test/eth)" = "$ETH_HASH /tmp/test/eth" test "$(shasum /tmp/test/eth)" = "$ETH_HASH /tmp/test/eth"
@ -94,16 +94,23 @@ download_eth
ETH_PID=$(run_eth /tmp/test) ETH_PID=$(run_eth /tmp/test)
progress="--show-progress" progress="--show-progress"
if [ "$CI" ] if [ "$CIRCLECI" ]
then then
progress="" progress=""
fi fi
EVM_VERSIONS="homestead byzantium"
if [ "$CIRCLECI" ] || [ -z "$CI" ]
then
EVM_VERSIONS+=" constantinople"
fi
# And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer # And then run the Solidity unit-tests in the matrix combination of optimizer / no optimizer
# and homestead / byzantium VM, # pointing to that IPC endpoint. # and homestead / byzantium VM, # pointing to that IPC endpoint.
for optimize in "" "--optimize" for optimize in "" "--optimize"
do do
for vm in homestead byzantium for vm in $EVM_VERSIONS
do do
echo "--> Running tests using "$optimize" --evm-version "$vm"..." echo "--> Running tests using "$optimize" --evm-version "$vm"..."
log="" log=""
@ -116,7 +123,7 @@ do
log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt
fi fi
fi fi
"$REPO_ROOT"/build/test/soltest $progress $log -- "$optimize" --evm-version "$vm" --ipcpath /tmp/test/geth.ipc "$REPO_ROOT"/build/test/soltest $progress $log -- --testpath "$REPO_ROOT"/test "$optimize" --evm-version "$vm" --ipcpath /tmp/test/geth.ipc
done done
done done

View File

@ -9,6 +9,6 @@ do
echo -n $x " # " echo -n $x " # "
# This subshell is a workaround to prevent the shell from printing # This subshell is a workaround to prevent the shell from printing
# "Aborted" # "Aborted"
("$REPO"/build/test/solfuzzer < "$x" || true) 2>&1 | head -n 1 ("$REPO"/build/test/tools/solfuzzer < "$x" || true) 2>&1 | head -n 1
done done
) | sort -u -t'#' -k 2 ) | sort -u -t'#' -k 2

View File

@ -116,6 +116,7 @@ static string const g_strStandardJSON = "standard-json";
static string const g_strStrictAssembly = "strict-assembly"; static string const g_strStrictAssembly = "strict-assembly";
static string const g_strPrettyJson = "pretty-json"; static string const g_strPrettyJson = "pretty-json";
static string const g_strVersion = "version"; static string const g_strVersion = "version";
static string const g_strIgnoreMissingFiles = "ignore-missing";
static string const g_argAbi = g_strAbi; static string const g_argAbi = g_strAbi;
static string const g_argPrettyJson = g_strPrettyJson; static string const g_argPrettyJson = g_strPrettyJson;
@ -152,6 +153,7 @@ static string const g_argStandardJSON = g_strStandardJSON;
static string const g_argStrictAssembly = g_strStrictAssembly; static string const g_argStrictAssembly = g_strStrictAssembly;
static string const g_argVersion = g_strVersion; static string const g_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr; static string const g_stdinFileName = g_stdinFileNameStr;
static string const g_argIgnoreMissingFiles = g_strIgnoreMissingFiles;
/// Possible arguments to for --combined-json /// Possible arguments to for --combined-json
static set<string> const g_combinedJsonArgs static set<string> const g_combinedJsonArgs
@ -398,8 +400,9 @@ void CommandLineInterface::handleGasEstimation(string const& _contract)
} }
} }
void CommandLineInterface::readInputFilesAndConfigureRemappings() bool CommandLineInterface::readInputFilesAndConfigureRemappings()
{ {
bool ignoreMissing = m_args.count(g_argIgnoreMissingFiles);
bool addStdin = false; bool addStdin = false;
if (!m_args.count(g_argInputFile)) if (!m_args.count(g_argInputFile))
addStdin = true; addStdin = true;
@ -416,13 +419,27 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings()
auto infile = boost::filesystem::path(path); auto infile = boost::filesystem::path(path);
if (!boost::filesystem::exists(infile)) if (!boost::filesystem::exists(infile))
{ {
cerr << "Skipping non-existent input file \"" << infile << "\"" << endl; if (!ignoreMissing)
{
cerr << "\"" << infile << "\" is not found" << endl;
return false;
}
else
cerr << "\"" << infile << "\" is not found. Skipping." << endl;
continue; continue;
} }
if (!boost::filesystem::is_regular_file(infile)) if (!boost::filesystem::is_regular_file(infile))
{ {
cerr << "\"" << infile << "\" is not a valid file. Skipping" << endl; if (!ignoreMissing)
{
cerr << "\"" << infile << "\" is not a valid file" << endl;
return false;
}
else
cerr << "\"" << infile << "\" is not a valid file. Skipping." << endl;
continue; continue;
} }
@ -433,6 +450,8 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings()
} }
if (addStdin) if (addStdin)
m_sourceCodes[g_stdinFileName] = dev::readStandardInput(); m_sourceCodes[g_stdinFileName] = dev::readStandardInput();
return true;
} }
bool CommandLineInterface::parseLibraryOption(string const& _input) bool CommandLineInterface::parseLibraryOption(string const& _input)
@ -599,7 +618,8 @@ Allowed options)",
g_argAllowPaths.c_str(), g_argAllowPaths.c_str(),
po::value<string>()->value_name("path(s)"), po::value<string>()->value_name("path(s)"),
"Allow a given path for imports. A list of paths can be supplied by separating them with a comma." "Allow a given path for imports. A list of paths can be supplied by separating them with a comma."
); )
(g_argIgnoreMissingFiles.c_str(), "Ignore missing files.");
po::options_description outputComponents("Output Components"); po::options_description outputComponents("Output Components");
outputComponents.add_options() outputComponents.add_options()
(g_argAst.c_str(), "AST of all source files.") (g_argAst.c_str(), "AST of all source files.")
@ -616,7 +636,7 @@ Allowed options)",
(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.") (g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
(g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.") (g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.")
(g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.") (g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.")
(g_argFormal.c_str(), "Translated source suitable for formal analysis."); (g_argFormal.c_str(), "Translated source suitable for formal analysis. (Deprecated)");
desc.add(outputComponents); desc.add(outputComponents);
po::options_description allOptions = desc; po::options_description allOptions = desc;
@ -680,7 +700,7 @@ bool CommandLineInterface::processInput()
try try
{ {
auto path = boost::filesystem::path(_path); auto path = boost::filesystem::path(_path);
auto canonicalPath = boost::filesystem::canonical(path); auto canonicalPath = weaklyCanonicalFilesystemPath(path);
bool isAllowed = false; bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories) for (auto const& allowedDir: m_allowedDirectories)
{ {
@ -696,16 +716,16 @@ bool CommandLineInterface::processInput()
} }
if (!isAllowed) if (!isAllowed)
return ReadCallback::Result{false, "File outside of allowed directories."}; return ReadCallback::Result{false, "File outside of allowed directories."};
else if (!boost::filesystem::exists(path))
if (!boost::filesystem::exists(canonicalPath))
return ReadCallback::Result{false, "File not found."}; return ReadCallback::Result{false, "File not found."};
else if (!boost::filesystem::is_regular_file(canonicalPath))
if (!boost::filesystem::is_regular_file(canonicalPath))
return ReadCallback::Result{false, "Not a valid file."}; return ReadCallback::Result{false, "Not a valid file."};
else
{ auto contents = dev::readFileAsString(canonicalPath.string());
auto contents = dev::readFileAsString(canonicalPath.string()); m_sourceCodes[path.string()] = contents;
m_sourceCodes[path.string()] = contents; return ReadCallback::Result{true, contents};
return ReadCallback::Result{true, contents};
}
} }
catch (Exception const& _exception) catch (Exception const& _exception)
{ {
@ -741,7 +761,8 @@ bool CommandLineInterface::processInput()
return true; return true;
} }
readInputFilesAndConfigureRemappings(); if (!readInputFilesAndConfigureRemappings())
return false;
if (m_args.count(g_argLibraries)) if (m_args.count(g_argLibraries))
for (string const& library: m_args[g_argLibraries].as<vector<string>>()) for (string const& library: m_args[g_argLibraries].as<vector<string>>())

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