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
compiler: gcc
node_js:
- "7"
- "8"
services:
- docker
before_install:
- nvm install 7
- nvm use 7
- nvm install 8
- nvm use 8
- docker pull trzeci/emscripten:sdk-tag-1.35.4-64bit
env:
- SOLC_EMSCRIPTEN=On
@ -159,6 +159,8 @@ cache:
install:
- test $SOLC_INSTALL_DEPS_TRAVIS != On || (scripts/install_deps.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
- echo -n "$TRAVIS_COMMIT" > commit_hash.txt

View File

@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.4.21")
set(PROJECT_VERSION "0.4.22")
project(solidity VERSION ${PROJECT_VERSION})
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)
Features:

View File

@ -4,7 +4,7 @@
## 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.
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).

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).
- [ ] 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 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.
- [ ] 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.

View File

@ -71,7 +71,7 @@ build_script:
test_script:
- 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
- cd %APPVEYOR_BUILD_FOLDER%
- 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
jobs:
build_emscripten:
@ -48,7 +55,7 @@ jobs:
export NVM_DIR="/usr/local/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm --version
nvm install 6
nvm install 8
node --version
npm --version
- run:
@ -72,7 +79,7 @@ jobs:
export NVM_DIR="/usr/local/nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
nvm --version
nvm install 7
nvm install 8
node --version
npm --version
- run:
@ -89,20 +96,12 @@ jobs:
name: Install build dependencies
command: |
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:
name: Store commit hash and prerelease
command: |
if [ "$CIRCLE_BRANCH" = release -o -n "$CIRCLE_TAG" ]; then echo -n > prerelease.txt; else date -u +"nightly.%Y.%-m.%-d" > prerelease.txt; fi
echo -n "$CIRCLE_SHA1" > commit_hash.txt
- 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:
name: Build
command: |
@ -110,14 +109,6 @@ jobs:
cd build
cmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
make -j4
- run:
name: CCache statistics
command: ccache -s
- save_cache:
key: ccache-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
when: always
paths:
- ~/.ccache
- store_artifacts:
path: build/solc/solc
destination: solc
@ -126,7 +117,7 @@ jobs:
paths:
- solc/solc
- test/soltest
- test/solfuzzer
- test/tools/solfuzzer
test_x86:
docker:
@ -173,15 +164,18 @@ workflows:
version: 2
build_all:
jobs:
- build_emscripten
- build_emscripten: *build_on_tags
- test_emscripten_solcjs:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86
- build_x86: *build_on_tags
- test_x86:
<<: *build_on_tags
requires:
- build_x86
- docs
- docs: *build_on_tags

View File

