mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #3892 from ethereum/develop
Merge develop into release for 0.4.22
This commit is contained in:
commit
4cb486ee99
@ -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
|
||||
|
||||
|
@ -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
262
CODING_STYLE.md
Normal 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"
|
53
Changelog.md
53
Changelog.md
@ -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:
|
||||
|
@ -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).
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
40
circle.yml
40
circle.yml
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -422,6 +422,10 @@
|
||||
"bugs": [],
|
||||
"released": "2018-03-07"
|
||||
},
|
||||
"0.4.22": {
|
||||
"bugs": [],
|
||||
"released": "2018-04-16"
|
||||
},
|
||||
"0.4.3": {
|
||||
"bugs": [
|
||||
"ZeroFunctionSelector",
|
||||
|
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
========
|
||||
|
||||
|
@ -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
|
||||
|
@ -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? ')' )?
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
76
libdevcore/Algorithms.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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++);
|
||||
|
@ -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 } },
|
||||
|
@ -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) ||
|
||||
|
@ -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},
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
48
libjulia/optimiser/CommonSubexpressionEliminator.cpp
Normal file
48
libjulia/optimiser/CommonSubexpressionEliminator.cpp
Normal 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);
|
||||
}
|
45
libjulia/optimiser/CommonSubexpressionEliminator.h
Normal file
45
libjulia/optimiser/CommonSubexpressionEliminator.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
|
@ -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) };
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 '_'.
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
@ -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 {};
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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); }
|
||||
};
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
47
libsolidity/formal/SymbolicBoolVariable.cpp
Normal file
47
libsolidity/formal/SymbolicBoolVariable.cpp
Normal 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)
|
||||
{
|
||||
}
|
50
libsolidity/formal/SymbolicBoolVariable.h
Normal file
50
libsolidity/formal/SymbolicBoolVariable.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {};
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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
17
scripts/cpp-ethereum/build.sh
Executable 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
|
7
scripts/cpp-ethereum/eth_artful.docker
Normal file
7
scripts/cpp-ethereum/eth_artful.docker
Normal 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
|
13
scripts/cpp-ethereum/eth_trusty.docker
Normal file
13
scripts/cpp-ethereum/eth_trusty.docker
Normal 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
49
scripts/extract_test_cases.py
Executable 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)
|
||||
|
@ -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
6
scripts/isoltest.sh
Executable 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
43
scripts/soltest.sh
Executable 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}
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user