@ -34,13 +34,6 @@ endif()
set(ETH_CMAKE_DIR ${CMAKE_CURRENT_LIST_DIR})
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
set(Boost_USE_MULTITHREADED 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>``.
- ``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``.
- ``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:
@ -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.
- ``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``: 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``: 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.
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
------
@ -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
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
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

View File

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

View File

@ -130,7 +130,7 @@ restrictions highly readable.
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.22;
contract AccessRestriction {
// These will be assigned at the construction
@ -147,7 +147,10 @@ restrictions highly readable.
// a certain address.
modifier onlyBy(address _account)
{
require(msg.sender == _account);
require(
msg.sender == _account,
"Sender not authorized."
);
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
@ -164,7 +167,10 @@ restrictions highly readable.
}
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,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
require(msg.value >= _amount);
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
@ -194,6 +203,7 @@ restrictions highly readable.
function forceOwnerChange(address _newOwner)
public
payable
costs(200 ether)
{
owner = _newOwner;
@ -272,7 +282,7 @@ function finishes.
::
pragma solidity ^0.4.11;
pragma solidity ^0.4.22;
contract StateMachine {
enum Stages {
@ -289,7 +299,10 @@ function finishes.
uint public creationTime = now;
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 {
// 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
// creator and the assigned name.
function OwnedToken(bytes32 _name) public {
constructor(bytes32 _name) public {
// State variables are accessed via their name
// and not via e.g. this.owner. This also applies
// 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 {
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
// thrown.
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 {
bool locked;
modifier noReentrancy() {
require(!locked);
require(
!locked,
"Reentrant call."
);
locked = true;
_;
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
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
``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
==============================================
@ -976,8 +982,31 @@ virtual method lookup.
Constructors
============
A constructor is an optional function with the same name as the contract which is executed upon contract creation.
Constructor functions can be either ``public`` or ``internal``.
A constructor is an optional function declared with the ``constructor`` keyword which is executed upon contract creation.
Constructor functions can be either ``public`` or ``internal``. If there is no constructor, the contract will assume the
default constructor: ``contructor() public {}``.
::
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 {}
}
A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`.
.. index:: ! base;constructor
@ -1009,12 +1037,15 @@ the base constructors. This can be done in two ways::
contract Base {
uint x;
function Base(uint _x) public { x = _x; }
constructor(uint _x) public { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) public {
}
contract Derived1 is Base(7) {
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
@ -1024,8 +1055,9 @@ do it is more convenient if the constructor argument is a
constant and defines the behaviour of the contract or
describes it. The second way has to be used if the
constructor arguments of the base depend on those of the
derived contract. If, as in this silly example, both places
are used, the modifier-style argument takes precedence.
derived contract. Arguments have to be given either in the
inheritance list or in modifier-style in the derived constuctor.
Specifying arguments in both places is an error.
.. 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 {
// 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.
Finally, please make sure you respect the `coding standards
<https://raw.githubusercontent.com/ethereum/cpp-ethereum/develop/CodingStandards.txt>`_
Finally, please make sure you respect the `coding style
<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
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,
some others require ``libz3`` to be installed.
To disable the z3 tests, use ``./build/test/soltest -- --no-smt`` and
to run a subset of the tests that do not require ``cpp-ethereum``, use ``./build/test/soltest -- --no-ipc``.
``soltest`` reads test contracts that are annotated with expected results
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``.
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:
``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
``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.
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
========

View File

@ -284,10 +284,12 @@ Solidity internally allows tuple types, i.e. a list of objects of potentially di
}
function g() public {
// Declares and assigns the variables. Specifying the type explicitly is not possible.
var (x, b, y) = f();
// Assigns to a pre-existing variable.
(x, y) = (2, 7);
// Variables declared with type
uint x;
bool b;
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.
(x, y) = (y, x);
// 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.
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
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
revert the current call. It is possible to provide a string message containing details about the error
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::
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.
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 {
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;
addr.transfer(msg.value / 2);
// 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
(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.
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)? ';'
UsingForDeclaration = 'using' Identifier 'for' ('*' | TypeName) ';'
StructDefinition = 'struct' Identifier '{'
( VariableDeclaration ';' (VariableDeclaration ';')* )? '}'
( VariableDeclaration ';' (VariableDeclaration ';')* ) '}'
ModifierDefinition = 'modifier' Identifier ParameterList? Block
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)
* `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)
* `Korean <http://solidity-kr.readthedocs.io>`_ (in progress)
Useful links

View File

@ -122,7 +122,6 @@ We will re-add the pre-built bottles soon.
brew upgrade
brew tap ethereum/ethereum
brew install solidity
brew linkapps solidity
If you need a specific version of Solidity you can install a
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.
This means that in order to create a contract, you do not
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

View File

@ -64,12 +64,15 @@ The position of ``data[4][9].b`` is at ``keccak256(uint256(9) . keccak256(uint25
Layout in Memory
****************
Solidity reserves three 256-bit slots:
Solidity reserves four 32 byte slots:
- 0 - 64: scratch space for hashing methods
- 64 - 96: currently allocated memory size (aka. free memory pointer)
- ``0x00`` - ``0x3f``: scratch space for hashing methods
- ``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).
@ -314,7 +317,12 @@ The following is the order of precedence for operators, listed in order of evalu
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.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
@ -330,7 +338,10 @@ Global Variables
- ``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)
- ``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(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>`
- ``sha3(...) returns (bytes32)``: an alias to ``keccak256``
- ``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``
- ``super``: the contract one level higher in the inheritance hierarchy
- ``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>.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
.. 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
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.
contract Ballot {
@ -87,18 +87,25 @@ of votes.
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) public {
// If the argument of `require` evaluates to `false`,
// it terminates and reverts all changes to
// the state and to Ether balances. It is often
// a good idea to use this if functions are
// called incorrectly. But watch out, this
// will currently also consume all provided gas
// (this is planned to change in the future).
// If the first argument of `require` evaluates
// to `false`, execution terminates and all
// changes to the state and to Ether balances
// are reverted.
// This used to consume all gas in old EVM versions, but
// not anymore.
// 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(
(msg.sender == chairperson) &&
!voters[voter].voted &&
(voters[voter].weight == 0)
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
@ -106,10 +113,9 @@ of votes.
function delegate(address to) public {
// assigns reference
Voter storage sender = voters[msg.sender];
require(!sender.voted);
require(!sender.voted, "You already voted.");
// Self-delegation is not allowed.
require(to != msg.sender);
require(to != msg.sender, "Self-delegation is disallowed.");
// Forward the delegation as long as
// `to` also delegated.
@ -123,7 +129,7 @@ of votes.
to = voters[to].delegate;
// 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
@ -146,7 +152,7 @@ of votes.
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted);
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
@ -219,7 +225,7 @@ activate themselves.
::
pragma solidity ^0.4.21;
pragma solidity ^0.4.22;
contract SimpleAuction {
// Parameters of the auction. Times are either
@ -271,11 +277,17 @@ activate themselves.
// Revert the call if the bidding
// period is over.
require(now <= auctionEnd);
require(
now <= auctionEnd,
"Auction already ended."
);
// If the bid is not higher, send the
// money back.
require(msg.value > highestBid);
require(
msg.value > highestBid,
"There already is a higher bid."
);
if (highestBid != 0) {
// Sending back the money by simply using
@ -325,8 +337,8 @@ activate themselves.
// external contracts.
// 1. Conditions
require(now >= auctionEnd); // auction did not yet end
require(!ended); // this function has already been called
require(now >= auctionEnd, "Auction not yet ended.");
require(!ended, "auctionEnd has already been called.");
// 2. Effects
ended = true;
@ -376,7 +388,7 @@ high or low invalid bids.
::
pragma solidity ^0.4.21;
pragma solidity ^0.4.22;
contract BlindAuction {
struct Bid {
@ -529,7 +541,7 @@ Safe Remote Purchase
::
pragma solidity ^0.4.21;
pragma solidity ^0.4.22;
contract Purchase {
uint public value;
@ -544,7 +556,7 @@ Safe Remote Purchase
function Purchase() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value);
require((2 * value) == msg.value, "Value has to be even.");
}
modifier condition(bool _condition) {
@ -553,17 +565,26 @@ Safe Remote Purchase
}
modifier onlyBuyer() {
require(msg.sender == buyer);
require(
msg.sender == buyer,
"Only buyer can call this."
);
_;
}
modifier onlySeller() {
require(msg.sender == seller);
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
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 {
address public seller;
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
words. Examples: ``MAX_BLOCKS``, `TOKEN_NAME`, ``TOKEN_TICKER``, ``CONTRACT_VERSION``.
words. Examples: ``MAX_BLOCKS``, ``TOKEN_NAME``, ``TOKEN_TICKER``, ``CONTRACT_VERSION``.
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
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:
@ -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``.
.. note::
All contracts inherit the members of address, so it is possible to query the balance of the
current contract using ``this.balance``.
All contracts can be converted to ``address`` type, so it is possible to query the balance of the
current contract using ``address(this).balance``.
.. note::
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::
pragma solidity ^0.4.21;
pragma solidity ^0.4.22;
contract Oracle {
struct Request {
@ -495,7 +495,10 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) public {
require(msg.sender == address(oracle));
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
// 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
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.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.difficulty`` (``uint``): current block difficulty
- ``block.gaslimit`` (``uint``): current block gaslimit
@ -74,7 +75,7 @@ Block and Transaction Properties
This includes calls to library functions.
.. 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.
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
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
Error Handling
@ -99,8 +120,12 @@ Error Handling
throws if the condition is not met - to be used for internal errors.
``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components.
``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()``:
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,
@ -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:
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::
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`
``suicide(address recipient)``:
alias to ``selfdestruct``
deprecated alias to ``selfdestruct``
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")
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_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
target_link_libraries(devcore PRIVATE jsoncpp ${Boost_FILESYSTEM_LIBRARIES} ${Boost_REGEX_LIBRARIES} ${Boost_SYSTEM_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT})
target_include_directories(devcore PUBLIC "${CMAKE_SOURCE_DIR}")
target_include_directories(devcore PUBLIC "${JSONCPP_INCLUDE_DIR}")
add_dependencies(devcore jsoncpp)
target_include_directories(devcore SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
add_dependencies(devcore solidity_BuildInfo.h)

View File

@ -27,6 +27,7 @@
#if defined(_WIN32)
#include <windows.h>
#else
#include <unistd.h>
#include <termios.h>
#endif
#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 <string>
#include <boost/filesystem.hpp>
#include "Common.h"
namespace dev
@ -37,6 +38,9 @@ std::string readFileAsString(std::string const& _file);
/// Retrieve and returns the contents of standard input (until EOF).
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.
/// Throws exception on error.
/// @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();
}
/// 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.
AssemblyItems optimisedItems;
bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem(Instruction::MSIZE)) != m_items.end());
auto iter = m_items.begin();
while (iter != m_items.end())
{
KnownState emptyState;
CommonSubexpressionEliminator eliminator(emptyState);
auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end());
iter = eliminator.feedItems(iter, m_items.end(), usesMSize);
bool shouldReplace = false;
AssemblyItems optimisedChunk;
try

View File

@ -65,8 +65,9 @@ public:
/// 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.
/// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect
template <class _AssemblyItemIterator>
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end);
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant);
/// @returns the resulting items after optimization.
AssemblyItems getOptimizedItems();
@ -168,11 +169,12 @@ private:
template <class _AssemblyItemIterator>
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _iterator,
_AssemblyItemIterator _end
_AssemblyItemIterator _end,
bool _msizeImportant
)
{
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);
if (_iterator != _end)
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::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } },
{ 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::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } },
{ 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>
{
static size_t applySimple(
@ -260,7 +305,7 @@ bool PeepholeOptimiser::optimise()
{
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
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() || (
m_optimisedItems.size() == m_items.size() && (
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;
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask);
}, 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
{{Instruction::ADD, {X, 0}}, [=]{ return X; }, false},

View File

@ -28,7 +28,7 @@ using namespace std;
using namespace dev;
using namespace dev::eth;
bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item)
bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant)
{
switch (_item.type())
{
@ -59,6 +59,11 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item)
return false;
if (_item.instruction() == Instruction::MSTORE)
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:
// calldatacopy, codecopy, extcodecopy, mstore8,
// msize (note that msize also depends on memory read access)

View File

@ -38,7 +38,8 @@ class AssemblyItem;
struct SemanticInformation
{
/// @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
/// order of its arguments.
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));
}
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)
{
if (_type && _type->category() == Type::Category::RationalNumber)

View File

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

View File

@ -45,7 +45,8 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if (
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.
@ -68,6 +69,11 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
!dynamic_cast<EventDefinition const*>(declaration)
)
return declaration;
if (
dynamic_cast<MagicVariableDeclaration const*>(&_declaration) &&
!dynamic_cast<MagicVariableDeclaration const*>(declaration)
)
return declaration;
// Or, continue.
}
}

View File

@ -35,9 +35,11 @@ namespace solidity
GlobalContext::GlobalContext():
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>("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>("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>("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)),
@ -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>("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", "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{"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>("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)),

View File

@ -47,7 +47,9 @@ NameAndTypeResolver::NameAndTypeResolver(
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
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)
@ -202,8 +204,9 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration),
"Found overloading involving something not a function or a variable."
dynamic_cast<VariableDeclaration const*>(declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
);
FunctionTypePointer functionType { declaration->functionType(false) };

View File

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

View File

@ -21,6 +21,7 @@
*/
#include <libsolidity/analysis/StaticAnalyzer.h>
#include <libsolidity/analysis/ConstantEvaluator.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/interface/ErrorReporter.h>
#include <memory>
@ -50,6 +51,16 @@ void StaticAnalyzer::endVisit(ContractDefinition const&)
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())
m_currentFunction = &_function;
else
@ -68,13 +79,13 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
for (auto const& var: m_localVarUseCount)
if (var.second == 0)
{
if (var.first->isCallableParameter())
if (var.first.second->isCallableParameter())
m_errorReporter.warning(
var.first->location(),
var.first.second->location(),
"Unused function parameter. Remove or comment out the variable name to silence this warning."
);
else
m_errorReporter.warning(var.first->location(), "Unused local variable.");
m_errorReporter.warning(var.first.second->location(), "Unused local variable.");
}
m_localVarUseCount.clear();
@ -87,7 +98,7 @@ bool StaticAnalyzer::visit(Identifier const& _identifier)
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
m_localVarUseCount[var] += 1;
m_localVarUseCount[make_pair(var->id(), var)] += 1;
}
return true;
}
@ -99,7 +110,7 @@ bool StaticAnalyzer::visit(VariableDeclaration const& _variable)
solAssert(_variable.isLocalVariable(), "");
if (_variable.name() != "")
// 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())
{
@ -122,7 +133,7 @@ bool StaticAnalyzer::visit(Return const& _return)
if (m_currentFunction && _return.expression())
for (auto const& var: m_currentFunction->returnParameters())
if (!var->name().empty())
m_localVarUseCount[var.get()] += 1;
m_localVarUseCount[make_pair(var->id(), var.get())] += 1;
return true;
}
@ -142,6 +153,7 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
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 (type->kind() == MagicType::Kind::Message && _memberAccess.memberName() == "gas")
{
if (v050)
@ -155,6 +167,20 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
"\"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 (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 (ContractType const* type = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type.get()))
if (type->contractDefinition() == *m_currentContract)
m_errorReporter.warning(_memberAccess.location(), "\"this\" used in constructor.");
if (m_constructor)
{
auto const* expr = &_memberAccess.expression();
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;
}
@ -199,13 +247,54 @@ bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!var->name().empty(), "");
if (var->isLocalVariable())
m_localVarUseCount[var] += 1;
m_localVarUseCount[make_pair(var->id(), var)] += 1;
}
}
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)
{
switch (_type.category())

View File

@ -64,6 +64,8 @@ private:
virtual bool visit(Return const& _return) override;
virtual bool visit(MemberAccess const& _memberAccess) 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.
static bigint structureSizeEstimate(Type const& _type, std::set<StructDefinition const*>& _structsSeen);
@ -77,7 +79,9 @@ private:
bool m_nonPayablePublic = false;
/// 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;

View File

@ -214,18 +214,31 @@ bool SyntaxChecker::visit(FunctionDefinition const& _function)
{
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)
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
m_errorReporter.warning(
_function.location(),
"No visibility specified. Defaulting to \"" +
Declaration::visibilityToString(_function.visibility()) +
"\"."
"Defining constructors as functions with the same name as the contract is deprecated. "
"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;
}
@ -255,3 +268,17 @@ bool SyntaxChecker::visit(VariableDeclaration const& _declaration)
}
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(StructDefinition const& _struct) override;
ErrorReporter& m_errorReporter;
/// 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)
{
try
{
_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.
}
_contract.accept(*this);
return Error::containsOnlyWarnings(m_errorReporter.errors());
}
@ -101,7 +91,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractDuplicateEvents(_contract);
checkContractIllegalOverrides(_contract);
checkContractAbstractFunctions(_contract);
checkContractAbstractConstructors(_contract);
checkContractBaseConstructorArguments(_contract);
FunctionDefinition const* function = _contract.constructor();
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;
// check that we get arguments for all base constructors that need it.
// If not mark the contract as abstract (not fully implemented)
bool const v050 = _contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
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)
{
if (FunctionDefinition const* constructor = contract->constructor())
for (auto const& modifier: constructor->modifiers())
{
auto baseContract = dynamic_cast<ContractDefinition const*>(
&dereference(*modifier->name())
);
if (baseContract)
argumentsNeeded.erase(baseContract);
auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(*modifier->name()));
if (modifier->arguments())
{
if (baseContract && baseContract->constructor())
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())
{
auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name()));
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)
_contract.annotation().unimplementedFunctions.push_back(contract->constructor());
// check that we get arguments for all base constructors that need it.
// 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)
@ -378,7 +434,16 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
function.annotation().superFunction = &super;
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.");
}
else if (function.stateMutability() != super.stateMutability())
overrideError(
@ -497,30 +562,46 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
// Interfaces do not have constructors, so there are zero parameters.
parameterTypes = ContractType(*base).newExpressionType()->parameterTypes();
if (!arguments.empty() && parameterTypes.size() != arguments.size())
if (arguments)
{
m_errorReporter.typeError(
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments.size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
"."
);
return;
}
bool v050 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050);
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."
);
if (parameterTypes.size() != arguments->size())
{
if (arguments->size() == 0 && !v050)
m_errorReporter.warning(
_inheritance.location(),
"Wrong argument count for constructor call: " +
toString(arguments->size()) +
" arguments given but expected " +
toString(parameterTypes.size()) +
"."
);
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)
@ -731,7 +812,8 @@ void TypeChecker::visitManually(
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)
argument->accept(*this);
_modifier.name()->accept(*this);
@ -769,7 +851,7 @@ void TypeChecker::visitManually(
);
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])))
m_errorReporter.typeError(
arguments[i]->location(),
@ -1551,16 +1633,22 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
_functionCall.expression().annotation().isPure &&
functionType->isPure();
bool allowDynamicTypes = m_evmVersion.supportsReturndata();
if (!functionType)
{
m_errorReporter.typeError(_functionCall.location(), "Type is not callable");
_functionCall.annotation().type = make_shared<TupleType>();
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
_functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes());
_functionCall.annotation().type = make_shared<TupleType>(returnTypes);
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;
@ -1623,15 +1723,36 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
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)
{
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 (!t->mobileType())
{
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]))
m_errorReporter.typeError(
@ -1867,7 +1988,8 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
m_errorReporter.warning(
_memberAccess.location(),
"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)
{
TypePointer function = declaration->type();
solAssert(!!function, "Requested type not present.");
auto const* functionType = dynamic_cast<FunctionType const*>(function.get());
if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
FunctionTypePointer functionType = declaration->functionType(true);
solAssert(!!functionType, "Requested type not present.");
if (functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration);
}
if (candidates.empty())

View File

@ -73,7 +73,12 @@ private:
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
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
/// external argument types (i.e. different signature).
void checkContractExternalTypeClashes(ContractDefinition const& _contract);

View File

@ -305,10 +305,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
mutability = StateMutability::View;
break;
case Type::Category::Magic:
{
// 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;
break;
}
case Type::Category::Struct:
{
if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))

View File

@ -290,7 +290,14 @@ TypeDeclarationAnnotation& EnumDefinition::annotation() const
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)
{
@ -331,6 +338,7 @@ shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
TypePointer FunctionDefinition::type() const
{
solAssert(visibility() != Declaration::Visibility::External, "");
return make_shared<FunctionType>(*this);
}
@ -372,7 +380,7 @@ TypePointer EventDefinition::type() const
return make_shared<FunctionType>(*this);
}
std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const
FunctionTypePointer EventDefinition::functionType(bool _internal) const
{
if (_internal)
return make_shared<FunctionType>(*this);
@ -477,7 +485,7 @@ TypePointer VariableDeclaration::type() const
return annotation().type;
}
shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const
FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
{
if (_internal)
return {};

View File

@ -203,6 +203,7 @@ public:
bool isPublic() const { return visibility() >= Visibility::Public; }
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && visibility() >= Visibility::Internal; }
bool isVisibleAsLibraryMember() const { return visibility() >= Visibility::Internal; }
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.
/// @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:
virtual Visibility defaultVisibility() const { return Visibility::Public; }
@ -424,19 +425,22 @@ public:
InheritanceSpecifier(
SourceLocation const& _location,
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(ASTConstVisitor& _visitor) const override;
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:
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; }
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; }
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
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.
std::string externalSignature() const;
ContractDefinition::ContractKind inContractKind() const;
virtual TypePointer type() const override;
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @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;
@ -696,7 +703,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @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;
@ -755,19 +762,22 @@ public:
ModifierInvocation(
SourceLocation const& _location,
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(ASTConstVisitor& _visitor) const override;
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:
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; }
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;
@ -821,6 +831,11 @@ public:
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; }
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.
/// Also includes all contracts from @a linearizedBaseContracts.
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

View File

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

View File

@ -152,7 +152,7 @@ private:
}
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);
void appendExpressionAttributes(
std::vector<std::pair<std::string, Json::Value>> &_attributes,

View File

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

View File

@ -28,6 +28,7 @@
#include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h>
#include <libdevcore/Algorithms.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
@ -43,6 +44,85 @@ using namespace std;
using namespace dev;
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)
{
bigint slotOffset = 0;
@ -208,9 +288,9 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
case Token::UInt:
return make_shared<IntegerType>(256, IntegerType::Modifier::Unsigned);
case Token::Fixed:
return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Signed);
return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Signed);
case Token::UFixed:
return make_shared<FixedPointType>(128, 19, FixedPointType::Modifier::Unsigned);
return make_shared<FixedPointType>(128, 18, FixedPointType::Modifier::Unsigned);
case Token::Byte:
return make_shared<FixedBytesType>(1);
case Token::Address:
@ -232,11 +312,22 @@ TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
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 secondNum;
Token::Value token;
tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(_name);
return fromElementaryTypeName(ElementaryTypeNameToken(token, firstNum, secondNum));
tie(token, firstNum, secondNum) = Token::fromIdentifierOrKeyword(name);
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)
@ -304,7 +395,7 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
);
for (FunctionDefinition const* function: library.definedFunctions())
{
if (!function->isVisibleInDerivedContracts() || seenFunctions.count(function))
if (!function->isVisibleAsLibraryMember() || seenFunctions.count(function))
continue;
seenFunctions.insert(function);
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))
return !otherInt->isAddress();
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
return false;
}
@ -677,31 +768,39 @@ tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal
}
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()));
if (exp > numeric_limits<int32_t>::max() || exp < numeric_limits<int32_t>::min())
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));
if (!get<0>(base))
return make_tuple(false, rational(0));
value = get<1>(base);
if (exp < 0)
{
exp *= -1;
if (!fitsPrecisionBase10(abs(value.denominator()), expAbs))
return make_tuple(false, rational(0));
value /= boost::multiprecision::pow(
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(
bigint(10),
exp.convert_to<int32_t>()
expAbs
);
}
}
else
{
@ -827,10 +926,10 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
{
if (_other->category() == Category::Integer || _other->category() == Category::FixedPoint)
{
auto mobile = mobileType();
if (!mobile)
auto commonType = Type::commonType(shared_from_this(), _other);
if (!commonType)
return TypePointer();
return mobile->binaryOperatorResult(_operator, _other);
return commonType->binaryOperatorResult(_operator, _other);
}
else if (_other->category() != category())
return TypePointer();
@ -900,16 +999,49 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
using boost::multiprecision::pow;
if (other.isFractional())
return TypePointer();
else if (abs(other.m_value) > numeric_limits<uint32_t>::max())
return TypePointer(); // This will need too much memory to represent.
uint32_t exponent = abs(other.m_value).numerator().convert_to<uint32_t>();
bigint numerator = pow(m_value.numerator(), exponent);
bigint denominator = pow(m_value.denominator(), exponent);
if (other.m_value >= 0)
value = rational(numerator, denominator);
solAssert(other.m_value.denominator() == 1, "");
bigint const& exp = other.m_value.numerator();
// x ** 0 = 1
// for 0, 1 and -1 the size of the exponent doesn't have to be restricted
if (exp == 0)
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
// 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;
}
case Token::SHL:
@ -921,28 +1053,48 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer();
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
value = m_value.numerator() * pow(bigint(2), exponent);
if (m_value.numerator() == 0)
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;
}
// 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).
case Token::SAR:
{
using boost::multiprecision::pow;
namespace mp = boost::multiprecision;
if (fractional)
return TypePointer();
else if (other.m_value < 0)
return TypePointer();
else if (other.m_value > numeric_limits<uint32_t>::max())
return TypePointer();
uint32_t exponent = other.m_value.numerator().convert_to<uint32_t>();
value = rational(m_value.numerator() / pow(bigint(2), exponent), 1);
if (m_value.numerator() == 0)
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;
}
default:
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);
}
}
@ -1262,6 +1414,8 @@ bool ContractType::isPayable() const
TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
{
if (isSuper())
return TypePointer{};
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
}
@ -1969,25 +2123,19 @@ bool StructType::recursive() const
{
if (!m_recursive.is_initialized())
{
set<StructDefinition const*> structsSeen;
function<bool(StructType const*)> check = [&](StructType const* t) -> bool
auto visitor = [&](StructDefinition const& _struct, CycleDetector<StructDefinition>& _cycleDetector)
{
StructDefinition const* str = &t->structDefinition();
if (structsSeen.count(str))
return true;
structsSeen.insert(str);
for (ASTPointer<VariableDeclaration> const& variable: str->members())
for (ASTPointer<VariableDeclaration> const& variable: _struct.members())
{
Type const* memberType = variable->annotation().type.get();
while (dynamic_cast<ArrayType const*>(memberType))
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
if (check(innerStruct))
return true;
if (_cycleDetector.run(innerStruct->structDefinition()))
return;
}
return false;
};
m_recursive = check(this);
m_recursive = (CycleDetector<StructDefinition>(visitor).run(structDefinition()) != nullptr);
}
return *m_recursive;
}
@ -2311,6 +2459,18 @@ vector<string> FunctionType::parameterNames() const
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
{
if (!bound())
@ -2355,7 +2515,11 @@ string FunctionType::richIdentifier() const
case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; 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;
}
id += "_" + stateMutabilityToString(m_stateMutability);
@ -2772,18 +2936,9 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
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>(
parameterTypes,
returnParameterTypes,
m_returnParameterTypes,
m_parameterNames,
m_returnParameterNames,
kind,
@ -2875,7 +3030,7 @@ MemberList::MemberMap TypeType::nativeMembers(ContractDefinition const* _current
}
if (contract.isLibrary())
for (FunctionDefinition const* function: contract.definedFunctions())
if (function->isVisibleInDerivedContracts())
if (function->isVisibleAsLibraryMember())
members.push_back(MemberList::Member(
function->name(),
FunctionType(*function).asMemberFunction(true),
@ -2985,6 +3140,8 @@ string MagicType::richIdentifier() const
return "t_magic_message";
case Kind::Transaction:
return "t_magic_transaction";
case Kind::ABI:
return "t_magic_abi";
default:
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)},
{"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:
solAssert(false, "Unknown kind of magic.");
}
@ -3040,6 +3236,8 @@ string MagicType::toString(bool) const
return "msg";
case Kind::Transaction:
return "tx";
case Kind::ABI:
return "abi";
default:
solAssert(false, "Unknown kind of magic.");
}

View File

@ -150,6 +150,7 @@ public:
/// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a 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);
/// @}
@ -229,6 +230,9 @@ public:
/// i.e. it behaves differently in lvalue context and in value context.
virtual bool isValueType() const { return false; }
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.
/// This returns the corresponding IntegerType or FixedPointType for RationalNumberType
/// 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.
* There is one distinct type per value.
*/
@ -411,7 +415,7 @@ public:
/// @returns true if the literal is a valid integer.
static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
explicit RationalNumberType(rational const& _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.
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.
std::shared_ptr<FixedPointType const> fixedPointType() const;
@ -442,6 +446,9 @@ public:
/// @returns true if the value is negative.
bool isNegative() const { return m_value < 0; }
/// @returns true if the value is zero.
bool isZero() const { return m_value == 0; }
private:
rational m_value;
@ -568,6 +575,7 @@ public:
virtual TypePointer mobileType() const override { return copyForLocation(m_location, true); }
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
/// 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 unsigned calldataEncodedSize(bool _padded ) const override
{
solAssert(!isSuper(), "");
return encodingType()->calldataEncodedSize(_padded);
}
virtual unsigned storageBytes() const override { return 20; }
virtual bool canLiveOutsideStorage() const override { return true; }
virtual unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; }
virtual bool canLiveOutsideStorage() const override { return !isSuper(); }
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 canonicalName() const override;
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override
{
if (isSuper())
return TypePointer{};
return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address);
}
virtual TypePointer interfaceType(bool _inLibrary) const override
{
if (isSuper())
return TypePointer{};
return _inLibrary ? shared_from_this() : encodingType();
}
@ -768,7 +781,7 @@ public:
virtual std::string canonicalName() 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.
FunctionTypePointer constructorType() const;
@ -850,6 +863,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override;
virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual TypePointer mobileType() const override;
/// Converts components to their temporary types and performs some wildcard matching.
virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override;
@ -903,6 +917,10 @@ public:
ObjectCreation, ///< array creation using new
Assert, ///< assert()
Require, ///< require()
ABIEncode,
ABIEncodePacked,
ABIEncodeWithSelector,
ABIEncodeWithSignature,
GasLeft ///< gasleft()
};
@ -973,6 +991,9 @@ public:
TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const;
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; }
/// @returns the "self" parameter type for a bound function
TypePointer const& selfType() const;
@ -991,6 +1012,7 @@ public:
virtual bool isValueType() const override { return true; }
virtual bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
virtual unsigned sizeOnStack() const override;
virtual bool hasSimpleZeroValueInMemory() const override { return false; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
virtual TypePointer encodingType() const override;
virtual TypePointer interfaceType(bool _inLibrary) const override;
@ -1024,7 +1046,7 @@ public:
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.
/// Currently, this will only return true for internal functions like keccak and ecrecover.
bool isPure() const;
@ -1034,14 +1056,14 @@ public:
ASTPointer<ASTString> documentation() const;
/// 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 gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
bool bound() const { return m_bound; }
/// @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;
/// @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();
}
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& valueType() const { return m_valueType; }
@ -1124,6 +1148,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
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 MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
@ -1146,6 +1171,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) 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 canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -1187,7 +1214,7 @@ private:
class MagicType: public Type
{
public:
enum class Kind { Block, Message, Transaction };
enum class Kind { Block, Message, Transaction, ABI };
virtual Category category() const override { return Category::Magic; }
explicit MagicType(Kind _kind): m_kind(_kind) {}
@ -1201,6 +1228,7 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
virtual bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); }
virtual unsigned sizeOnStack() const override { return 0; }
virtual MemberList::MemberMap nativeMembers(ContractDefinition const*) const override;
@ -1230,6 +1258,7 @@ public:
virtual bool canLiveOutsideStorage() const override { return false; }
virtual bool isValueType() const override { return true; }
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 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());
break;
}
case Type::Category::InaccessibleDynamic:
templ("body", "cleaned := 0");
break;
default:
solAssert(false, "Cleanup of type " + _type.identifier() + " requested.");
}

View File

@ -741,10 +741,10 @@ void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
if (_type.isByteArray())
// For a "long" byte array, store length as 2*length+1
_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
_context << Instruction::DUP2 << Instruction::DUP2
<< Instruction::ISZERO << Instruction::GT;
<< Instruction::GT << Instruction::ISZERO;
_context.appendConditionalJumpTo(resizeEnd);
// 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
{
m_context.callLowLevelFunction(

View File

@ -67,6 +67,12 @@ public:
/// Stack pre: reference (excludes byte offset) new_length
/// Stack post:
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).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref

View File

@ -193,14 +193,22 @@ Declaration const* CompilerContext::nextFunctionToCompile() const
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.");
for (ContractDefinition const* contract: m_inheritanceHierarchy)
for (ModifierDefinition const* modifier: contract->functionModifiers())
if (modifier->name() == _name)
if (modifier->name() == _modifier.name())
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
@ -254,12 +262,20 @@ CompilerContext& CompilerContext::appendRevert()
return *this << u256(0) << u256(0) << Instruction::REVERT;
}
CompilerContext& CompilerContext::appendConditionalRevert()
CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
{
*this << Instruction::ISZERO;
eth::AssemblyItem afterTag = appendConditionalJump();
appendRevert();
*this << afterTag;
if (_forwardReturnData && m_evmVersion.supportsReturndata())
appendInlineAssembly(R"({
if condition {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
})", {"condition"});
else
appendInlineAssembly(R"({
if condition { revert(0, 0) }
})", {"condition"});
*this << Instruction::POP;
return *this;
}

View File

@ -130,7 +130,7 @@ public:
void appendMissingLowLevelFunctions();
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).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
/// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns
@ -156,8 +156,11 @@ public:
CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call
CompilerContext& appendRevert();
/// Appends a conditional REVERT(0, 0) call
CompilerContext& appendConditionalRevert();
/// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
/// 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
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag.

View File

@ -21,12 +21,16 @@
*/
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libevmasm/Instruction.h>
#include <libsolidity/codegen/ArrayUtils.h>
#include <libsolidity/codegen/LValue.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libevmasm/Instruction.h>
#include <libdevcore/Whiskers.h>
using namespace std;
namespace dev
@ -36,11 +40,17 @@ namespace solidity
const unsigned CompilerUtils::dataStartOffset = 4;
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;
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()
{
m_context << u256(freeMemoryPointer + 32);
m_context << u256(generalPurposeMemoryStart);
storeFreeMemoryPointer();
}
@ -68,6 +78,20 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
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 _offset,
Type const& _type,
@ -139,7 +163,6 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
dynamic_cast<FunctionType const&>(_type).kind() == FunctionType::Kind::External
)
{
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
combineExternalFunctionType(true);
m_context << Instruction::DUP2 << Instruction::MSTORE;
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(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
@ -321,15 +501,13 @@ void CompilerUtils::abiEncodeV2(
void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
{
// stack: <source_offset>
// stack: <source_offset> <length> [stack top]
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;
if (_fromMemory)
// TODO pass correct size for the memory case
m_context << (u256(1) << 63);
else
m_context << Instruction::CALLDATASIZE;
m_context << Instruction::SWAP1;
// stack: <return tag> <end> <start>
string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
m_context.appendJumpTo(m_context.namedTag(decoderName));
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)
{
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);
if (_type.baseType()->hasSimpleZeroValueInMemory())
{
solAssert(_type.baseType()->isValueType(), "");
Whiskers templ(R"({
let size := mul(length, <element_size>)
// cheap way of zero-initializing a memory range
codecopy(memptr, codesize(), size)
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;
}
@ -427,7 +625,7 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
leftShiftNumberOnStack(64);
}
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly)
{
m_context << m_context.functionEntryLabel(_function).pushTag();
// 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())
{
leftShiftNumberOnStack(32);
m_context <<
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
if (_runtimeOnly)
m_context <<
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
}
}
@ -485,19 +684,17 @@ void CompilerUtils::convertType(
// clear for conversion to longer bytes
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
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)
m_context << Instruction::POP << u256(0);
else
{
m_context << ((u256(1) << (256 - typeOnStack.numBytes() * 8)) - 1);
m_context << Instruction::NOT << Instruction::AND;
}
int bytes = min(typeOnStack.numBytes(), targetType.numBytes());
m_context << ((u256(1) << (256 - bytes * 8)) - 1);
m_context << Instruction::NOT << Instruction::AND;
}
}
}
break;
}
case Type::Category::Enum:
solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, "");
if (enumOverflowCheckPending)
@ -506,6 +703,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding)
// TODO: error message?
m_context.appendConditionalRevert();
else
m_context.appendConditionalInvalid();
@ -598,8 +796,9 @@ void CompilerUtils::convertType(
bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes)
{
int const numBytes = dynamic_cast<FixedBytesType const&>(_targetType).numBytes();
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)
{
@ -873,6 +1072,13 @@ void CompilerUtils::pushZeroValue(Type const& _type)
return;
}
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();
m_context.callLowLevelFunction(
@ -893,13 +1099,8 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
{
if (arrayType->isDynamicallySized())
{
// zero length
_context << u256(0);
utils.storeInMemoryDynamic(IntegerType(256));
}
else if (arrayType->length() > 0)
solAssert(!arrayType->isDynamicallySized(), "");
if (arrayType->length() > 0)
{
_context << arrayType->length() << Instruction::SWAP1;
// 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)
{
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable));

View File

@ -54,6 +54,13 @@ public:
/// Stack post: <size> <mem_start>
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.
/// @param _offset offset in memory (or calldata)
/// @param _type data type to load
@ -88,6 +95,15 @@ public:
/// Stack post: (memory_offset+length)
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
/// 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.
@ -149,7 +165,7 @@ public:
/// 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.
/// Can allocate memory.
/// Stack pre: <source_offset>
/// Stack pre: <source_offset> <length>
/// Stack post: <value0> <value1> ... <valuen>
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
/// 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.
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
/// 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
/// memory for memory references.
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.
void moveToStackVariable(VariableDeclaration const& _variable);
@ -245,6 +265,10 @@ public:
/// Position of the free-memory-pointer in memory;
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:
/// 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.
m_context << Instruction::CALLVALUE;
// TODO: error message?
m_context.appendConditionalRevert();
}
@ -135,33 +136,13 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
{
solAssert(!_contract.isLibrary(), "Tried to initialize library.");
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())
{
ContractDefinition const* baseContract = dynamic_cast<ContractDefinition const*>(
base->name().annotation().referencedDeclaration
);
solAssert(baseContract, "");
m_baseArguments = &_contract.annotation().baseConstructorArguments;
if (m_baseArguments.count(baseContract->constructor()) == 0)
m_baseArguments[baseContract->constructor()] = &base->arguments();
}
}
// 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);
if (FunctionDefinition const* constructor = _contract.constructor())
@ -235,9 +216,16 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc
FunctionType constructorType(_constructor);
if (!constructorType.parameterTypes().empty())
{
solAssert(m_baseArguments.count(&_constructor), "");
std::vector<ASTPointer<Expression>> const* arguments = m_baseArguments[&_constructor];
solAssert(m_baseArguments, "");
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->size() == constructorType.parameterTypes().size(), "");
for (unsigned i = 0; i < arguments->size(); ++i)
compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]);
}
@ -278,9 +266,10 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
m_context.appendProgramSize();
m_context << Instruction::DUP4 << Instruction::CODECOPY;
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::DUP1;
CompilerUtils(m_context).storeFreeMemoryPointer();
// stack: <memptr>
appendCalldataUnpacker(FunctionType(_constructor).parameterTypes(), true);
CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
}
_constructor.accept(*this);
}
@ -339,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP;
}
else
// TODO: error message here?
m_context.appendRevert();
for (auto const& it: interfaceFunctions)
@ -367,7 +357,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
{
// Parameter for calldataUnpacker
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 << 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)
{
CompilerUtils utils(m_context);
@ -1002,15 +894,21 @@ void ContractCompiler::appendModifierOrFunctionCode()
appendModifierOrFunctionCode();
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);
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)
{
m_context.addVariable(*modifier.parameters()[i]);
addedVariables.push_back(modifier.parameters()[i].get());
compileExpression(
*modifierInvocation->arguments()[i],
*modifierArguments[i],
modifier.parameters()[i]->annotation().type
);
}

View File

@ -90,10 +90,6 @@ private:
void appendDelegatecallCheck();
void appendFunctionSelector(ContractDefinition const& _contract);
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 registerStateVariables(ContractDefinition const& _contract);
@ -139,7 +135,7 @@ private:
FunctionDefinition const* m_currentFunction = nullptr;
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
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 <libevmasm/GasMeter.h>
#include <libdevcore/Whiskers.h>
using namespace std;
namespace dev
@ -139,8 +141,8 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
utils().popStackSlots(paramTypes.size() - 1);
}
unsigned retSizeOnStack = 0;
solAssert(accessorType.returnParameterTypes().size() >= 1, "");
auto const& returnTypes = accessorType.returnParameterTypes();
auto returnTypes = accessorType.returnParameterTypes();
solAssert(returnTypes.size() >= 1, "");
if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get()))
{
// remove offset
@ -518,7 +520,23 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[i]->accept(*this);
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());
if (function.bound())
{
@ -592,7 +610,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
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())
m_context << swapInstruction(1) << Instruction::POP;
break;
@ -654,8 +673,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer)
{
// 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.appendConditionalRevert();
m_context.appendConditionalRevert(true);
}
break;
case FunctionType::Kind::Selfdestruct:
@ -664,8 +684,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT;
break;
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;
}
case FunctionType::Kind::SHA3:
{
TypePointers argumentTypes;
@ -805,24 +836,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
function.kind() == FunctionType::Kind::ArrayPush ?
make_shared<ArrayType>(DataLocation::Storage, paramType) :
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);
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
TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
solAssert(type, "");
utils().convertType(*arguments[0]->annotation().type, *type);
utils().convertType(*argType, *type);
utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack());
// stack: newLength argValue storageSlot slotOffset
@ -834,8 +868,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
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);
_functionCall.expression().accept(*this);
solAssert(arguments.size() == 1, "");
@ -845,15 +877,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().convertType(*arguments[0]->annotation().type, IntegerType(256));
// Stack: requested_length
// Allocate at max(MSIZE, freeMemoryPointer)
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
m_context << Instruction::SWAP1;
@ -878,13 +902,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// Check if length is zero
m_context << Instruction::DUP1 << Instruction::ISZERO;
auto skipInit = m_context.appendConditionalJump();
// We only have to initialise if the base type is a not a value type.
if (dynamic_cast<ReferenceType const*>(arrayType.baseType().get()))
{
m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
utils().zeroInitialiseMemoryArray(arrayType);
}
// Always initialize because the free memory pointer might point at
// a dirty memory area.
m_context << Instruction::DUP2 << u256(32) << Instruction::ADD;
utils().zeroInitialiseMemoryArray(arrayType);
m_context << skipInit;
m_context << Instruction::POP;
break;
@ -894,16 +915,130 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
arguments.front()->accept(*this);
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
m_context << Instruction::ISZERO << Instruction::ISZERO;
auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error
m_context.appendInvalid();
else if (arguments.size() > 1)
utils().revertWithStringData(*arguments.at(1)->annotation().type);
else
m_context.appendRevert();
// the success branch
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;
}
case FunctionType::Kind::GasLeft:
@ -1147,6 +1282,9 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
else if (member == "sig")
m_context << u256(0) << Instruction::CALLDATALOAD
<< (u256(0xffffffff) << (256 - 32)) << Instruction::AND;
else if (member == "blockhash")
{
}
else
solAssert(false, "Unknown magic member.");
break;
@ -1356,6 +1494,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
}
}
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));
else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
appendVariable(*variable, static_cast<Expression const&>(_identifier));
@ -1615,15 +1757,27 @@ void ExpressionCompiler::appendExternalFunctionCall(
m_context.experimentalFeatureActive(ExperimentalFeature::V050) &&
m_context.evmVersion().hasStaticCall();
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
TypePointers returnTypes;
if (returnSuccessCondition)
retSize = 0; // return value actually is success condition
else if (haveReturndatacopy)
returnTypes = _functionType.returnParameterTypes();
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.");
retSize += retType->calldataEncodedSize();
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
retSize = 0;
break;
}
else
retSize += retType->calldataEncodedSize();
// Evaluate arguments.
TypePointers argumentTypes;
@ -1755,6 +1909,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
m_context.appendConditionalRevert();
existenceChecked = true;
}
@ -1797,7 +1952,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
m_context.appendConditionalRevert();
m_context.appendConditionalRevert(true);
}
utils().popStackSlots(remainsSize);
@ -1821,20 +1976,42 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().fetchFreeMemoryPointer();
m_context << Instruction::SUB << Instruction::MLOAD;
}
else if (!_functionType.returnParameterTypes().empty())
else if (!returnTypes.empty())
{
utils().fetchFreeMemoryPointer();
bool memoryNeeded = false;
for (auto const& retType: _functionType.returnParameterTypes())
{
utils().loadFromMemoryDynamic(*retType, false, true, true);
if (dynamic_cast<ReferenceType const*>(retType.get()))
memoryNeeded = true;
}
if (memoryNeeded)
utils().storeFreeMemoryPointer();
// Stack: return_data_start
// The old decoder did not allocate any memory (i.e. did not touch the free
// memory pointer), but kept references to the return data for
// (statically-sized) arrays
bool needToUpdateFreeMemoryPtr = false;
if (dynamicReturnSize || m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
needToUpdateFreeMemoryPtr = true;
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(),
"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(
_assignment.location(),
"Assertion checker does not yet implement type " + _assignment.annotation().type->toString()
@ -266,14 +266,15 @@ void SMTChecker::endVisit(UnaryOperation const& _op)
{
case Token::Not: // !
{
solAssert(_op.annotation().type->category() == Type::Category::Bool, "");
solAssert(SSAVariable::isBool(_op.annotation().type->category()), "");
defineExpr(_op, !expr(_op.subExpression()));
break;
}
case Token::Inc: // ++ (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, "");
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.
}
else if (SSAVariable::supportedType(_identifier.annotation().type.get()))
else if (SSAVariable::isSupportedType(_identifier.annotation().type->category()))
defineExpr(_identifier, currentValue(*decl));
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)
{
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 right(expr(_op.rightExpression()));
Token::Value op = _op.getOperator();
smt::Expression value = (
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)
);
shared_ptr<smt::Expression> value;
if (SSAVariable::isInteger(_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)
);
}
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.
defineExpr(_op, value);
defineExpr(_op, *value);
}
else
m_errorReporter.warning(
@ -728,10 +745,10 @@ void SMTChecker::mergeVariables(vector<Declaration const*> const& _variables, sm
bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{
if (SSAVariable::supportedType(_varDecl.type().get()))
if (SSAVariable::isSupportedType(_varDecl.type()->category()))
{
solAssert(m_variables.count(&_varDecl) == 0, "");
m_variables.emplace(&_varDecl, SSAVariable(&_varDecl, *m_interface));
m_variables.emplace(&_varDecl, SSAVariable(_varDecl, *m_interface));
return true;
}
else

View File

@ -17,6 +17,7 @@
#include <libsolidity/formal/SSAVariable.h>
#include <libsolidity/formal/SymbolicBoolVariable.h>
#include <libsolidity/formal/SymbolicIntVariable.h>
#include <libsolidity/ast/AST.h>
@ -26,23 +27,35 @@ using namespace dev;
using namespace dev::solidity;
SSAVariable::SSAVariable(
Declaration const* _decl,
Declaration const& _decl,
smt::SolverInterface& _interface
)
{
resetIndex();
if (dynamic_cast<IntegerType const*>(_decl->type().get()))
if (isInteger(_decl.type()->category()))
m_symbolicVar = make_shared<SymbolicIntVariable>(_decl, _interface);
else if (isBool(_decl.type()->category()))
m_symbolicVar = make_shared<SymbolicBoolVariable>(_decl, _interface);
else
{
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()

View File

@ -37,7 +37,7 @@ public:
/// @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.
SSAVariable(
Declaration const* _decl,
Declaration const& _decl,
smt::SolverInterface& _interface
);
@ -68,8 +68,10 @@ public:
void setZeroValue();
void setUnknownValue();
/// So far Int is supported.
static bool supportedType(Type const* _decl);
/// So far Int and Bool are supported.
static bool isSupportedType(Type::Category _category);
static bool isInteger(Type::Category _category);
static bool isBool(Type::Category _category);
private:
smt::Expression valueAtSequence(int _seq) const

View File

@ -46,7 +46,8 @@ enum class Sort
{
Int,
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.
@ -132,10 +133,22 @@ public:
Expression operator()(Expression _a) const
{
solAssert(
sort == Sort::IntIntFun && arguments.empty(),
arguments.empty(),
"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;
@ -167,9 +180,18 @@ public:
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
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)
{

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;
SymbolicIntVariable::SymbolicIntVariable(
Declaration const* _decl,
Declaration const& _decl,
smt::SolverInterface& _interface
):
SymbolicVariable(_decl, _interface)
{
solAssert(m_declaration->type()->category() == Type::Category::Integer, "");
m_expression = make_shared<smt::Expression>(m_interface.newFunction(uniqueSymbol(), smt::Sort::Int, smt::Sort::Int));
solAssert(m_declaration.type()->category() == Type::Category::Integer, "");
}
smt::Expression SymbolicIntVariable::valueAtSequence(int _seq) const
{
return m_interface.newInteger(uniqueSymbol(_seq));
}
void SymbolicIntVariable::setZeroValue(int _seq)
@ -40,7 +44,7 @@ void SymbolicIntVariable::setZeroValue(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) <= maxValue(intType));
}

View File

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

View File

@ -164,85 +164,94 @@ bool CompilerStack::analyze()
resolveImports();
bool noErrors = true;
SyntaxChecker syntaxChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
DocStringAnalyser docStringAnalyser(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!docStringAnalyser.analyseDocStrings(*source->ast))
noErrors = false;
try {
SyntaxChecker syntaxChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
m_globalContext = make_shared<GlobalContext>();
NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast))
return false;
DocStringAnalyser docStringAnalyser(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!docStringAnalyser.analyseDocStrings(*source->ast))
noErrors = false;
map<string, SourceUnit const*> sourceUnitsByName;
for (auto& source: m_sources)
sourceUnitsByName[source.first] = source.second.ast.get();
for (Source const* source: m_sourceOrder)
if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false;
m_globalContext = make_shared<GlobalContext>();
NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast))
return false;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
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;
map<string, SourceUnit const*> sourceUnitsByName;
for (auto& source: m_sources)
sourceUnitsByName[source.first] = source.second.ast.get();
for (Source const* source: m_sourceOrder)
if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false;
// Note that we now reference contracts by their fully qualified names, and
// 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.
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
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())
m_contracts[contract->fullyQualifiedName()].contract = contract;
}
// Note that we now reference contracts by their fully qualified names, and
// 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);
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))
if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
m_contracts[contract->fullyQualifiedName()].contract = 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;
}
if (noErrors)
{
PostTypeChecker postTypeChecker(m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!postTypeChecker.check(*source->ast))
if (noErrors)
{
StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
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;
}
if (noErrors)
{
SMTChecker smtChecker(m_errorReporter, m_smtQuery);
for (Source const* source: m_sourceOrder)
smtChecker.analyze(*source->ast);
}
}
if (noErrors)
catch(FatalError const&)
{
StaticAnalyzer staticAnalyzer(m_errorReporter);
for (Source const* source: m_sourceOrder)
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;
}
if (noErrors)
{
SMTChecker smtChecker(m_errorReporter, m_smtQuery);
for (Source const* source: m_sourceOrder)
smtChecker.analyze(*source->ast);
if (m_errorReporter.errors().empty())
throw; // Something is weird here, rather throw again.
noErrors = false;
}
if (noErrors)

View File

@ -49,7 +49,7 @@ public:
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())
return v;
return {};

View File

@ -61,6 +61,9 @@ void ErrorReporter::warning(
void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, string const& _description)
{
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type);
*err <<
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)
{
if (checkForExcessiveErrors(_type))
return;
auto err = make_shared<Error>(_type);
*err <<
errinfo_sourceLocation(_location) <<
@ -80,6 +86,37 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
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)
{

View File

@ -102,7 +102,16 @@ private:
SourceLocation const& _location = SourceLocation(),
std::string const& _description = std::string());
// @returns true if error shouldn't be stored
bool checkForExcessiveErrors(Error::Type _type);
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();
using Id = ExpressionClasses::Id;
using Ids = vector<Id>;
// div(calldataload(0), 1 << 224) equals to hashValue
Id hashValue = classes.find(u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256(_signature)))));
Id calldata = classes.find(Instruction::CALLDATALOAD, Ids{classes.find(u256(0))});
classes.forceEqual(hashValue, Instruction::DIV, Ids{
calldata,
classes.find(u256(1) << (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);

View File

@ -119,21 +119,17 @@ DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
return _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 descStartPos = skipWhitespace(nameEndPos, _end);
if (descStartPos == _end)
auto nlPos = find(descStartPos, _end, '\n');
if (descStartPos == nlPos)
{
appendError("No description given for param " + paramName);
return _end;
}
auto nlPos = find(descStartPos, _end, '\n');
auto paramDesc = string(descStartPos, nlPos);
newTag("param");
m_lastTag->paramName = paramName;

View File

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

View File

@ -56,6 +56,7 @@ private:
/// This struct is shared for parsing a function header and a function type.
struct FunctionHeaderParserResult
{
bool isConstructor;
ASTPointer<ASTString> name;
ASTPointer<ParameterList> parameters;
ASTPointer<ParameterList> returnParameters;
@ -73,7 +74,11 @@ private:
ASTPointer<InheritanceSpecifier> parseInheritanceSpecifier();
Declaration::Visibility parseVisibilitySpecifier(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<FunctionDefinition> parseFunctionDefinition(ASTString const* _contractName);
ASTPointer<StructDefinition> parseStructDefinition();

View File

@ -53,7 +53,7 @@ namespace solidity
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)
{
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):
if '_build' in subdirs:
subdirs.remove('_build')
if 'compilationTests' in subdirs:
subdirs.remove('compilationTests')
for f in files:
path = join(root, f)
if docs:
cases = extract_docs_cases(path)
else:
cases = extract_test_cases(path)
if f.endswith(".sol"):
cases = [open(path, "r").read()]
else:
cases = extract_test_cases(path)
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
if grep -i trusty /etc/lsb-release >/dev/null 2>&1
then
# built from 1ecff3cac12f0fbbeea3e645f331d5ac026b24d3 at 2018-03-06
ETH_BINARY=eth_byzantium_trusty
ETH_HASH="5432ea81c150e8a3547615bf597cd6dce9e1e27b"
# built from 5ac09111bd0b6518365fe956e1bdb97a2db82af1 at 2018-04-05
ETH_BINARY=eth_2018-04-05_trusty
ETH_HASH="1e5e178b005e5b51f9d347df4452875ba9b53cc6"
else
# built from ?? at 2018-02-13 ?
ETH_BINARY=eth_byzantium_artful
ETH_HASH="e527dd3e3dc17b983529dd7dcfb74a0d3a5aed4e"
# built from 5ac09111bd0b6518365fe956e1bdb97a2db82af1 at 2018-04-05
ETH_BINARY=eth_2018-04-05_artful
ETH_HASH="eb2d0df022753bb2b442ba73e565a9babf6828d6"
fi
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"
@ -94,16 +94,23 @@ download_eth
ETH_PID=$(run_eth /tmp/test)
progress="--show-progress"
if [ "$CI" ]
if [ "$CIRCLECI" ]
then
progress=""
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 homestead / byzantium VM, # pointing to that IPC endpoint.
for optimize in "" "--optimize"
do
for vm in homestead byzantium
for vm in $EVM_VERSIONS
do
echo "--> Running tests using "$optimize" --evm-version "$vm"..."
log=""
@ -116,7 +123,7 @@ do
log=--logger=JUNIT,test_suite,$log_directory/noopt_$vm.xml $testargs_no_opt
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

View File

@ -9,6 +9,6 @@ do
echo -n $x " # "
# This subshell is a workaround to prevent the shell from printing
# "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
) | 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_strPrettyJson = "pretty-json";
static string const g_strVersion = "version";
static string const g_strIgnoreMissingFiles = "ignore-missing";
static string const g_argAbi = g_strAbi;
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_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr;
static string const g_argIgnoreMissingFiles = g_strIgnoreMissingFiles;
/// Possible arguments to for --combined-json
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;
if (!m_args.count(g_argInputFile))
addStdin = true;
@ -416,13 +419,27 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings()
auto infile = boost::filesystem::path(path);
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;
}
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;
}
@ -433,6 +450,8 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings()
}
if (addStdin)
m_sourceCodes[g_stdinFileName] = dev::readStandardInput();
return true;
}
bool CommandLineInterface::parseLibraryOption(string const& _input)
@ -599,7 +618,8 @@ Allowed options)",
g_argAllowPaths.c_str(),
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."
);
)
(g_argIgnoreMissingFiles.c_str(), "Ignore missing files.");
po::options_description outputComponents("Output Components");
outputComponents.add_options()
(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_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.")
(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);
po::options_description allOptions = desc;
@ -680,7 +700,7 @@ bool CommandLineInterface::processInput()
try
{
auto path = boost::filesystem::path(_path);
auto canonicalPath = boost::filesystem::canonical(path);
auto canonicalPath = weaklyCanonicalFilesystemPath(path);
bool isAllowed = false;
for (auto const& allowedDir: m_allowedDirectories)
{
@ -696,16 +716,16 @@ bool CommandLineInterface::processInput()
}
if (!isAllowed)
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."};
else if (!boost::filesystem::is_regular_file(canonicalPath))
if (!boost::filesystem::is_regular_file(canonicalPath))
return ReadCallback::Result{false, "Not a valid file."};
else
{
auto contents = dev::readFileAsString(canonicalPath.string());
m_sourceCodes[path.string()] = contents;
return ReadCallback::Result{true, contents};
}
auto contents = dev::readFileAsString(canonicalPath.string());
m_sourceCodes[path.string()] = contents;
return ReadCallback::Result{true, contents};
}
catch (Exception const& _exception)
{
@ -741,7 +761,8 @@ bool CommandLineInterface::processInput()
return true;
}
readInputFilesAndConfigureRemappings();
if (!readInputFilesAndConfigureRemappings())
return false;
if (m_args.count(g_argLibraries))
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