Merge pull request #6853 from ethereum/develop

Merge develop inte release for 0.5.9
This commit is contained in:
chriseth 2019-05-28 16:41:19 +02:00 committed by GitHub
commit e560f70d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
656 changed files with 15371 additions and 4719 deletions

View File

@ -30,7 +30,9 @@ defaults:
command: scripts/tests.sh --junit_report test_results
- run_regressions: &run_regressions
name: Regression tests
command: scripts/regressions.py -o test_results
command: |
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
scripts/regressions.py -o test_results
- solc_artifact: &solc_artifact
path: build/solc/solc
destination: solc
@ -43,12 +45,14 @@ defaults:
- ossfuzz_artifacts: &ossfuzz_artifacts
root: build
paths:
- test/tools/ossfuzz/solc_opt_ossfuzz
- test/tools/ossfuzz/solc_noopt_ossfuzz
- test/tools/ossfuzz/const_opt_ossfuzz
- test/tools/ossfuzz/solc_noopt_ossfuzz
- test/tools/ossfuzz/solc_opt_ossfuzz
- test/tools/ossfuzz/strictasm_assembly_ossfuzz
- test/tools/ossfuzz/strictasm_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz
- test/tools/ossfuzz/strictasm_opt_ossfuzz
- test/tools/ossfuzz/yul_proto_diff_ossfuzz
- test/tools/ossfuzz/yul_proto_ossfuzz
version: 2
jobs:
@ -134,6 +138,24 @@ jobs:
command: |
test/externalTests/zeppelin.sh /tmp/workspace/soljson.js || test/externalTests/zeppelin.sh /tmp/workspace/soljson.js
test_emscripten_external_colony:
docker:
- image: circleci/node:10
environment:
TERM: xterm
steps:
- checkout
- attach_workspace:
at: /tmp/workspace
- run:
name: Install test dependencies
command: |
sudo apt-get -qy install lsof
- run:
name: External ColonyNetworks tests
command: |
test/externalTests/colony.sh /tmp/workspace/soljson.js || test/externalTests/colony.sh /tmp/workspace/soljson.js
build_x86_linux:
docker:
- image: buildpack-deps:bionic
@ -337,6 +359,7 @@ jobs:
ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
build/test/soltest --logger=JUNIT,test_suite,test_results/result.xml -- --no-ipc --testpath test
- run:
name: Run commandline tests with ASAN
@ -344,6 +367,7 @@ jobs:
ulimit -a
# Increase stack size because ASan makes stack frames bigger and that breaks our assumptions (in tests).
ulimit -s 16384
export ASAN_OPTIONS="check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true:strict_string_checks=true:detect_invalid_pointer_pairs=2"
test/cmdlineTests.sh
- store_test_results:
path: test_results/
@ -416,11 +440,11 @@ jobs:
build_x86_linux_ossfuzz:
docker:
- image: buildpack-deps:cosmic
- image: buildpack-deps:disco
environment:
TERM: xterm
CC: /usr/bin/clang-7
CXX: /usr/bin/clang++-7
CC: /usr/bin/clang-8
CXX: /usr/bin/clang++-8
CMAKE_OPTIONS: -DOSSFUZZ=1 -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/libfuzzer.cmake
steps:
- checkout
@ -428,16 +452,18 @@ jobs:
name: Install build dependencies
command: |
apt-get -qq update
apt-get -qy install clang-7 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\*
apt-get -qy install wget clang-8 cmake libboost-regex-dev libboost-filesystem-dev libboost-test-dev libboost-system-dev libboost-program-options-dev libcvc4-dev libbz2-dev ninja-build zlib1g-dev libjsoncpp-dev=1.7.4-\*
./scripts/install_lpm.sh
./scripts/install_libfuzzer.sh
# Install evmone and dependencies (intx and ethash)
./scripts/install_evmone.sh
- run: *setup_prerelease_commit_hash
- run: *run_build_ossfuzz
- persist_to_workspace: *ossfuzz_artifacts
test_x86_ossfuzz_regression:
docker:
- image: buildpack-deps:cosmic
- image: buildpack-deps:disco
environment:
TERM: xterm
steps:
@ -448,13 +474,11 @@ jobs:
name: Install dependencies
command: |
apt-get -qq update
apt-get -qy install libcvc4-dev llvm-7-dev
apt-get -qy install libcvc4-dev llvm-8-dev
./scripts/download_ossfuzz_corpus.sh
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-7 1
update-alternatives --install /usr/bin/llvm-symbolizer llvm-symbolizer /usr/bin/llvm-symbolizer-8 1
- run: mkdir -p test_results
- run: *run_regressions
- store_test_results:
path: test_results/
- store_artifacts:
path: test_results/
destination: test_results/
@ -471,14 +495,6 @@ workflows:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external_zeppelin:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external_gnosis:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86_linux: *build_on_tags
- build_x86_linux_cxx17: *build_on_tags
- build_x86_clang7_asan: *build_on_tags
@ -521,8 +537,13 @@ workflows:
<<: *build_on_tags
requires:
- build_emscripten
- test_emscripten_external_colony:
<<: *build_on_tags
requires:
- build_emscripten
- build_x86_linux_ossfuzz: *build_on_tags
- test_x86_ossfuzz_regression:
<<: *build_on_tags
requires:
- build_x86_linux_ossfuzz

View File

@ -33,8 +33,8 @@ Please provide a *minimal* source code example to trigger the bug you have found
Please also mention any command line flags that are necessary for triggering the bug.
Provide as much information as necessary to reproduce the bug.
```
```solidity
// Some *minimal* Solidity source code to reproduce the bug.
// ...
```
-->
-->

View File

@ -10,7 +10,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
set(PROJECT_VERSION "0.5.8")
set(PROJECT_VERSION "0.5.9")
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
option(LLL "Build LLL" OFF)

View File

@ -1,3 +1,38 @@
### 0.5.9 (2019-05-28)
Language Features:
* Inline Assembly: Revert change introduced in 0.5.7: The ``callvalue()`` instruction does not require ``payable`` anymore.
* Static Analyzer: Disallow libraries calling themselves externally.
Compiler Features:
* Assembler: Encode the compiler version in the deployed bytecode.
* Code Generator: Fix handling of structs of dynamic size as constructor parameters.
* Commandline Interface: Experimental parser error recovery via the ``--error-recovery`` commandline switch.
* Inline Assembly: Disallow the combination of ``msize()`` and the Yul optimizer.
* Metadata: Add IPFS hashes of source files.
* Optimizer: Add rule to simplify SHL/SHR combinations.
* Optimizer: Add rules for multiplication and division by left-shifted one.
* SMTChecker: Support inherited state variables.
* SMTChecker: Support tuples and function calls with multiple return values.
* SMTChecker: Support ``delete``.
* SMTChecker: Inline external function calls to ``this``.
* Yul Optimizer: Simplify single-run ``for`` loops to ``if`` statements.
* Yul Optimizer: Optimize representation of numbers.
* Yul Optimizer: Do not inline recursive functions.
* Yul Optimizer: Do not remove instructions that affect ``msize()`` if ``msize()`` is used.
Bugfixes:
* Code Generator: Explicitly turn uninitialized internal function pointers into invalid functions when loaded from storage.
* Code Generator: Fix assertion failure when assigning structs containing array of mapping.
* Compiler Internals: Reset the Yul string repository before each compilation, freeing up memory.
* SMTChecker: Fix bad cast in base constructor modifier.
* SMTChecker: Fix internal error when visiting state variable inherited from base class.
* SMTChecker: Fix internal error in fixed point operations.
* SMTChecker: Fix internal error in assignment to unsupported type.
* SMTChecker: Fix internal error in branching when inlining function calls that modify local variables.
### 0.5.8 (2019-04-30)
Important Bugfixes:
@ -33,6 +68,7 @@ Bugfixes:
* SMTChecker: SSA control-flow did not take into account state variables that were modified inside inlined functions that were called inside branches.
* Type System: Use correct type name for contracts in event parameters when used in libraries. This affected code generation.
* Type System: Allow direct call to base class functions that have overloads.
* Type System: Warn about shadowing builtin variables if user variables are named ``this`` or ``super``.
* Yul: Properly register functions and disallow shadowing between function variables and variables in the outside scope.

View File

@ -37,6 +37,9 @@ function(create_build_info NAME)
-DETH_BUILD_COMPILER="${ETH_BUILD_COMPILER}"
-DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}"
-DPROJECT_VERSION="${PROJECT_VERSION}"
-DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}"
-DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}"
-DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}"
-P "${ETH_SCRIPTS_DIR}/buildinfo.cmake"
)
include_directories("${PROJECT_BINARY_DIR}/include")

View File

@ -144,7 +144,13 @@ else ()
endif ()
if (SANITIZE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=${SANITIZE}")
# Perform case-insensitive string compare
string(TOLOWER "${SANITIZE}" san)
# -fno-omit-frame-pointer gives more informative stack trace in case of an error
# -fsanitize-address-use-after-scope throws an error when a variable is used beyond its scope
if (san STREQUAL "address")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=address -fsanitize-address-use-after-scope")
endif()
endif()
# Code coverage support.

View File

@ -1,6 +1,9 @@
#pragma once
#define ETH_PROJECT_VERSION "@PROJECT_VERSION@"
#define ETH_PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define ETH_PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define ETH_PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define SOL_COMMIT_HASH "@SOL_COMMIT_HASH@"
#define ETH_BUILD_TYPE "@ETH_BUILD_TYPE@"
#define ETH_BUILD_OS "@ETH_BUILD_OS@"

View File

@ -67,7 +67,7 @@ jsoncpp:
license you like.
scanner/token:
The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are dervied from
The liblangutil/{CharStream,Scanner,Token}.{h,cpp} files are derived from
code originating from the V8 project licensed under the following terms:
Copyright 2006-2012, the V8 project authors. All rights reserved.

View File

@ -214,10 +214,11 @@ Given the contract:
pragma solidity >=0.4.16 <0.7.0;
contract Foo {
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
function bar(bytes3[2] memory) public pure {}
function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
function sam(bytes memory, bool, uint[] memory) public pure {}
}
@ -485,12 +486,13 @@ For example,
pragma solidity >=0.5.0 <0.7.0;
contract Test {
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
constructor() public { b = hex"12345678901234567890123456789012"; }
event Event(uint indexed a, bytes32 b);
event Event2(uint indexed a, bytes32 b);
function foo(uint a) public { emit Event(a, b); }
bytes32 b;
}
would result in the JSON:
@ -533,11 +535,12 @@ As an example, the code
pragma solidity >=0.4.19 <0.7.0;
pragma experimental ABIEncoderV2;
contract Test {
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
struct S { uint a; uint[] b; T[] c; }
struct T { uint x; uint y; }
function f(S memory s, T memory t, uint a) public;
function g() public returns (S memory s, T memory t, uint a);
}
would result in the JSON:

View File

@ -104,47 +104,48 @@ efficient code, for example:
pragma solidity >=0.4.16 <0.7.0;
library VectorSum {
// This function is less efficient because the optimizer currently fails to
// remove the bounds checks in array access.
function sumSolidity(uint[] memory _data) public pure returns (uint o_sum) {
function sumSolidity(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i)
o_sum += _data[i];
sum += _data[i];
}
// We know that we only access the array in bounds, so we can avoid the check.
// 0x20 needs to be added to an array because the first slot contains the
// array length.
function sumAsm(uint[] memory _data) public pure returns (uint o_sum) {
function sumAsm(uint[] memory _data) public pure returns (uint sum) {
for (uint i = 0; i < _data.length; ++i) {
assembly {
o_sum := add(o_sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
sum := add(sum, mload(add(add(_data, 0x20), mul(i, 0x20))))
}
}
}
// Same as above, but accomplish the entire code within inline assembly.
function sumPureAsm(uint[] memory _data) public pure returns (uint o_sum) {
function sumPureAsm(uint[] memory _data) public pure returns (uint sum) {
assembly {
// Load the length (first 32 bytes)
let len := mload(_data)
// Load the length (first 32 bytes)
let len := mload(_data)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// Skip over the length field.
//
// Keep temporary variable so it can be incremented in place.
//
// NOTE: incrementing _data would result in an unusable
// _data variable after this assembly block
let data := add(_data, 0x20)
// Iterate until the bound is not met.
for
{ let end := add(data, mul(len, 0x20)) }
lt(data, end)
{ data := add(data, 0x20) }
{
o_sum := add(o_sum, mload(data))
}
// Iterate until the bound is not met.
for
{ let end := add(data, mul(len, 0x20)) }
lt(data, end)
{ data := add(data, 0x20) }
{
sum := add(sum, mload(data))
}
}
}
}
@ -693,12 +694,13 @@ We consider the runtime bytecode of the following Solidity program::
pragma solidity >=0.4.16 <0.7.0;
contract C {
function f(uint x) public pure returns (uint y) {
y = 1;
for (uint i = 0; i < x; i++)
y = 2 * y;
}
function f(uint x) public pure returns (uint y) {
y = 1;
for (uint i = 0; i < x; i++)
y = 2 * y;
}
}
The following assembly will be generated::

View File

@ -1,4 +1,15 @@
[
{
"name": "DynamicConstructorArgumentsClippedABIV2",
"summary": "A contract's constructor that takes structs or arrays that contain dynamically-sized arrays reverts or decodes to invalid data.",
"description": "During construction of a contract, constructor parameters are copied from the code section to memory for decoding. The amount of bytes to copy was calculated incorrectly in case all parameters are statically-sized but contain dynamically-sized arrays as struct members or inner arrays. Such types are only available if ABIEncoderV2 is activated.",
"introduced": "0.4.16",
"fixed": "0.5.9",
"severity": "very low",
"conditions": {
"ABIEncoderV2": true
}
},
{
"name": "UninitializedFunctionPointerInConstructor",
"summary": "Calling uninitialized internal function pointers created in the constructor does not always revert and can cause unexpected behaviour.",

View File

@ -452,6 +452,7 @@
},
"0.4.16": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -462,6 +463,7 @@
},
"0.4.17": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -473,6 +475,7 @@
},
"0.4.18": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ExpExponentCleanup",
@ -483,6 +486,7 @@
},
"0.4.19": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -510,6 +514,7 @@
},
"0.4.20": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -521,6 +526,7 @@
},
"0.4.21": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -532,6 +538,7 @@
},
"0.4.22": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -543,6 +550,7 @@
},
"0.4.23": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -553,6 +561,7 @@
},
"0.4.24": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x",
@ -563,6 +572,7 @@
},
"0.4.25": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor_0.4.x",
"IncorrectEventSignatureInLibraries_0.4.x",
"ABIEncoderV2PackedStorage_0.4.x"
@ -570,7 +580,9 @@
"released": "2018-09-12"
},
"0.4.26": {
"bugs": [],
"bugs": [
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-29"
},
"0.4.3": {
@ -677,6 +689,7 @@
},
"0.5.0": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage"
@ -685,6 +698,7 @@
},
"0.5.1": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage"
@ -693,6 +707,7 @@
},
"0.5.2": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage"
@ -701,6 +716,7 @@
},
"0.5.3": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage"
@ -709,6 +725,7 @@
},
"0.5.4": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage"
@ -717,6 +734,7 @@
},
"0.5.5": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage",
@ -727,6 +745,7 @@
},
"0.5.6": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries",
"ABIEncoderV2PackedStorage",
@ -736,13 +755,20 @@
},
"0.5.7": {
"bugs": [
"DynamicConstructorArgumentsClippedABIV2",
"UninitializedFunctionPointerInConstructor",
"IncorrectEventSignatureInLibraries"
],
"released": "2019-03-26"
},
"0.5.8": {
"bugs": [],
"bugs": [
"DynamicConstructorArgumentsClippedABIV2"
],
"released": "2019-04-30"
},
"0.5.9": {
"bugs": [],
"released": "2019-05-28"
}
}

View File

@ -35,6 +35,7 @@ This means that cyclic creation dependencies are impossible.
pragma solidity >=0.4.22 <0.7.0;
contract OwnedToken {
// `TokenCreator` is a contract type that is defined below.
// It is fine to reference it as long as it is not used
@ -86,10 +87,11 @@ This means that cyclic creation dependencies are impossible.
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
public
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type is

View File

@ -59,7 +59,9 @@ logs that match a topic with a certain address value:
The hash of the signature of the event is one of the topics, except if you
declared the event with the ``anonymous`` specifier. This means that it is
not possible to filter for specific anonymous events by name.
not possible to filter for specific anonymous events by name, you can
only filter by the contract address. The advantage of anonymous events
is that they are cheaper to deploy and call.
::

View File

@ -12,7 +12,9 @@ is called, except when the contract name is explicitly given or the
When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts
is compiled into the created contract.
is compiled into the created contract. This means that all internal calls
to functions of base contracts also just use internal function calls
(``super.f(..)`` will use JUMP and not a message call).
The general inheritance system is very similar to
`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_,
@ -25,21 +27,24 @@ Details are given in the following example.
pragma solidity >=0.5.0 <0.7.0;
contract owned {
contract Owned {
constructor() public { owner = msg.sender; }
address payable owner;
}
// Use `is` to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract mortal is owned {
contract Mortal is Owned {
function kill() public {
if (msg.sender == owner) selfdestruct(owner);
}
}
// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
@ -48,15 +53,17 @@ Details are given in the following example.
function lookup(uint id) public returns (address adr);
}
contract NameReg {
function register(bytes32 name) public;
function unregister() public;
}
}
// Multiple inheritance is possible. Note that `owned` is
// also a base class of `mortal`, yet there is only a single
// instance of `owned` (as for virtual inheritance in C++).
contract named is owned, mortal {
contract Named is Owned, Mortal {
constructor(bytes32 name) public {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
@ -73,22 +80,23 @@ Details are given in the following example.
NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific
// overridden function.
mortal.kill();
Mortal.kill();
}
}
}
// If a constructor takes an argument, it needs to be
// provided in the header (or modifier-invocation-style at
// the constructor of the derived contract (see below)).
contract PriceFeed is owned, mortal, named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
contract PriceFeed is Owned, Mortal, Named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
function get() public view returns(uint r) { return info; }
function get() public view returns(uint r) { return info; }
uint info;
uint info;
}
Note that above, we call ``mortal.kill()`` to "forward" the

View File

@ -49,46 +49,48 @@ more advanced example to implement a set).
pragma solidity >=0.4.22 <0.7.0;
library Set {
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
// We define a new struct datatype that will be used to
// hold its data in the calling contract.
struct Data { mapping(uint => bool) flags; }
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter `self`, if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
// Note that the first parameter is of type "storage
// reference" and thus only its storage address and not
// its contents is passed as part of the call. This is a
// special feature of library functions. It is idiomatic
// to call the first parameter `self`, if the function can
// be seen as a method of that object.
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // already there
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // not there
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;

View File

@ -43,7 +43,7 @@ For state variables, ``external`` is not possible.
.. note::
Everything that is inside a contract is visible to
all observers external to the blockchain. Making something ``private``
only prevents other contracts from accessing and modifying
only prevents other contracts from reading or modifying
the information, but it will still be visible to the
whole world outside of the blockchain.

View File

@ -96,7 +96,7 @@ The option ``--no-smt`` disables the tests that require ``libz3`` and
``--no-ipc`` disables those that require ``aleth``.
If you want to run the ipc tests (that test the semantics of the generated code),
you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0-rc.1/aleth-1.6.0-rc.1-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``.
you need to install `aleth <https://github.com/ethereum/aleth/releases/download/v1.6.0/aleth-1.6.0-linux-x86_64.tar.gz>`_ and run it in testing mode: ``aleth --db memorydb --test -d /tmp/testeth``.
To run the actual tests, use: ``./scripts/soltest.sh --ipcpath /tmp/testeth/geth.ipc``.

View File

@ -4,10 +4,18 @@
Modular Contracts
*****************
A modular approach to building your contracts helps you prevent overflow risks
and unexpected tokens. In the example below, the contract uses the ``move`` method
A modular approach to building your contracts helps you reduce the complexity
and improve the readability which will help to identify bugs and vulnerabilities
during development and code review.
If you specify and control the behaviour or each module in isolation, the
interactions you have to consider are only those between the module specifications
and not every other moving part of the contract.
In the example below, the contract uses the ``move`` method
of the ``Balances`` :ref:`library <libraries>` to check that balances sent between
addresses match what you expect.
addresses match what you expect. In this way, the ``Balances`` library
provides an isolated component that properly tracks balances of accounts.
It is easy to verify that the ``Balances`` library never produces negative balances or overflows
and the sum of all balances is an invariant across the lifetime of the contract.
::

View File

@ -20,10 +20,11 @@ Remix
*We recommend Remix for small contracts and for quickly learning Solidity.*
`Access Remix online <https://remix.ethereum.org/>`_, you don't need to install anything.
`Access Remix online <https://remix.ethereum.org/>`_, you do not need to install anything.
If you want to use it without connection to the Internet, go to
https://github.com/ethereum/remix-live/tree/gh-pages and download the ``.zip`` file as
explained on that page.
explained on that page. Remix is also a convenient option for testing nightly builds
without installing multiple Solidity versions.
Further options on this page detail installing commandline Solidity compiler software
on your computer. Choose a commandline compiler if you are working on a larger contract
@ -60,17 +61,36 @@ Please refer to the solc-js repository for instructions.
Docker
======
We provide up to date docker builds for the compiler. The ``stable``
repository contains released versions while the ``nightly``
repository contains potentially unstable changes in the develop branch.
Docker images of Solidity builds are available using the ``solc`` image from the ``ethereum`` organisation.
Use the ``stable`` tag for the latest released version, and ``nightly`` for potentially unstable changes in the develop branch.
The Docker image runs the compiler executable, so you can pass all compiler arguments to it.
For example, the command below pulls the stable version of the ``solc`` image (if you do not have it already),
and runs it in a new container, passing the ``--help`` argument.
.. code-block:: bash
docker run ethereum/solc:stable --version
docker run ethereum/solc:stable --help
Currently, the docker image only contains the compiler executable,
so you have to do some additional work to link in the source and
output directories.
You can also specify release build versions in the tag, for example, for the 0.5.4 release.
.. code-block:: bash
docker run ethereum/solc:0.5.4 --help
To use the Docker image to compile Solidity files on the host machine mount a
local folder for input and output, and specify the contract to compile. For example.
.. code-block:: bash
docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/Contract.sol
You can also use the standard JSON interface (which is recommended when using the compiler with tooling).
When using this interface it is not necessary to mount any directories.
.. code-block:: bash
docker run ethereum/solc:stable --standard-json < input.json > output.json
Binary Packages
===============

View File

@ -31,10 +31,9 @@ Storage Example
}
}
The first line simply tells that the source code is written for
Solidity version 0.4.0 or anything newer that does not break functionality
(up to, but not including, version 0.7.0). This is to ensure that the
contract is not compilable with a new (breaking) compiler version, where it could behave differently.
The first line tells you that the source code is written for
Solidity version 0.4.0, or a newer version of the language up to, but not including version 0.7.0.
This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently.
:ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_).
@ -42,9 +41,9 @@ A contract in the sense of Solidity is a collection of code (its *functions*) an
data (its *state*) that resides at a specific address on the Ethereum
blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of
type ``uint`` (*u*\nsigned *int*\eger of *256* bits). You can think of it as a single slot
in a database that can be queried and altered by calling functions of the
in a database that you can query and alter by calling functions of the
code that manages the database. In the case of Ethereum, this is always the owning
contract. And in this case, the functions ``set`` and ``get`` can be used to modify
contract. In this case, the functions ``set`` and ``get`` can be used to modify
or retrieve the value of the variable.
To access a state variable, you do not need the prefix ``this.`` as is common in
@ -53,9 +52,9 @@ other languages.
This contract does not do much yet apart from (due to the infrastructure
built by Ethereum) allowing anyone to store a single number that is accessible by
anyone in the world without a (feasible) way to prevent you from publishing
this number. Of course, anyone could just call ``set`` again with a different value
and overwrite your number, but the number will still be stored in the history
of the blockchain. Later, we will see how you can impose access restrictions
this number. Anyone could call ``set`` again with a different value
and overwrite your number, but the number is still stored in the history
of the blockchain. Later, you will see how you can impose access restrictions
so that only you can alter the number.
.. note::
@ -64,7 +63,7 @@ so that only you can alter the number.
.. warning::
Be careful with using Unicode text, as similar looking (or even identical) characters can
have different code points and as such will be encoded as a different byte array.
have different code points and as such are encoded as a different byte array.
.. index:: ! subcurrency

View File

@ -124,30 +124,35 @@ Encoding of the Metadata Hash in the Bytecode
=============================================
Because we might support other ways to retrieve the metadata file in the future,
the mapping ``{"bzzr0": <Swarm hash>}`` is stored
the mapping ``{"bzzr0": <Swarm hash>, "solc": <compiler version>}`` is stored
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might
contain more keys (see below) and the beginning of that
encoding is not easy to find, its length is added in a two-byte big-endian
encoding. The current version of the Solidity compiler usually adds the following
to the end of the deployed bytecode::
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
0xa2
0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash>
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding>
0x00 0x32
So in order to retrieve the data, the end of the deployed bytecode can be checked
to match that pattern and use the Swarm hash to retrieve the file.
Whereas release builds of solc use a 3 byte encoding of the version as shown
above (one byte each for major, minor and patch version number), prerelease builds
will instead use a complete version string including commit hash and build date.
.. note::
The CBOR mapping can also contain other keys, so it is better to fully
decode the data instead of relying on it starting with ``0xa165``.
decode the data instead of relying on it starting with ``0xa265``.
For example, if any experimental features that affect code generation
are used, the mapping will also contain ``"experimental": true``.
Furthermore, we are planning to add the compiler version to the mapping
to ease source-verification and scanning for bugs.
.. note::
The compiler currently uses the "swarm version 0" hash of the metadata,
but this might change in the future, so do not rely on this sequence
to start with ``0xa1 0x65 'b' 'z' 'z' 'r' '0'``. We might also
to start with ``0xa2 0x65 'b' 'z' 'z' 'r' '0'``. We might also
add additional data to this CBOR structure, so the
best option is to use a proper CBOR parser.

View File

@ -20,7 +20,8 @@ to take too much care, but if you manage your bank account using that web servic
you should be more careful.
This section will list some pitfalls and general security recommendations but
can, of course, never be complete. Also, keep in mind that even if your
can, of course, never be complete.
Also, keep in mind that even if your
smart contract code is bug-free, the compiler or the platform itself might
have a bug. A list of some publicly known security-relevant bugs of the compiler
can be found in the
@ -31,6 +32,10 @@ Solidity compiler.
As always, with open source documentation, please help us extend this section
(especially, some examples would not hurt)!
NOTE: In addition to the list below, you can find more security recommendations and best practices
`in Guy Lando's knowledge list <https://github.com/guylando/KnowledgeLists/blob/master/EthereumSmartContracts.md>`_ and
`the Consensys GitHub repo <https://consensys.github.io/smart-contract-best-practices/>`_.
********
Pitfalls
********

View File

@ -26,11 +26,11 @@ doing, an explicit type conversion is sometimes possible. Note that this may
give you some unexpected behaviour and allows you to bypass some security
features of the compiler, so be sure to test that the
result is what you want! Take the following example where you are converting
a negative ``int8`` to a ``uint``:
a negative ``int`` to a ``uint``:
::
int8 y = -3;
int y = -3;
uint x = uint(y);
At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex

View File

@ -7,6 +7,8 @@ If ``a`` is an LValue (i.e. a variable or something that can be assigned to), th
``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change.
.. _delete:
delete
------

View File

@ -217,13 +217,13 @@ Array Members
For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array.
Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion.
Increasing the length adds new zero-initialised elements to the array.
Reducing the length performs an implicit :ref:``delete`` on each of the
Reducing the length performs an implicit :ref:`delete<delete>` on each of the
removed elements. If you try to resize a non-dynamic array that isn't in
storage, you receive a ``Value must be an lvalue`` error.
**push**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length.
**pop**:
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element.
Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:`delete<delete>` on the removed element.
.. warning::
If you use ``.length--`` on an empty array, it causes an underflow and
@ -234,7 +234,7 @@ Array Members
storage is assumed to be zero-initialised, while decreasing
the length has at least linear cost (but in most cases worse than linear),
because it includes explicitly clearing the removed
elements similar to calling :ref:``delete`` on them.
elements similar to calling :ref:`delete<delete>` on them.
.. note::
It is not yet possible to use arrays of arrays in external functions
@ -371,7 +371,8 @@ shown in the following example:
campaignID = numCampaigns++; // campaignID is return variable
// Creates new struct in memory and copies it to storage.
// We leave out the mapping type, because it is not valid in memory.
// If structs are copied (even from storage to storage), mapping types
// If structs are copied (even from storage to storage),
// types that are not valid outside of storage (ex. mappings and array of mappings)
// are always omitted, because they cannot be enumerated.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}

View File

@ -194,7 +194,7 @@ Mathematical and Cryptographic Functions
the ecrecover function remained unchanged.
This is usually not a problem unless you require signatures to be unique or
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/docs/cryptography_ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/v2.3.0/api/cryptography#ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
.. note::

View File

@ -47,20 +47,21 @@ if the backend changes. For a list of mandatory built-in functions, see the sect
The following example program assumes that the EVM opcodes ``mul``, ``div``
and ``mod`` are available either natively or as functions and computes exponentiation.
As per the warning above, the following code is untyped and can be compiled using ``solc --strict-assembly``.
.. code::
{
function power(base:u256, exponent:u256) -> result:u256
function power(base, exponent) -> result
{
switch exponent
case 0:u256 { result := 1:u256 }
case 1:u256 { result := base }
case 0 { result := 1 }
case 1 { result := base }
default
{
result := power(mul(base, base), div(exponent, 2:u256))
switch mod(exponent, 2:u256)
case 1:u256 { result := mul(base, result) }
result := power(mul(base, base), div(exponent, 2))
switch mod(exponent, 2)
case 1 { result := mul(base, result) }
}
}
}
@ -72,10 +73,10 @@ and ``add`` to be available.
.. code::
{
function power(base:u256, exponent:u256) -> result:u256
function power(base, exponent) -> result
{
result := 1:u256
for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
result := 1
for { let i := 0 } lt(i, exponent) { i := add(i, 1) }
{
result := mul(result, base)
}
@ -593,15 +594,21 @@ An example Yul Object is shown below:
// are in scope without nested access.
object "Contract1" {
code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
// first create "runtime.Contract2"
let size = datasize("runtime.Contract2")
let offset = allocate(size)
let size := datasize("runtime.Contract2")
let offset := allocate(size)
// This will turn into a memory->memory copy for eWASM and
// a codecopy for EVM
datacopy(offset, dataoffset("runtime.Contract2"), size)
// constructor parameter is a single number 0x1234
mstore(add(offset, size), 0x1234)
create(offset, add(size, 32))
pop(create(offset, add(size, 32), 0))
// now return the runtime object (this is
// constructor code)
@ -617,16 +624,22 @@ An example Yul Object is shown below:
object "runtime" {
code {
function allocate(size) -> ptr {
ptr := mload(0x40)
if iszero(ptr) { ptr := 0x60 }
mstore(0x40, add(ptr, size))
}
// runtime code
let size = datasize("Contract2")
let offset = allocate(size)
let size := datasize("Contract2")
let offset := allocate(size)
// This will turn into a memory->memory copy for eWASM and
// a codecopy for EVM
datacopy(offset, dataoffset("Contract2"), size)
// constructor parameter is a single number 0x1234
mstore(add(offset, size), 0x1234)
create(offset, add(size, 32))
pop(create(offset, add(size, 32), 0))
}
// Embedded object. Use case is that the outside is a factory contract,
@ -640,9 +653,9 @@ An example Yul Object is shown below:
code {
// code here ...
}
}
}
data "Table1" hex"4123"
data "Table1" hex"4123"
}
}
}

View File

@ -12,10 +12,13 @@ set(sources
FixedHash.h
IndentedWriter.cpp
IndentedWriter.h
IpfsHash.cpp
IpfsHash.h
JSON.cpp
JSON.h
Keccak256.cpp
Keccak256.h
picosha2.h
Result.h
StringUtils.cpp
StringUtils.h

View File

@ -91,6 +91,20 @@ inline u256 s2u(s256 _u)
return u256(c_end + _u);
}
inline u256 exp256(u256 _base, u256 _exponent)
{
using boost::multiprecision::limb_type;
u256 result = 1;
while (_exponent)
{
if (boost::multiprecision::bit_test(_exponent, 0))
result *= _base;
_base *= _base;
_exponent >>= 1;
}
return result;
}
inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
{
std::ostringstream ss;

View File

@ -111,7 +111,7 @@ enum class HexCase
/// @example toHex("A\x69") == "4169"
std::string toHex(bytes const& _data, HexPrefix _prefix = HexPrefix::DontAdd, HexCase _case = HexCase::Lower);
/// Converts a (printable) ASCII hex character into the correspnding integer value.
/// Converts a (printable) ASCII hex character into the corresponding integer value.
/// @example fromHex('A') == 10 && fromHex('f') == 15 && fromHex('5') == 5
int fromHex(char _i, WhenError _throw);
@ -188,12 +188,6 @@ inline bytes toCompactBigEndian(uint8_t _val, unsigned _min = 0)
return (_min || _val) ? bytes{ _val } : bytes{};
}
/// Workarounds shift left bug in boost <1.65.1.
template <class S> S bigintShiftLeftWorkaround(S const& _a, unsigned _b)
{
return (S)(bigint(_a) << _b);
}
/// Convenience function for conversion of a u256 to hex
inline std::string toHex(u256 val, HexPrefix prefix = HexPrefix::DontAdd)
{
@ -352,4 +346,27 @@ inline std::string findAnyOf(std::string const& _haystack, std::vector<std::stri
return needle;
return "";
}
namespace detail
{
template<typename T>
void variadicEmplaceBack(std::vector<T>&) {}
template<typename T, typename A, typename... Args>
void variadicEmplaceBack(std::vector<T>& _vector, A&& _a, Args&&... _args)
{
_vector.emplace_back(std::forward<A>(_a));
variadicEmplaceBack(_vector, std::forward<Args>(_args)...);
}
}
template<typename T, typename... Args>
std::vector<T> make_vector(Args&&... _args)
{
std::vector<T> result;
result.reserve(sizeof...(_args));
detail::variadicEmplaceBack(result, std::forward<Args>(_args)...);
return result;
}
}

View File

@ -47,6 +47,7 @@ private:
DEV_SIMPLE_EXCEPTION(InvalidAddress);
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
DEV_SIMPLE_EXCEPTION(FileError);
DEV_SIMPLE_EXCEPTION(DataTooLong);
// error information to be added to exceptions
using errinfo_invalidSymbol = boost::error_info<struct tag_invalidSymbol, char>;

93
libdevcore/IpfsHash.cpp Normal file
View File

@ -0,0 +1,93 @@
/*
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 <libdevcore/IpfsHash.h>
#include <libdevcore/Exceptions.h>
#include <libdevcore/picosha2.h>
#include <libdevcore/CommonData.h>
using namespace std;
using namespace dev;
namespace
{
bytes varintEncoding(size_t _n)
{
bytes encoded;
while (_n > 0x7f)
{
encoded.emplace_back(uint8_t(0x80 | (_n & 0x7f)));
_n >>= 7;
}
encoded.emplace_back(_n);
return encoded;
}
string base58Encode(bytes const& _data)
{
static string const alphabet{"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"};
bigint data(toHex(_data, HexPrefix::Add));
string output;
while (data)
{
output += alphabet[size_t(data % alphabet.size())];
data /= alphabet.size();
}
reverse(output.begin(), output.end());
return output;
}
}
bytes dev::ipfsHash(string _data)
{
if (_data.length() >= 1024 * 256)
BOOST_THROW_EXCEPTION(
DataTooLong() <<
errinfo_comment("Ipfs hash for large (chunked) files not yet implemented.")
);
bytes lengthAsVarint = varintEncoding(_data.size());
bytes protobufEncodedData;
// Type: File
protobufEncodedData += bytes{0x08, 0x02};
if (!_data.empty())
{
// Data (length delimited bytes)
protobufEncodedData += bytes{0x12};
protobufEncodedData += lengthAsVarint;
protobufEncodedData += asBytes(std::move(_data));
}
// filesize: length as varint
protobufEncodedData += bytes{0x18} + lengthAsVarint;
// PBDag:
// Data: (length delimited bytes)
size_t protobufLength = protobufEncodedData.size();
bytes blockData = bytes{0x0a} + varintEncoding(protobufLength) + std::move(protobufEncodedData);
// TODO Handle "large" files with multiple blocks
// Multihash: sha2-256, 256 bits
bytes hash = bytes{0x12, 0x20} + picosha2::hash256(std::move(blockData));
return hash;
}
string dev::ipfsHashBase58(string _data)
{
return base58Encode(ipfsHash(std::move(_data)));
}

37
libdevcore/IpfsHash.h Normal file
View File

@ -0,0 +1,37 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <libdevcore/Common.h>
#include <string>
namespace dev
{
/// Compute the "ipfs hash" of a file with the content @a _data.
/// The output will be the multihash of the UnixFS protobuf encoded data.
/// As hash function it will use sha2-256.
/// The effect is that the hash should be identical to the one produced by
/// the command `ipfs add <filename>`.
bytes ipfsHash(std::string _data);
/// Compute the "ipfs hash" as above, but encoded in base58 as used by ipfs / bitcoin.
std::string ipfsHashBase58(std::string _data);
}

View File

@ -74,6 +74,22 @@ std::string joinHumanReadable
return result;
}
/// Joins collection of strings just like joinHumanReadable, but prepends the separator
/// unless the collection is empty.
template<class T>
std::string joinHumanReadablePrefixed
(
T const& _list,
std::string const& _separator = ", ",
std::string const& _lastSeparator = ""
)
{
if (begin(_list) == end(_list))
return {};
else
return _separator + joinHumanReadable(_list, _separator, _lastSeparator);
}
/// Formats large numbers to be easily readable by humans.
/// Returns decimal representation for smaller numbers; hex for large numbers.
/// "Special" numbers, powers-of-two and powers-of-two minus 1, are returned in

View File

@ -30,64 +30,73 @@
using namespace std;
using namespace dev;
Whiskers::Whiskers(string const& _template):
m_template(_template)
Whiskers::Whiskers(string _template):
m_template(move(_template))
{
}
Whiskers& Whiskers::operator ()(string const& _parameter, string const& _value)
Whiskers& Whiskers::operator()(string _parameter, string _value)
{
assertThrow(
m_parameters.count(_parameter) == 0,
WhiskersError,
_parameter + " already set."
);
assertThrow(
m_listParameters.count(_parameter) == 0,
WhiskersError,
_parameter + " already set as list parameter."
);
m_parameters[_parameter] = _value;
checkParameterUnknown(_parameter);
m_parameters[move(_parameter)] = move(_value);
return *this;
}
Whiskers& Whiskers::operator ()(
string const& _listParameter,
vector<map<string, string>> const& _values
Whiskers& Whiskers::operator()(string _parameter, bool _value)
{
checkParameterUnknown(_parameter);
m_conditions[move(_parameter)] = _value;
return *this;
}
Whiskers& Whiskers::operator()(
string _listParameter,
vector<map<string, string>> _values
)
{
assertThrow(
m_listParameters.count(_listParameter) == 0,
WhiskersError,
_listParameter + " already set."
);
assertThrow(
m_parameters.count(_listParameter) == 0,
WhiskersError,
_listParameter + " already set as value parameter."
);
m_listParameters[_listParameter] = _values;
checkParameterUnknown(_listParameter);
m_listParameters[move(_listParameter)] = move(_values);
return *this;
}
string Whiskers::render() const
{
return replace(m_template, m_parameters, m_listParameters);
return replace(m_template, m_parameters, m_conditions, m_listParameters);
}
void Whiskers::checkParameterUnknown(string const& _parameter)
{
assertThrow(
!m_parameters.count(_parameter),
WhiskersError,
_parameter + " already set as value parameter."
);
assertThrow(
!m_conditions.count(_parameter),
WhiskersError,
_parameter + " already set as condition parameter."
);
assertThrow(
!m_listParameters.count(_parameter),
WhiskersError,
_parameter + " already set as list parameter."
);
}
string Whiskers::replace(
string const& _template,
StringMap const& _parameters,
map<string, bool> const& _conditions,
map<string, vector<StringMap>> const& _listParameters
)
{
using namespace boost;
static regex listOrTag("<([^#/>]+)>|<#([^>]+)>(.*?)</\\2>");
static regex listOrTag("<([^#/?!>]+)>|<#([^>]+)>(.*?)</\\2>|<\\?([^>]+)>(.*?)(<!\\4>(.*?))?</\\4>");
return regex_replace(_template, listOrTag, [&](match_results<string::const_iterator> _match) -> string
{
string tagName(_match[1]);
string listName(_match[2]);
string conditionName(_match[4]);
if (!tagName.empty())
{
assertThrow(
@ -99,20 +108,32 @@ string Whiskers::replace(
);
return _parameters.at(tagName);
}
else
else if (!listName.empty())
{
string listName(_match[2]);
string templ(_match[3]);
assertThrow(!listName.empty(), WhiskersError, "");
assertThrow(
_listParameters.count(listName),
WhiskersError, "List parameter " + listName + " not set."
);
string replacement;
for (auto const& parameters: _listParameters.at(listName))
replacement += replace(templ, joinMaps(_parameters, parameters));
replacement += replace(templ, joinMaps(_parameters, parameters), _conditions);
return replacement;
}
else
{
assertThrow(!conditionName.empty(), WhiskersError, "");
assertThrow(
_conditions.count(conditionName),
WhiskersError, "Condition parameter " + conditionName + " not set."
);
return replace(
_conditions.at(conditionName) ? _match[5] : _match[7],
_parameters,
_conditions,
_listParameters
);
}
});
}

View File

@ -34,45 +34,63 @@ namespace dev
DEV_SIMPLE_EXCEPTION(WhiskersError);
///
/// Moustache-like templates.
///
/// Usage:
/// std::vector<std::map<std::string, std::string>> listValues(2);
/// listValues[0]["k"] = "key1";
/// listValues[0]["v"] = "value1";
/// listValues[1]["k"] = "key2";
/// listValues[1]["v"] = "value2";
/// auto s = Whiskers("<p1>\n<#list><k> -> <v>\n</list>")
/// ("p1", "HEAD")
/// ("list", listValues)
/// .render();
///
/// results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
///
/// Note that lists cannot themselves contain lists - this would be a future feature.
/**
* Moustache-like templates.
*
* Usage:
* std::vector<std::map<std::string, std::string>> listValues(2);
* listValues[0]["k"] = "key1";
* listValues[0]["v"] = "value1";
* listValues[1]["k"] = "key2";
* listValues[1]["v"] = "value2";
* auto s = Whiskers("<?c><p1><!c>y</c>\n<#list><k> -> <v>\n</list>")
* ("p1", "HEAD")
* ("c", true)
* ("list", listValues)
* .render();
*
* results in s == "HEAD\nkey1 -> value1\nkey2 -> value2\n"
*
* Note that lists cannot themselves contain lists - this would be a future feature.
*
* The elements are:
* - Regular parameter: <name>
* just replaced
* - Condition parameter: <?name>...<!name>...</name>, where "<!name>" is optional
* replaced (and recursively expanded) by the first part if the condition is true
* and by the second (or empty string if missing) if the condition is false
* - List parameter: <#list>...</list>
* The part between the tags is repeated as often as values are provided
* in the mapping. Each list element can have its own parameter -> value mapping.
*/
class Whiskers
{
public:
using StringMap = std::map<std::string, std::string>;
using StringListMap = std::map<std::string, std::vector<StringMap>>;
explicit Whiskers(std::string const& _template);
explicit Whiskers(std::string _template);
/// Sets a single parameter, <paramName>.
Whiskers& operator()(std::string const& _parameter, std::string const& _value);
/// Sets a single regular parameter, <paramName>.
Whiskers& operator()(std::string _parameter, std::string _value);
Whiskers& operator()(std::string _parameter, char const* _value) { return (*this)(_parameter, std::string{_value}); }
/// Sets a condition parameter, <?paramName>...<!paramName>...</paramName>
Whiskers& operator()(std::string _parameter, bool _value);
/// Sets a list parameter, <#listName> </listName>.
Whiskers& operator()(
std::string const& _listParameter,
std::vector<StringMap> const& _values
std::string _listParameter,
std::vector<StringMap> _values
);
std::string render() const;
private:
void checkParameterUnknown(std::string const& _parameter);
static std::string replace(
std::string const& _template,
StringMap const& _parameters,
std::map<std::string, bool> const& _conditions,
StringListMap const& _listParameters = StringListMap()
);
@ -81,6 +99,7 @@ private:
std::string m_template;
StringMap m_parameters;
std::map<std::string, bool> m_conditions;
StringListMap m_listParameters;
};

288
libdevcore/picosha2.h Normal file
View File

@ -0,0 +1,288 @@
/*
The MIT License (MIT)
Copyright (C) 2014 okdshin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#pragma once
//picosha2:20140213
#include <cstdint>
#include <iostream>
#include <vector>
#include <iterator>
#include <cassert>
#include <sstream>
#include <algorithm>
namespace picosha2
{
namespace detail
{
inline uint8_t mask_8bit(uint8_t x)
{
return x & 0xff;
}
inline uint32_t mask_32bit(uint32_t x)
{
return x & 0xffffffff;
}
static uint32_t const add_constant[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
static uint32_t const initial_message_digest[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
inline uint32_t ch(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) ^ ((~x) & z);
}
inline uint32_t maj(uint32_t x, uint32_t y, uint32_t z)
{
return (x & y) ^ (x & z) ^ (y & z);
}
inline uint32_t rotr(uint32_t x, std::size_t n)
{
assert(n < 32);
return mask_32bit((x >> n) | (x << (32 - n)));
}
inline uint32_t bsig0(uint32_t x)
{
return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
}
inline uint32_t bsig1(uint32_t x)
{
return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
}
inline uint32_t shr(uint32_t x, std::size_t n)
{
assert(n < 32);
return x >> n;
}
inline uint32_t ssig0(uint32_t x)
{
return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3);
}
inline uint32_t ssig1(uint32_t x)
{
return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10);
}
template<typename RaIter1, typename RaIter2>
void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last)
{
(void)last; // FIXME: check this is valid
uint32_t w[64];
std::fill(w, w+64, 0);
for (std::size_t i = 0; i < 16; ++i)
w[i] = (static_cast<uint32_t>(mask_8bit(*(first + i * 4))) << 24)
| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 1))) << 16)
| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 2))) << 8)
| (static_cast<uint32_t>(mask_8bit(*(first + i * 4 + 3))));
for (std::size_t i = 16; i < 64; ++i)
w[i] = mask_32bit(ssig1(w[i-2])+w[i-7]+ssig0(w[i-15])+w[i-16]);
uint32_t a = *message_digest;
uint32_t b = *(message_digest + 1);
uint32_t c = *(message_digest + 2);
uint32_t d = *(message_digest + 3);
uint32_t e = *(message_digest + 4);
uint32_t f = *(message_digest + 5);
uint32_t g = *(message_digest + 6);
uint32_t h = *(message_digest + 7);
for (std::size_t i = 0; i < 64; ++i)
{
uint32_t temp1 = h+bsig1(e)+ch(e,f,g)+add_constant[i]+w[i];
uint32_t temp2 = bsig0(a)+maj(a,b,c);
h = g;
g = f;
f = e;
e = mask_32bit(d+temp1);
d = c;
c = b;
b = a;
a = mask_32bit(temp1+temp2);
}
*message_digest += a;
*(message_digest+1) += b;
*(message_digest+2) += c;
*(message_digest+3) += d;
*(message_digest+4) += e;
*(message_digest+5) += f;
*(message_digest+6) += g;
*(message_digest+7) += h;
for (std::size_t i = 0; i < 8; ++i)
*(message_digest+i) = mask_32bit(*(message_digest+i));
}
}//namespace detail
class hash256_one_by_one
{
public:
hash256_one_by_one()
{
init();
}
void init()
{
buffer_.clear();
std::fill(data_length_digits_, data_length_digits_ + 4, 0);
std::copy(detail::initial_message_digest, detail::initial_message_digest+8, h_);
}
template<typename RaIter>
void process(RaIter first, RaIter last)
{
add_to_data_length(std::distance(first, last));
std::copy(first, last, std::back_inserter(buffer_));
std::size_t i = 0;
for (;i + 64 <= buffer_.size(); i+=64)
detail::hash256_block(h_, buffer_.begin()+i, buffer_.begin()+i+64);
buffer_.erase(buffer_.begin(), buffer_.begin()+i);
}
void finish()
{
uint8_t temp[64];
std::fill(temp, temp+64, 0);
std::size_t remains = buffer_.size();
std::copy(buffer_.begin(), buffer_.end(), temp);
temp[remains] = 0x80;
if (remains > 55)
{
std::fill(temp+remains+1, temp+64, 0);
detail::hash256_block(h_, temp, temp+64);
std::fill(temp, temp+64-4, 0);
}
else
std::fill(temp+remains+1, temp+64-4, 0);
write_data_bit_length(&(temp[56]));
detail::hash256_block(h_, temp, temp+64);
}
template<typename OutIter>
void get_hash_bytes(OutIter first, OutIter last) const
{
for (uint32_t const* iter = h_; iter != h_ + 8; ++iter)
for (std::size_t i = 0; i < 4 && first != last; ++i)
*(first++) = detail::mask_8bit(static_cast<uint8_t>(*iter >> (24 - 8 * i)));
}
private:
void add_to_data_length(uint32_t n)
{
uint32_t carry = 0;
data_length_digits_[0] += n;
for (std::size_t i = 0; i < 4; ++i)
{
data_length_digits_[i] += carry;
if (data_length_digits_[i] >= 65536u)
{
carry = data_length_digits_[i] >> 16;
data_length_digits_[i] &= 65535u;
}
else
break;
}
}
void write_data_bit_length(uint8_t* begin)
{
uint32_t data_bit_length_digits[4];
std::copy(
data_length_digits_, data_length_digits_ + 4,
data_bit_length_digits
);
// convert byte length to bit length (multiply 8 or shift 3 times left)
uint32_t carry = 0;
for (std::size_t i = 0; i < 4; ++i)
{
uint32_t before_val = data_bit_length_digits[i];
data_bit_length_digits[i] <<= 3;
data_bit_length_digits[i] |= carry;
data_bit_length_digits[i] &= 65535u;
carry = (before_val >> (16-3)) & 65535u;
}
// write data_bit_length
for (int i = 3; i >= 0; --i)
{
(*begin++) = static_cast<uint8_t>(data_bit_length_digits[i] >> 8);
(*begin++) = static_cast<uint8_t>(data_bit_length_digits[i]);
}
}
std::vector<uint8_t> buffer_;
uint32_t data_length_digits_[4]; //as 64bit integer (16bit x 4 integer)
uint32_t h_[8];
};
template<typename RaIter, typename OutIter>
void hash256(RaIter first, RaIter last, OutIter first2, OutIter last2)
{
hash256_one_by_one hasher;
//hasher.init();
hasher.process(first, last);
hasher.finish();
hasher.get_hash_bytes(first2, last2);
}
template <typename RaContainer>
std::vector<uint8_t> hash256(RaContainer const& _src)
{
std::vector<uint8_t> ret(32);
hash256(_src.begin(), _src.end(), ret.begin(), ret.end());
return ret;
}
}//namespace picosha2

View File

@ -25,6 +25,8 @@ public:
using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type;
using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type;
using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type;
using iterator = _T*;
using const_iterator = _T const*;
static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data.");

View File

@ -151,6 +151,7 @@ bigint CodeCopyMethod::gasNeeded() const
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
{
bytes data = toBigEndian(m_value);
assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
AssemblyItems actualCopyRoutine = copyRoutine();
actualCopyRoutine[4] = _assembly.newData(data);
return actualCopyRoutine;
@ -159,15 +160,25 @@ AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
AssemblyItems const& CodeCopyMethod::copyRoutine()
{
AssemblyItems static copyRoutine{
// constant to be reused 3+ times
u256(0),
// back up memory
// mload(0)
Instruction::DUP1,
Instruction::MLOAD, // back up memory
Instruction::MLOAD,
// codecopy(0, <offset>, 32)
u256(32),
AssemblyItem(PushData, u256(1) << 16), // has to be replaced
AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4]
Instruction::DUP4,
Instruction::CODECOPY,
// mload(0)
Instruction::DUP2,
Instruction::MLOAD,
// restore original memory
Instruction::SWAP2,
Instruction::MSTORE
};
@ -211,11 +222,16 @@ AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
if (lowerPart != 0)
newRoutine += findRepresentation(u256(abs(lowerPart)));
if (m_params.evmVersion.hasBitwiseShifting())
newRoutine += AssemblyItems{u256(1), u256(bits), Instruction::SHL};
{
newRoutine += findRepresentation(upperPart);
newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
}
else
{
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
}
if (lowerPart > 0)
newRoutine += AssemblyItems{Instruction::ADD};
else if (lowerPart < 0)

View File

@ -47,6 +47,13 @@ template <class S> S modWorkaround(S const& _a, S const& _b)
return (S)(bigint(_a) % bigint(_b));
}
// This works around a bug fixed with Boost 1.64.
// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64
inline u256 shlWorkaround(u256 const& _x, unsigned _amount)
{
return u256((bigint(_x) << _amount) & u256(-1));
}
// simplificationRuleList below was split up into parts to prevent
// stack overflows in the JavaScript optimizer for emscripten builds
// that affected certain browser versions.
@ -93,7 +100,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1(
{{Instruction::SHL, {A, B}}, [=]{
if (A.d() > 255)
return u256(0);
return bigintShiftLeftWorkaround(B.d(), unsigned(A.d()));
return shlWorkaround(B.d(), unsigned(A.d()));
}, false},
{{Instruction::SHR, {A, B}}, [=]{
if (A.d() > 255)
@ -365,6 +372,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
}
}
// Combine two SHL by constant
rules.push_back({
// SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X)
{Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}},
@ -378,6 +386,7 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
false
});
// Combine two SHR by constant
rules.push_back({
// SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X)
{Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}},
@ -391,6 +400,93 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
false
});
// Combine SHL-SHR by constant
rules.push_back({
// SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
{Instruction::SHR, {{B}, {Instruction::SHL, {{A}, {X}}}}},
[=]() -> Pattern {
u256 mask = shlWorkaround(u256(-1), unsigned(A.d())) >> unsigned(B.d());
if (A.d() > B.d())
return {Instruction::AND, {{Instruction::SHL, {A.d() - B.d(), X}}, mask}};
else if (B.d() > A.d())
return {Instruction::AND, {{Instruction::SHR, {B.d() - A.d(), X}}, mask}};
else
return {Instruction::AND, {X, mask}};
},
false,
[=] { return A.d() < 256 && B.d() < 256; }
});
// Combine SHR-SHL by constant
rules.push_back({
// SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask)
{Instruction::SHL, {{B}, {Instruction::SHR, {{A}, {X}}}}},
[=]() -> Pattern {
u256 mask = shlWorkaround(u256(-1) >> unsigned(A.d()), unsigned(B.d()));
if (A.d() > B.d())
return {Instruction::AND, {{Instruction::SHR, {A.d() - B.d(), X}}, mask}};
else if (B.d() > A.d())
return {Instruction::AND, {{Instruction::SHL, {B.d() - A.d(), X}}, mask}};
else
return {Instruction::AND, {X, mask}};
},
false,
[=] { return A.d() < 256 && B.d() < 256; }
});
// Move AND with constant across SHL and SHR by constant
for (auto shiftOp: {Instruction::SHL, Instruction::SHR})
{
auto replacement = [=]() -> Pattern {
u256 mask =
shiftOp == Instruction::SHL ?
shlWorkaround(A.d(), unsigned(B.d())) :
A.d() >> unsigned(B.d());
return {Instruction::AND, {{shiftOp, {B.d(), X}}, std::move(mask)}};
};
rules.push_back({
// SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
{shiftOp, {{B}, {Instruction::AND, {{X}, {A}}}}},
replacement,
false,
[=] { return B.d() < 256; }
});
rules.push_back({
// SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ])
{shiftOp, {{B}, {Instruction::AND, {{A}, {X}}}}},
replacement,
false,
[=] { return B.d() < 256; }
});
}
rules.push_back({
// MUL(X, SHL(Y, 1)) -> SHL(Y, X)
{Instruction::MUL, {X, {Instruction::SHL, {Y, u256(1)}}}},
[=]() -> Pattern {
return {Instruction::SHL, {Y, X}};
},
false
});
rules.push_back({
// MUL(SHL(X, 1), Y) -> SHL(X, Y)
{Instruction::MUL, {{Instruction::SHL, {X, u256(1)}}, Y}},
[=]() -> Pattern {
return {Instruction::SHL, {X, Y}};
},
false
});
rules.push_back({
// DIV(X, SHL(Y, 1)) -> SHR(Y, X)
{Instruction::DIV, {X, {Instruction::SHL, {Y, u256(1)}}}},
[=]() -> Pattern {
return {Instruction::SHR, {Y, X}};
},
false
});
std::function<bool()> feasibilityFunction = [=]() {
if (B.d() > 256)

View File

@ -136,7 +136,13 @@ bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item)
{
if (_item.type() != Operation)
return false;
switch (_item.instruction())
else
return terminatesControlFlow(_item.instruction());
}
bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::RETURN:
case Instruction::SELFDESTRUCT:
@ -202,6 +208,22 @@ bool SemanticInformation::movable(Instruction _instruction)
return true;
}
bool SemanticInformation::sideEffectFree(Instruction _instruction)
{
// These are not really functional.
assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, "");
return !instructionInfo(_instruction).sideEffects;
}
bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction)
{
if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD)
return true;
else
return sideEffectFree(_instruction);
}
bool SemanticInformation::invalidatesMemory(Instruction _instruction)
{
switch (_instruction)

View File

@ -48,6 +48,7 @@ struct SemanticInformation
static bool isJumpInstruction(AssemblyItem const& _item);
static bool altersControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(Instruction _instruction);
/// @returns false if the value put on the stack by _item depends on anything else than
/// the information in the current block header, memory, storage or stack.
static bool isDeterministic(AssemblyItem const& _item);
@ -55,6 +56,16 @@ struct SemanticInformation
/// without altering the semantics. This means it cannot depend on storage or memory,
/// cannot have any side-effects, but it can depend on a call-constant state of the blockchain.
static bool movable(Instruction _instruction);
/// @returns true if the instruction can be removed without changing the semantics.
/// This does not mean that it has to be deterministic or retrieve information from
/// somewhere else than purely the values of its arguments.
static bool sideEffectFree(Instruction _instruction);
/// @returns true if the instruction can be removed without changing the semantics.
/// This does not mean that it has to be deterministic or retrieve information from
/// somewhere else than purely the values of its arguments.
/// If true, the instruction is still allowed to influence the value returned by the
/// msize instruction.
static bool sideEffectFreeIfNoMSize(Instruction _instruction);
/// @returns true if the given instruction modifies memory.
static bool invalidatesMemory(Instruction _instruction);
/// @returns true if the given instruction modifies storage (even indirectly).

View File

@ -6,6 +6,7 @@ set(sources
ErrorReporter.cpp
ErrorReporter.h
EVMVersion.h
EVMVersion.cpp
Exceptions.cpp
Exceptions.h
ParserBase.cpp

View File

@ -73,6 +73,13 @@ char CharStream::rollback(size_t _amount)
return get();
}
char CharStream::setPosition(size_t _location)
{
solAssert(_location <= m_source.size(), "Attempting to set position past end of source.");
m_position = _location;
return get();
}
string CharStream::lineAtPosition(int _position) const
{
// if _position points to \n, it returns the line before the \n
@ -106,5 +113,3 @@ tuple<int, int> CharStream::translatePositionToLineColumn(int _position) const
}
return tuple<int, int>(lineNumber, searchPosition - lineStart);
}

View File

@ -76,7 +76,13 @@ public:
char get(size_t _charsForward = 0) const { return m_source[m_position + _charsForward]; }
char advanceAndGet(size_t _chars = 1);
/// Sets scanner position to @ _amount characters backwards in source text.
/// @returns The character of the current location after update is returned.
char rollback(size_t _amount);
/// Sets scanner position to @ _location if it refers a valid offset in m_source.
/// If not, nothing is done.
/// @returns The character of the current location after update is returned.
char setPosition(size_t _location);
void reset() { m_position = 0; }

View File

@ -0,0 +1,47 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* EVM versioning.
*/
#include <liblangutil/EVMVersion.h>
using namespace langutil;
using namespace dev::eth;
bool EVMVersion::hasOpcode(Instruction _opcode) const
{
switch (_opcode)
{
case Instruction::RETURNDATACOPY:
case Instruction::RETURNDATASIZE:
return supportsReturndata();
case Instruction::STATICCALL:
return hasStaticCall();
case Instruction::SHL:
case Instruction::SHR:
case Instruction::SAR:
return hasBitwiseShifting();
case Instruction::CREATE2:
return hasCreate2();
case Instruction::EXTCODEHASH:
return hasExtCodeHash();
default:
return true;
}
}

View File

@ -20,11 +20,14 @@
#pragma once
#include <libevmasm/Instruction.h>
#include <string>
#include <boost/optional.hpp>
#include <boost/operators.hpp>
namespace langutil
{
@ -78,6 +81,8 @@ public:
bool hasCreate2() const { return *this >= constantinople(); }
bool hasExtCodeHash() const { return *this >= constantinople(); }
bool hasOpcode(dev::eth::Instruction _opcode) const;
/// Whether we have to retain the costs for the call opcode itself (false),
/// or whether we can just forward easily all remaining gas (true).
bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); }
@ -90,5 +95,4 @@ private:
Version m_version = Version::Petersburg;
};
}

View File

@ -86,6 +86,11 @@ void ErrorReporter::error(Error::Type _type, SourceLocation const& _location, Se
m_errorList.push_back(err);
}
bool ErrorReporter::hasExcessiveErrors() const
{
return m_errorCount > c_maxErrorsAllowed;
}
bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
{
if (_type == Error::Type::Warning)

View File

@ -118,6 +118,9 @@ public:
return m_errorCount > 0;
}
// @returns true if the maximum error count has been reached.
bool hasExcessiveErrors() const;
private:
void error(
Error::Type _type,
@ -149,4 +152,3 @@ private:
};
}

View File

@ -47,7 +47,7 @@ Token ParserBase::peekNextToken() const
return m_scanner->peekNextToken();
}
std::string ParserBase::currentLiteral() const
string ParserBase::currentLiteral() const
{
return m_scanner->currentLiteral();
}
@ -57,34 +57,83 @@ Token ParserBase::advance()
return m_scanner->next();
}
string ParserBase::tokenName(Token _token)
{
if (_token == Token::Identifier)
return "identifier";
else if (_token == Token::EOS)
return "end of source";
else if (TokenTraits::isReservedKeyword(_token))
return "reserved keyword '" + TokenTraits::friendlyName(_token) + "'";
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return "'" + elemTypeName.toString() + "'";
}
else
return "'" + TokenTraits::friendlyName(_token) + "'";
}
void ParserBase::expectToken(Token _value, bool _advance)
{
Token tok = m_scanner->currentToken();
if (tok != _value)
{
auto tokenName = [this](Token _token)
{
if (_token == Token::Identifier)
return string("identifier");
else if (_token == Token::EOS)
return string("end of source");
else if (TokenTraits::isReservedKeyword(_token))
return string("reserved keyword '") + TokenTraits::friendlyName(_token) + "'";
else if (TokenTraits::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
{
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
return string("'") + elemTypeName.toString() + "'";
}
else
return string("'") + TokenTraits::friendlyName(_token) + "'";
};
fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
string const expectedToken = ParserBase::tokenName(_value);
if (m_parserErrorRecovery)
parserError("Expected " + expectedToken + " but got " + tokenName(tok));
else
fatalParserError("Expected " + expectedToken + " but got " + tokenName(tok));
// Do not advance so that recovery can sync or make use of the current token.
// This is especially useful if the expected token
// is the only one that is missing and is at the end of a construct.
// "{ ... ; }" is such an example.
// ^
_advance = false;
}
if (_advance)
m_scanner->next();
}
void ParserBase::expectTokenOrConsumeUntil(Token _value, string const& _currentNodeName, bool _advance)
{
Token tok = m_scanner->currentToken();
if (tok != _value)
{
int startPosition = position();
SourceLocation errorLoc = SourceLocation{startPosition, endPosition(), source()};
while (m_scanner->currentToken() != _value && m_scanner->currentToken() != Token::EOS)
m_scanner->next();
string const expectedToken = ParserBase::tokenName(_value);
string const msg = "In " + _currentNodeName + ", " + expectedToken + "is expected; got " + ParserBase::tokenName(tok) + "instead.";
if (m_scanner->currentToken() == Token::EOS)
{
// rollback to where the token started, and raise exception to be caught at a higher level.
m_scanner->setPosition(startPosition);
m_inParserRecovery = true;
fatalParserError(errorLoc, msg);
}
else
{
if (m_inParserRecovery)
parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + ".");
else
parserError(errorLoc, msg + "Recovered at next " + expectedToken);
m_inParserRecovery = false;
}
}
else if (m_inParserRecovery)
{
string expectedToken = ParserBase::tokenName(_value);
parserWarning("Recovered in " + _currentNodeName + " at " + expectedToken + ".");
m_inParserRecovery = false;
}
if (_advance)
m_scanner->next();
}
void ParserBase::increaseRecursionDepth()
{
m_recursionDepth++;
@ -98,12 +147,27 @@ void ParserBase::decreaseRecursionDepth()
m_recursionDepth--;
}
void ParserBase::parserWarning(string const& _description)
{
m_errorReporter.warning(SourceLocation{position(), endPosition(), source()}, _description);
}
void ParserBase::parserError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.parserError(_location, _description);
}
void ParserBase::parserError(string const& _description)
{
m_errorReporter.parserError(SourceLocation{position(), endPosition(), source()}, _description);
parserError(SourceLocation{position(), endPosition(), source()}, _description);
}
void ParserBase::fatalParserError(string const& _description)
{
m_errorReporter.fatalParserError(SourceLocation{position(), endPosition(), source()}, _description);
fatalParserError(SourceLocation{position(), endPosition(), source()}, _description);
}
void ParserBase::fatalParserError(SourceLocation const& _location, string const& _description)
{
m_errorReporter.fatalParserError(_location, _description);
}

View File

@ -36,7 +36,14 @@ class Scanner;
class ParserBase
{
public:
explicit ParserBase(ErrorReporter& errorReporter): m_errorReporter(errorReporter) {}
/// Set @a _parserErrorRecovery to true for additional error
/// recovery. This is experimental and intended for use
/// by front-end tools that need partial AST information even
/// when errors occur.
explicit ParserBase(ErrorReporter& errorReporter, bool _parserErrorRecovery = false): m_errorReporter(errorReporter)
{
m_parserErrorRecovery = _parserErrorRecovery;
}
std::shared_ptr<CharStream> source() const { return m_scanner->charStream(); }
@ -62,10 +69,17 @@ protected:
///@{
///@name Helper functions
/// If current token value is not _value, throw exception otherwise advance token.
/// If current token value is not @a _value, throw exception otherwise advance token
// @a if _advance is true and error recovery is in effect.
void expectToken(Token _value, bool _advance = true);
/// Like expectToken but if there is an error ignores tokens until
/// the expected token or EOS is seen. If EOS is encountered, back up to the error point,
/// and throw an exception so that a higher grammar rule has an opportunity to recover.
void expectTokenOrConsumeUntil(Token _value, std::string const& _currentNodeName, bool _advance = true);
Token currentToken() const;
Token peekNextToken() const;
std::string tokenName(Token _token);
std::string currentLiteral() const;
Token advance();
///@}
@ -77,16 +91,26 @@ protected:
/// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description.
void parserError(std::string const& _description);
void parserError(SourceLocation const& _location, std::string const& _description);
/// Creates a @ref ParserWarning and annotates it with the current position and the
/// given @a _description.
void parserWarning(std::string const& _description);
/// Creates a @ref ParserError and annotates it with the current position and the
/// given @a _description. Throws the FatalError.
void fatalParserError(std::string const& _description);
void fatalParserError(SourceLocation const& _location, std::string const& _description);
std::shared_ptr<Scanner> m_scanner;
/// The reference to the list of errors and warning to add errors/warnings during parsing
ErrorReporter& m_errorReporter;
/// Current recursion depth during parsing.
size_t m_recursionDepth = 0;
/// True if we are in parser error recovery. Usually this means we are scanning for
/// a synchronization token like ';', or '}'. We use this to reduce cascaded error messages.
bool m_inParserRecovery = false;
bool m_parserErrorRecovery = false;
};
}

View File

@ -156,6 +156,13 @@ void Scanner::reset()
next();
}
void Scanner::setPosition(size_t _offset)
{
m_char = m_source->setPosition(_offset);
scanToken();
next();
}
void Scanner::supportPeriodInIdentifier(bool _value)
{
m_supportPeriodInIdentifier = _value;

View File

@ -110,6 +110,9 @@ public:
/// @returns the next token and advances input
Token next();
/// Set scanner to a specific offset. This is used in error recovery.
void setPosition(size_t _offset);
///@{
///@name Information about the current token

View File

@ -21,10 +21,11 @@
*/
#include <libsolc/libsolc.h>
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/Version.h>
#include <libyul/YulString.h>
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <string>
@ -100,6 +101,9 @@ extern char const* solidity_compile(char const* _input, CStyleReadFileCallback _
}
extern void solidity_free() noexcept
{
// This is called right before each compilation, but not at the end, so additional memory
// can be freed here.
yul::YulStringRepository::reset();
s_outputBuffer.clear();
}
}

View File

@ -73,6 +73,8 @@ set(sources
codegen/ir/IRGeneratorForStatements.h
codegen/ir/IRGenerationContext.cpp
codegen/ir/IRGenerationContext.h
codegen/ir/IRLValue.cpp
codegen/ir/IRLValue.h
formal/EncodingContext.cpp
formal/EncodingContext.h
formal/SMTChecker.cpp

View File

@ -37,16 +37,17 @@ namespace solidity
{
NameAndTypeResolver::NameAndTypeResolver(
vector<Declaration const*> const& _globals,
GlobalContext& _globalContext,
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ErrorReporter& _errorReporter
) :
m_scopes(_scopes),
m_errorReporter(_errorReporter)
m_errorReporter(_errorReporter),
m_globalContext(_globalContext)
{
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals)
for (Declaration const* declaration: _globalContext.declarations())
{
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
}
@ -57,7 +58,7 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode
// The helper registers all declarations in m_scopes as a side-effect of its construction.
try
{
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, _currentScope);
DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errorReporter, m_globalContext, _currentScope);
}
catch (langutil::FatalError const&)
{
@ -272,6 +273,10 @@ bool NameAndTypeResolver::resolveNamesAndTypesInternal(ASTNode& _node, bool _res
setScope(contract->scope());
solAssert(!!m_currentScope, "");
m_globalContext.setCurrentContract(*contract);
updateDeclaration(*m_globalContext.currentSuper());
updateDeclaration(*m_globalContext.currentThis());
for (ASTPointer<InheritanceSpecifier> const& baseContract: contract->baseContracts())
if (!resolveNamesAndTypes(*baseContract, true))
success = false;
@ -452,11 +457,13 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper(
map<ASTNode const*, shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
ErrorReporter& _errorReporter,
GlobalContext& _globalContext,
ASTNode const* _currentScope
):
m_scopes(_scopes),
m_currentScope(_currentScope),
m_errorReporter(_errorReporter)
m_errorReporter(_errorReporter),
m_globalContext(_globalContext)
{
_astRoot.accept(*this);
solAssert(m_currentScope == _currentScope, "Scopes not correctly closed.");
@ -560,6 +567,10 @@ bool DeclarationRegistrationHelper::visit(ImportDirective& _import)
bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract)
{
m_globalContext.setCurrentContract(_contract);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentThis(), nullptr, false, true);
m_scopes[nullptr]->registerDeclaration(*m_globalContext.currentSuper(), nullptr, false, true);
registerDeclaration(_contract, true);
_contract.annotation().canonicalName = currentCanonicalName();
return true;

View File

@ -23,6 +23,7 @@
#pragma once
#include <libsolidity/analysis/DeclarationContainer.h>
#include <libsolidity/analysis/GlobalContext.h>
#include <libsolidity/analysis/ReferencesResolver.h>
#include <libsolidity/ast/ASTAnnotations.h>
#include <libsolidity/ast/ASTVisitor.h>
@ -53,7 +54,7 @@ public:
/// @param _scopes mapping of scopes to be used (usually default constructed), these
/// are filled during the lifetime of this object.
NameAndTypeResolver(
std::vector<Declaration const*> const& _globals,
GlobalContext& _globalContext,
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
langutil::ErrorReporter& _errorReporter
);
@ -131,6 +132,7 @@ private:
DeclarationContainer* m_currentScope = nullptr;
langutil::ErrorReporter& m_errorReporter;
GlobalContext& m_globalContext;
};
/**
@ -148,6 +150,7 @@ public:
std::map<ASTNode const*, std::shared_ptr<DeclarationContainer>>& _scopes,
ASTNode& _astRoot,
langutil::ErrorReporter& _errorReporter,
GlobalContext& _globalContext,
ASTNode const* _currentScope = nullptr
);
@ -200,6 +203,7 @@ private:
ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr;
langutil::ErrorReporter& m_errorReporter;
GlobalContext& m_globalContext;
};
}

View File

@ -309,6 +309,19 @@ bool StaticAnalyzer::visit(FunctionCall const& _functionCall)
"Arithmetic modulo zero."
);
}
if (
m_currentContract->isLibrary() &&
functionType->kind() == FunctionType::Kind::DelegateCall &&
functionType->declaration().scope() == m_currentContract
)
m_errorReporter.typeError(
_functionCall.location(),
SecondarySourceLocation().append(
"The function declaration is here:",
functionType->declaration().scope()->location()
),
"Libraries cannot call their own functions externally."
);
}
return true;
}

View File

@ -21,6 +21,9 @@
#include <libsolidity/ast/ExperimentalFeatures.h>
#include <libsolidity/interface/Version.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/AsmData.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SemVerHandler.h>
@ -254,6 +257,23 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation)
return true;
}
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
{
if (!m_useYulOptimizer)
return false;
if (yul::SideEffectsCollector(
_inlineAssembly.dialect(),
_inlineAssembly.operations()
).containsMSize())
m_errorReporter.syntaxError(
_inlineAssembly.location(),
"The msize instruction cannot be used when the Yul optimizer is activated because "
"it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
);
return false;
}
bool SyntaxChecker::visit(PlaceholderStatement const&)
{
m_placeholderFound = true;

View File

@ -39,12 +39,16 @@ namespace solidity
* - whether a modifier contains at least one '_'
* - issues deprecation warnings for unary '+'
* - issues deprecation warning for throw
* - whether the msize instruction is used and the Yul optimizer is enabled at the same time.
*/
class SyntaxChecker: private ASTConstVisitor
{
public:
/// @param _errorReporter provides the error logging functionality.
SyntaxChecker(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
SyntaxChecker(langutil::ErrorReporter& _errorReporter, bool _useYulOptimizer):
m_errorReporter(_errorReporter),
m_useYulOptimizer(_useYulOptimizer)
{}
bool checkSyntax(ASTNode const& _astRoot);
@ -75,6 +79,8 @@ private:
bool visit(UnaryOperation const& _operation) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
bool visit(PlaceholderStatement const& _placeholderStatement) override;
bool visit(ContractDefinition const& _contract) override;
@ -88,6 +94,8 @@ private:
langutil::ErrorReporter& m_errorReporter;
bool m_useYulOptimizer = false;
/// Flag that indicates whether a function modifier actually contains '_'.
bool m_placeholderFound = false;

View File

@ -27,7 +27,6 @@
#include <libyul/AsmAnalysis.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <liblangutil/ErrorReporter.h>
@ -707,7 +706,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly)
*_inlineAssembly.annotation().analysisInfo,
m_errorReporter,
Error::Type::SyntaxError,
yul::EVMDialect::looseAssemblyForEVM(m_evmVersion),
_inlineAssembly.dialect(),
identifierAccess
);
if (!analyzer.analyze(_inlineAssembly.operations()))

View File

@ -18,6 +18,7 @@
#include <libsolidity/analysis/ViewPureChecker.h>
#include <libsolidity/ast/ExperimentalFeatures.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <liblangutil/ErrorReporter.h>
#include <libevmasm/SemanticInformation.h>
#include <functional>
@ -33,7 +34,11 @@ namespace
class AssemblyViewPureChecker: public boost::static_visitor<void>
{
public:
explicit AssemblyViewPureChecker(std::function<void(StateMutability, SourceLocation const&)> _reportMutability):
explicit AssemblyViewPureChecker(
yul::Dialect const& _dialect,
std::function<void(StateMutability, SourceLocation const&)> _reportMutability
):
m_dialect(_dialect),
m_reportMutability(_reportMutability) {}
void operator()(yul::Label const&) { }
@ -69,6 +74,11 @@ public:
}
void operator()(yul::FunctionCall const& _funCall)
{
if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name))
if (fun->instruction)
checkInstruction(_funCall.location, *fun->instruction);
for (auto const& arg: _funCall.arguments)
boost::apply_visitor(*this, arg);
}
@ -107,16 +117,16 @@ public:
}
private:
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
void checkInstruction(SourceLocation _location, dev::eth::Instruction _instruction)
{
if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
m_reportMutability(StateMutability::NonPayable, _location);
else if (_instruction == dev::eth::Instruction::CALLVALUE)
m_reportMutability(StateMutability::Payable, _location);
else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
m_reportMutability(StateMutability::View, _location);
}
yul::Dialect const& m_dialect;
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
};
}
@ -223,6 +233,7 @@ void ViewPureChecker::endVisit(Identifier const& _identifier)
void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
{
AssemblyViewPureChecker{
_inlineAssembly.dialect(),
[=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
}(_inlineAssembly.operations());
}

View File

@ -400,6 +400,21 @@ SourceUnit const& Scopable::sourceUnit() const
return dynamic_cast<SourceUnit const&>(*s);
}
CallableDeclaration const* Scopable::functionOrModifierDefinition() const
{
ASTNode const* s = scope();
solAssert(s, "");
while (dynamic_cast<Scopable const*>(s))
{
if (auto funDef = dynamic_cast<FunctionDefinition const*>(s))
return funDef;
if (auto modDef = dynamic_cast<ModifierDefinition const*>(s))
return modDef;
s = dynamic_cast<Scopable const*>(s)->scope();
}
return nullptr;
}
string Scopable::sourceUnitName() const
{
return sourceUnit().annotation().path;

View File

@ -43,6 +43,7 @@ namespace yul
{
// Forward-declaration to <yul/AsmData.h>
struct Block;
struct Dialect;
}
namespace dev
@ -161,6 +162,9 @@ public:
/// @returns the source unit this scopable is present in.
SourceUnit const& sourceUnit() const;
/// @returns the function or modifier definition this scopable is present in or nullptr.
CallableDeclaration const* functionOrModifierDefinition() const;
/// @returns the source name this scopable is present in.
/// Can be combined with annotation().canonicalName (if present) to form a globally unique name.
std::string sourceUnitName() const;
@ -1046,17 +1050,20 @@ public:
InlineAssembly(
SourceLocation const& _location,
ASTPointer<ASTString> const& _docString,
yul::Dialect const& _dialect,
std::shared_ptr<yul::Block> const& _operations
):
Statement(_location, _docString), m_operations(_operations) {}
Statement(_location, _docString), m_dialect(_dialect), m_operations(_operations) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
yul::Dialect const& dialect() const { return m_dialect; }
yul::Block const& operations() const { return *m_operations; }
InlineAssemblyAnnotation& annotation() const override;
private:
yul::Dialect const& m_dialect;
std::shared_ptr<yul::Block> m_operations;
};

View File

@ -35,8 +35,9 @@
namespace yul
{
struct AsmAnalysisInfo;
struct Identifier;
struct AsmAnalysisInfo;
struct Identifier;
struct Dialect;
}
namespace dev

View File

@ -38,6 +38,7 @@ class SourceUnit;
class PragmaDirective;
class ImportDirective;
class Declaration;
class CallableDeclaration;
class ContractDefinition;
class InheritanceSpecifier;
class UsingForDirective;

View File

@ -2068,7 +2068,8 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
{
TypePointer type = variable->annotation().type;
solAssert(type, "");
// Skip all mapping members if we are not in storage.
// If we are not in storage, skip all members that cannot live outside of storage,
// ex. mappings and array of mappings
if (location() != DataLocation::Storage && !type->canLiveOutsideStorage())
continue;
members.emplace_back(

View File

@ -255,39 +255,6 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
return suffix;
}
string ABIFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
<body>
}
)");
templ("functionName", functionName);
unsigned storageBytes = _type.storageBytes();
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
if (type->isSigned() && storageBytes != 32)
{
templ("body", "cleaned := signextend(" + to_string(storageBytes - 1) + ", value)");
return templ.render();
}
if (storageBytes == 32)
templ("body", "cleaned := value");
else if (_type.leftAligned())
templ("body", "cleaned := " + m_utils.shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
else
templ("body", "cleaned := and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
return templ.render();
});
}
string ABIFunctions::abiEncodingFunction(
Type const& _from,
Type const& _to,
@ -609,7 +576,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
break;
case DataLocation::Storage:
if (_from.baseType()->isValueType())
templ("arrayElementAccess", readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)");
else
templ("arrayElementAccess", "srcPtr");
break;
@ -806,7 +773,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
items[i]["inRange"] = "1";
else
items[i]["inRange"] = "0";
items[i]["extractFromSlot"] = extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false);
}
templ("items", items);
return templ.render();
@ -893,7 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
previousSlotOffset = storageSlotOffset;
}
members.back()["retrieveValue"] = extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)";
}
else
{
@ -1400,51 +1367,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
});
}
string ABIFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
"offset_" +
to_string(_offset) +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot) -> value {
value := <extract>(sload(slot))
}
)")
("functionName", functionName)
("extract", extractFromStorageValue(_type, _offset, false))
.render();
});
}
string ABIFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_" +
string(_splitFunctionTypes ? "split_" : "") +
"offset_" +
to_string(_offset) +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value) -> value {
value := <cleanupStorage>(<shr>(slot_value))
}
)")
("functionName", functionName)
("shr", m_utils.shiftRightFunction(_offset * 8))
("cleanupStorage", cleanupFromStorageFunction(_type, false))
.render();
});
}
string ABIFunctions::calldataAccessFunction(Type const& _type)
{
solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), "");

View File

@ -128,15 +128,6 @@ private:
std::string toFunctionNameSuffix() const;
};
/// Performs cleanup after reading from a potentially compressed storage slot.
/// The function does not perform any validation, it just masks or sign-extends
/// higher order bytes or left-aligns (in case of bytesNN).
/// The storage cleanup expects the value to be right-aligned with potentially
/// dirty higher order bytes.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
/// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage
@ -231,19 +222,6 @@ private:
/// Part of @a abiDecodingFunction for array types.
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
/// @returns a function that reads a value type from storage.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// @returns a function that extracts a value type from storage slot that has been
/// retrieved already.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// @returns the name of a function that retrieves an element from calldata.
std::string calldataAccessFunction(Type const& _type);

View File

@ -33,6 +33,7 @@
#include <libyul/backends/evm/AsmCodeGen.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/Suite.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/YulString.h>
#include <liblangutil/ErrorReporter.h>
@ -382,7 +383,8 @@ void CompilerContext::appendInlineAssembly(
ErrorList errors;
ErrorReporter errorReporter(errors);
auto scanner = make_shared<langutil::Scanner>(langutil::CharStream(_assembly, "--CODEGEN--"));
auto parserResult = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM(m_evmVersion)).parse(scanner, false);
yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion);
auto parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false);
#ifdef SOL_OUTPUT_ASM
cout << yul::AsmPrinter()(*parserResult) << endl;
#endif
@ -409,7 +411,7 @@ void CompilerContext::appendInlineAssembly(
analysisInfo,
errorReporter,
boost::none,
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
dialect,
identifierAccess.resolve
).analyze(*parserResult);
if (!parserResult || !errorReporter.errors().empty() || !analyzerResult)
@ -419,8 +421,10 @@ void CompilerContext::appendInlineAssembly(
// so we essentially only optimize the ABI functions.
if (_optimiserSettings.runYulOptimiser && _localVariables.empty())
{
bool const isCreation = m_runtimeContext != nullptr;
yul::OptimiserSuite::run(
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
dialect,
yul::GasMeter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment),
*parserResult,
analysisInfo,
_optimiserSettings.optimizeStackAllocation,
@ -431,7 +435,7 @@ void CompilerContext::appendInlineAssembly(
analysisInfo,
errorReporter,
boost::none,
yul::EVMDialect::strictAssemblyForEVM(m_evmVersion),
dialect,
identifierAccess.resolve
).analyze(*parserResult))
reportError("Optimizer introduced error into inline assembly.");

View File

@ -228,34 +228,21 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
if (!_constructor.parameters().empty())
{
unsigned argumentSize = 0;
for (ASTPointer<VariableDeclaration> const& var: _constructor.parameters())
if (var->annotation().type->isDynamicallySized())
{
argumentSize = 0;
break;
}
else
argumentSize += var->annotation().type->calldataEncodedSize();
CompilerUtils(m_context).fetchFreeMemoryPointer();
if (argumentSize == 0)
{
// argument size is dynamic, use CODESIZE to determine it
m_context.appendProgramSize(); // program itself
// CODESIZE is program plus manually added arguments
m_context << Instruction::CODESIZE << Instruction::SUB;
}
else
m_context << u256(argumentSize);
// CODESIZE returns the actual size of the code,
// which is the size of the generated code (``programSize``)
// plus the constructor arguments added to the transaction payload.
m_context.appendProgramSize();
m_context << Instruction::CODESIZE << Instruction::SUB;
// stack: <memptr> <argument size>
m_context << Instruction::DUP1;
m_context.appendProgramSize();
m_context << Instruction::DUP4 << Instruction::CODECOPY;
m_context << Instruction::DUP2 << Instruction::ADD;
m_context << Instruction::DUP1;
// stack: <memptr> <argument size>
m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD;
// stack: <memptr> <argument size> <mem end>
CompilerUtils(m_context).storeFreeMemoryPointer();
// stack: <memptr>
// stack: <memptr> <argument size>
CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true);
}
_constructor.accept(*this);

View File

@ -206,6 +206,12 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
CompilerUtils(m_context).splitExternalFunctionType(false);
cleaned = true;
}
else if (fun->kind() == FunctionType::Kind::Internal)
{
m_context << Instruction::DUP1 << Instruction::ISZERO;
CompilerUtils(m_context).pushZeroValue(*fun);
m_context << Instruction::MUL << Instruction::OR;
}
}
if (!cleaned)
{
@ -288,7 +294,8 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
{
solAssert(
_sourceType.category() == m_dataType->category(),
"Wrong type conversation for assignment.");
"Wrong type conversation for assignment."
);
if (m_dataType->category() == Type::Category::Array)
{
m_context << Instruction::POP; // remove byte offset
@ -313,9 +320,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported.");
for (auto const& member: structType.members(nullptr))
{
// assign each member that is not a mapping
// assign each member that can live outside of storage
TypePointer const& memberType = member.type;
if (memberType->category() == Type::Category::Mapping)
if (!memberType->canLiveOutsideStorage())
continue;
TypePointer sourceMemberType = sourceType.memberType(member.name);
if (sourceType.location() == DataLocation::Storage)

View File

@ -104,6 +104,61 @@ string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
});
}
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
{
string functionName =
string(_assert ? "assert_helper" : "require_helper") +
(_messageType ? ("_" + _messageType->identifier()) : "");
solAssert(!_assert || !_messageType, "Asserts can't have messages!");
return m_functionCollector->createFunction(functionName, [&]() {
if (!_messageType)
return Whiskers(R"(
function <functionName>(condition) {
if iszero(condition) { <invalidOrRevert> }
}
)")
("invalidOrRevert", _assert ? "invalid()" : "revert(0, 0)")
("functionName", functionName)
.render();
int const hashHeaderSize = 4;
int const byteSize = 8;
u256 const errorHash =
u256(FixedHash<hashHeaderSize>::Arith(
FixedHash<hashHeaderSize>(dev::keccak256("Error(string)"))
)) << (256 - hashHeaderSize * byteSize);
string const encodeFunc = ABIFunctions(m_evmVersion, m_functionCollector)
.tupleEncoder(
{_messageType},
{TypeProvider::stringMemory()}
);
return Whiskers(R"(
function <functionName>(condition <messageVars>) {
if iszero(condition) {
let fmp := mload(<freeMemPointer>)
mstore(fmp, <errorHash>)
let end := <abiEncodeFunc>(add(fmp, <hashHeaderSize>) <messageVars>)
revert(fmp, sub(end, fmp))
}
}
)")
("functionName", functionName)
("freeMemPointer", to_string(CompilerUtils::freeMemoryPointer))
("errorHash", formatNumber(errorHash))
("abiEncodeFunc", encodeFunc)
("hashHeaderSize", to_string(hashHeaderSize))
("messageVars",
(_messageType->sizeOnStack() > 0 ? ", " : "") +
suffixedVariableNameList("message_", 1, 1 + _messageType->sizeOnStack())
)
.render();
});
}
string YulUtilFunctions::leftAlignFunction(Type const& _type)
{
string functionName = string("leftAlign_") + _type.identifier();
@ -206,35 +261,49 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
if (m_evmVersion.hasBitwiseShifting())
{
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := shr(<numBits>, value)
}
)")
("functionName", functionName)
("numBits", to_string(_numBits))
.render();
});
}
else
{
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue := div(value, <multiplier>)
}
)")
("functionName", functionName)
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name();
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value) -> newValue {
newValue :=
<?hasShifts>
shr(<numBits>, value)
<!hasShifts>
div(value, <multiplier>)
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
("numBits", to_string(_numBits))
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
.render();
});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{
solAssert(_numBytes <= 32, "");
solAssert(_shiftBytes <= 32, "");
size_t numBits = _numBytes * 8;
size_t shiftBits = _shiftBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, toInsert) -> result {
let mask := <mask>
toInsert := <shl>(toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits))
("shl", shiftLeftFunction(shiftBits))
.render();
});
}
string YulUtilFunctions::roundUpFunction()
@ -257,74 +326,135 @@ string YulUtilFunctions::overflowCheckedUIntAddFunction(size_t _bits)
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
string functionName = "checked_add_uint_" + to_string(_bits);
return m_functionCollector->createFunction(functionName, [&]() {
if (_bits < 256)
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
<?shortType>
let mask := <mask>
sum := add(and(x, mask), and(y, mask))
if and(sum, not(mask)) { revert(0, 0) }
}
)")
("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
else
return
Whiskers(R"(
function <functionName>(x, y) -> sum {
<!shortType>
sum := add(x, y)
if lt(sum, x) { revert(0, 0) }
}
)")
</shortType>
}
)")
("shortType", _bits < 256)
("functionName", functionName)
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
});
}
string YulUtilFunctions::overflowCheckedUIntMulFunction(size_t _bits)
{
solAssert(0 < _bits && _bits <= 256 && _bits % 8 == 0, "");
string functionName = "checked_mul_uint_" + to_string(_bits);
return m_functionCollector->createFunction(functionName, [&]() {
return
// - The current overflow check *before* the multiplication could
// be replaced by the following check *after* the multiplication:
// if and(iszero(iszero(x)), iszero(eq(div(product, x), y))) { revert(0, 0) }
// - The case the x equals 0 could be treated separately and directly return zero.
Whiskers(R"(
function <functionName>(x, y) -> product {
if and(iszero(iszero(x)), lt(div(<mask>, x), y)) { revert(0, 0) }
<?shortType>
product := mulmod(x, y, <powerOfTwo>)
<!shortType>
product := mul(x, y)
</shortType>
}
)")
("shortType", _bits < 256)
("functionName", functionName)
("powerOfTwo", toCompactHexWithPrefix(u256(1) << _bits))
("mask", toCompactHexWithPrefix((u256(1) << _bits) - 1))
.render();
});
}
string YulUtilFunctions::overflowCheckedIntDivFunction(IntegerType const& _type)
{
unsigned bits = _type.numBits();
solAssert(0 < bits && bits <= 256 && bits % 8 == 0, "");
string functionName = "checked_div_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(x, y) -> r {
if iszero(y) { revert(0, 0) }
<?signed>
// x / -1 == x
if and(
eq(x, <minVal>),
eq(y, sub(0, 1))
) { revert(0, 0) }
</signed>
r := <?signed>s</signed>div(x, y)
}
)")
("functionName", functionName)
("signed", _type.isSigned())
("minVal", (0 - (u256(1) << (bits - 1))).str())
.render();
});
}
string YulUtilFunctions::overflowCheckedUIntSubFunction()
{
string functionName = "checked_sub_uint";
return m_functionCollector->createFunction(functionName, [&] {
return
Whiskers(R"(
function <functionName>(x, y) -> diff {
if lt(x, y) { revert(0, 0) }
diff := sub(x, y)
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
{
string functionName = "array_length_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
Whiskers w(R"(
function <functionName>(value) -> length {
<body>
<?dynamic>
<?memory>
length := mload(value)
</memory>
<?storage>
length := sload(value)
<?byteArray>
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
length := div(and(length, mask), 2)
</byteArray>
</storage>
<!dynamic>
length := <length>
</dynamic>
}
)");
w("functionName", functionName);
string body;
w("dynamic", _type.isDynamicallySized());
if (!_type.isDynamicallySized())
body = "length := " + toCompactHexWithPrefix(_type.length());
else
{
switch (_type.location())
{
case DataLocation::CallData:
solAssert(false, "called regular array length function on calldata array");
break;
case DataLocation::Memory:
body = "length := mload(value)";
break;
case DataLocation::Storage:
if (_type.isByteArray())
{
// Retrieve length both for in-place strings and off-place strings:
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
body = R"(
length := sload(value)
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
length := div(and(length, mask), 2)
)";
}
else
body = "length := sload(value)";
break;
}
}
solAssert(!body.empty(), "");
w("body", body);
w("length", toCompactHexWithPrefix(_type.length()));
w("memory", _type.location() == DataLocation::Memory);
w("storage", _type.location() == DataLocation::Storage);
w("byteArray", _type.isByteArray());
if (_type.isDynamicallySized())
solAssert(
_type.location() != DataLocation::CallData,
"called regular array length function on calldata array"
);
return w.render();
});
}
@ -338,20 +468,21 @@ string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
function <functionName>(length) -> size {
// Make sure we can allocate memory without overflow
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
size := <allocationSize>
<addLengthSlot>
<?byteArray>
// round up
size := and(add(length, 0x1f), not(0x1f))
<!byteArray>
size := mul(length, 0x20)
</byteArray>
<?dynamic>
// add length slot
size := add(size, 0x20)
</dynamic>
}
)");
w("functionName", functionName);
if (_type.isByteArray())
// Round up
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
else
w("allocationSize", "mul(length, 0x20)");
if (_type.isDynamicallySized())
w("addLengthSlot", "size := add(size, 0x20)");
else
w("addLengthSlot", "");
w("byteArray", _type.isByteArray());
w("dynamic", _type.isDynamicallySized());
return w.render();
});
}
@ -360,64 +491,30 @@ string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
switch (_type.location())
{
case DataLocation::Memory:
if (_type.isDynamicallySized())
return Whiskers(R"(
function <functionName>(memPtr) -> dataPtr {
dataPtr := add(memPtr, 0x20)
}
)")
("functionName", functionName)
.render();
else
return Whiskers(R"(
function <functionName>(memPtr) -> dataPtr {
dataPtr := memPtr
}
)")
("functionName", functionName)
.render();
case DataLocation::Storage:
if (_type.isDynamicallySized())
{
Whiskers w(R"(
function <functionName>(slot) -> dataSlot {
mstore(0, slot)
dataSlot := keccak256(0, 0x20)
}
)");
w("functionName", functionName);
return w.render();
}
else
{
Whiskers w(R"(
function <functionName>(slot) -> dataSlot {
dataSlot := slot
}
)");
w("functionName", functionName);
return w.render();
}
case DataLocation::CallData:
{
// Calldata arrays are stored as offset of the data area and length
// on the stack, so the offset already points to the data area.
// This might change, if calldata arrays are stored in a single
// stack slot at some point.
Whiskers w(R"(
function <functionName>(slot) -> dataSlot {
dataSlot := slot
}
)");
w("functionName", functionName);
return w.render();
// No special processing for calldata arrays, because they are stored as
// offset of the data area and length on the stack, so the offset already
// points to the data area.
// This might change, if calldata arrays are stored in a single
// stack slot at some point.
return Whiskers(R"(
function <functionName>(ptr) -> data {
data := ptr
<?dynamic>
<?memory>
data := add(ptr, 0x20)
</memory>
<?storage>
mstore(0, ptr)
data := keccak256(0, 0x20)
</storage>
</dynamic>
}
default:
solAssert(false, "");
}
)")
("functionName", functionName)
("dynamic", _type.isDynamicallySized())
("memory", _type.location() == DataLocation::Memory)
("storage", _type.location() == DataLocation::Storage)
.render();
});
}
@ -428,39 +525,170 @@ string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
solAssert(_type.baseType()->storageBytes() > 16, "");
string functionName = "array_nextElement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
switch (_type.location())
Whiskers templ(R"(
function <functionName>(ptr) -> next {
next := add(ptr, <advance>)
}
)");
templ("functionName", functionName);
if (_type.location() == DataLocation::Memory)
templ("advance", "0x20");
else if (_type.location() == DataLocation::Storage)
templ("advance", "1");
else if (_type.location() == DataLocation::CallData)
templ("advance", toCompactHexWithPrefix(
_type.baseType()->isDynamicallyEncoded() ?
32 :
_type.baseType()->calldataEncodedSize()
));
else
solAssert(false, "");
return templ.render();
});
}
string YulUtilFunctions::mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType)
{
solAssert(_keyType.sizeOnStack() <= 1, "");
string functionName = "mapping_index_access_" + _mappingType.identifier() + "_of_" + _keyType.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
if (_mappingType.keyType()->isDynamicallySized())
return Whiskers(R"(
function <functionName>(slot <comma> <key>) -> dataSlot {
dataSlot := <hash>(slot <comma> <key>)
}
)")
("functionName", functionName)
("key", _keyType.sizeOnStack() > 0 ? "key" : "")
("comma", _keyType.sizeOnStack() > 0 ? "," : "")
("hash", packedHashFunction(
{&_keyType, TypeProvider::uint256()},
{_mappingType.keyType(), TypeProvider::uint256()}
))
.render();
else
{
case DataLocation::Memory:
return Whiskers(R"(
function <functionName>(memPtr) -> nextPtr {
nextPtr := add(memPtr, 0x20)
}
)")
("functionName", functionName)
.render();
case DataLocation::Storage:
return Whiskers(R"(
function <functionName>(slot) -> nextSlot {
nextSlot := add(slot, 1)
}
)")
("functionName", functionName)
.render();
case DataLocation::CallData:
return Whiskers(R"(
function <functionName>(calldataPtr) -> nextPtr {
nextPtr := add(calldataPtr, <stride>)
}
)")
("stride", toCompactHexWithPrefix(_type.baseType()->isDynamicallyEncoded() ? 32 : _type.baseType()->calldataEncodedSize()))
("functionName", functionName)
.render();
default:
solAssert(false, "");
solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
solAssert(!_mappingType.keyType()->isDynamicallyEncoded(), "");
solAssert(_mappingType.keyType()->calldataEncodedSize(false) <= 0x20, "");
Whiskers templ(R"(
function <functionName>(slot <key>) -> dataSlot {
mstore(0, <convertedKey>)
mstore(0x20, slot)
dataSlot := keccak256(0, 0x40)
}
)");
templ("functionName", functionName);
templ("key", _keyType.sizeOnStack() == 1 ? ", key" : "");
if (_keyType.sizeOnStack() == 0)
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "()");
else
templ("convertedKey", conversionFunction(_keyType, *_mappingType.keyType()) + "(key)");
return templ.render();
}
});
}
string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
"offset_" +
to_string(_offset) +
"_" +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot) -> value {
value := <extract>(sload(slot))
}
)")
("functionName", functionName)
("extract", extractFromStorageValue(_type, _offset, false))
.render();
});
}
string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_" +
string(_splitFunctionTypes ? "split_" : "") +
"offset_" +
to_string(_offset) +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value) -> value {
value := <cleanupStorage>(<shr>(slot_value))
}
)")
("functionName", functionName)
("shr", shiftRightFunction(_offset * 8))
("cleanupStorage", cleanupFromStorageFunction(_type, false))
.render();
});
}
string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes)
{
solAssert(_type.isValueType(), "");
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName = string("cleanup_from_storage_") + (_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
Whiskers templ(R"(
function <functionName>(value) -> cleaned {
cleaned := <cleaned>
}
)");
templ("functionName", functionName);
unsigned storageBytes = _type.storageBytes();
if (IntegerType const* type = dynamic_cast<IntegerType const*>(&_type))
if (type->isSigned() && storageBytes != 32)
{
templ("cleaned", "signextend(" + to_string(storageBytes - 1) + ", value)");
return templ.render();
}
if (storageBytes == 32)
templ("cleaned", "value");
else if (_type.leftAligned())
templ("cleaned", shiftLeftFunction(256 - 8 * storageBytes) + "(value)");
else
templ("cleaned", "and(value, " + toCompactHexWithPrefix((u256(1) << (8 * storageBytes)) - 1) + ")");
return templ.render();
});
}
string YulUtilFunctions::prepareStoreFunction(Type const& _type)
{
solUnimplementedAssert(_type.category() != Type::Category::Function, "");
string functionName = "prepare_store_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(value) -> ret {
ret := <actualPrepare>
}
)");
templ("functionName", functionName);
if (_type.category() == Type::Category::FixedBytes)
templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)");
else
templ("actualPrepare", "value");
return templ.render();
});
}
string YulUtilFunctions::allocationFunction()
{
string functionName = "allocateMemory";
@ -482,6 +710,9 @@ string YulUtilFunctions::allocationFunction()
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
return conversionFunctionSpecial(_from, _to);
string functionName =
"convert_" +
_from.identifier() +
@ -782,6 +1013,62 @@ string YulUtilFunctions::validatorFunction(Type const& _type, bool _revertOnFail
});
}
string YulUtilFunctions::packedHashFunction(
vector<Type const*> const& _givenTypes,
vector<Type const*> const& _targetTypes
)
{
string functionName = string("packed_hashed_");
for (auto const& t: _givenTypes)
functionName += t->identifier() + "_";
functionName += "_to_";
for (auto const& t: _targetTypes)
functionName += t->identifier() + "_";
size_t sizeOnStack = 0;
for (Type const* t: _givenTypes)
sizeOnStack += t->sizeOnStack();
return m_functionCollector->createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>(<variables>) -> hash {
let pos := mload(<freeMemoryPointer>)
let end := <packedEncode>(pos <comma> <variables>)
hash := keccak256(pos, sub(end, pos))
}
)");
templ("functionName", functionName);
templ("variables", suffixedVariableNameList("var_", 1, 1 + sizeOnStack));
templ("comma", sizeOnStack > 0 ? "," : "");
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("packedEncode", ABIFunctions(m_evmVersion, m_functionCollector).tupleEncoderPacked(_givenTypes, _targetTypes));
return templ.render();
});
}
string YulUtilFunctions::forwardingRevertFunction()
{
bool forward = m_evmVersion.supportsReturndata();
string functionName = "revert_forward_" + to_string(forward);
return m_functionCollector->createFunction(functionName, [&]() {
if (forward)
return Whiskers(R"(
function <functionName>() {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
)")
("functionName", functionName)
.render();
else
return Whiskers(R"(
function <functionName>() {
revert(0, 0)
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_t _startSuffix, size_t _endSuffix)
{
string result;
@ -799,3 +1086,166 @@ string YulUtilFunctions::suffixedVariableNameList(string const& _baseName, size_
}
return result;
}
std::string YulUtilFunctions::decrementCheckedFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "decrement_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
u256 minintval;
// Smallest admissible value to decrement
if (type.isSigned())
minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
else
minintval = 1;
return Whiskers(R"(
function <functionName>(value) -> ret {
if <lt>(value, <minval>) { revert(0,0) }
ret := sub(value, 1)
}
)")
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
("lt", type.isSigned() ? "slt" : "lt")
.render();
});
}
std::string YulUtilFunctions::incrementCheckedFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
string const functionName = "increment_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
u256 maxintval;
// Biggest admissible value to increment
if (type.isSigned())
maxintval = (u256(1) << (type.numBits() - 1)) - 2;
else
maxintval = (u256(1) << type.numBits()) - 2;
return Whiskers(R"(
function <functionName>(value) -> ret {
if <gt>(value, <maxval>) { revert(0,0) }
ret := add(value, 1)
}
)")
("functionName", functionName)
("maxval", toCompactHexWithPrefix(maxintval))
("gt", type.isSigned() ? "sgt" : "gt")
.render();
});
}
string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
{
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
solAssert(type.isSigned(), "Expected signed type!");
string const functionName = "negate_" + _type.identifier();
u256 const minintval = 0 - (u256(1) << (type.numBits() - 1)) + 1;
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(_value) -> ret {
if slt(_value, <minval>) { revert(0,0) }
ret := sub(0, _value)
}
)")
("functionName", functionName)
("minval", toCompactHexWithPrefix(minintval))
.render();
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type)
{
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
string const functionName = "zero_value_for_" + _type.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
}
)")
("functionName", functionName)
("body", "ret := 0x0")
.render();
});
}
string YulUtilFunctions::conversionFunctionSpecial(Type const& _from, Type const& _to)
{
string functionName =
"convert_" +
_from.identifier() +
"_to_" +
_to.identifier();
return m_functionCollector->createFunction(functionName, [&]() {
solUnimplementedAssert(
_from.category() == Type::Category::StringLiteral,
"Type conversion " + _from.toString() + " -> " + _to.toString() + " not yet implemented."
);
string const& data = dynamic_cast<StringLiteralType const&>(_from).value();
if (_to.category() == Type::Category::FixedBytes)
{
unsigned const numBytes = dynamic_cast<FixedBytesType const&>(_to).numBytes();
solAssert(data.size() <= 32, "");
Whiskers templ(R"(
function <functionName>() -> converted {
converted := <data>
}
)");
templ("functionName", functionName);
templ("data", formatNumber(
h256::Arith(h256(data, h256::AlignLeft)) &
(~(u256(-1) >> (8 * numBytes)))
));
return templ.render();
}
else if (_to.category() == Type::Category::Array)
{
auto const& arrayType = dynamic_cast<ArrayType const&>(_to);
solAssert(arrayType.isByteArray(), "");
size_t words = (data.size() + 31) / 32;
size_t storageSize = 32 + words * 32;
Whiskers templ(R"(
function <functionName>() -> converted {
converted := <allocate>(<storageSize>)
mstore(converted, <size>)
<#word>
mstore(add(converted, <offset>), <wordValue>)
</word>
}
)");
templ("functionName", functionName);
templ("allocate", allocationFunction());
templ("storageSize", to_string(storageSize));
templ("size", to_string(data.size()));
vector<map<string, string>> wordParams(words);
for (size_t i = 0; i < words; ++i)
{
wordParams[i]["offset"] = to_string(32 + i * 32);
wordParams[i]["wordValue"] = "0x" + h256(data.substr(32 * i, 32), h256::AlignLeft).hex();
}
templ("word", wordParams);
return templ.render();
}
else
solAssert(
false,
"Invalid conversion from string literal to " + _to.toString() + " requested."
);
});
}

View File

@ -26,6 +26,7 @@
#include <memory>
#include <string>
#include <vector>
namespace dev
{
@ -34,6 +35,8 @@ namespace solidity
class Type;
class ArrayType;
class MappingType;
class IntegerType;
/**
* Component that can generate various useful Yul functions.
@ -62,6 +65,10 @@ public:
/// Pads with zeros and might write more than exactly length.
std::string copyToMemoryFunction(bool _fromCalldata);
// @returns the name of a function that has the equivalent logic of an
// `assert` or `require` call.
std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr);
/// @returns the name of a function that takes a (cleaned) value of the given value type and
/// left-aligns it, usually for use in non-padded encoding.
std::string leftAlignFunction(Type const& _type);
@ -69,12 +76,28 @@ public:
std::string shiftLeftFunction(size_t _numBits);
std::string shiftRightFunction(size_t _numBits);
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
/// byte) by the _numBytes least significant bytes of `toInsert`.
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
/// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction();
std::string overflowCheckedUIntAddFunction(size_t _bits);
std::string overflowCheckedUIntMulFunction(size_t _bits);
/// @returns name of function to perform division on integers.
/// Checks for division by zero and the special case of
/// signed division of the smallest number by -1.
std::string overflowCheckedIntDivFunction(IntegerType const& _type);
/// @returns computes the difference between two values.
/// Assumes the input to be in range for the type.
std::string overflowCheckedUIntSubFunction();
std::string arrayLengthFunction(ArrayType const& _type);
/// @returns the name of a function that computes the number of bytes required
/// to store an array in memory given its length (internally encoded, not ABI encoded).
@ -88,6 +111,39 @@ public:
/// Only works for memory arrays, calldata arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type);
/// @returns the name of a function that performs index access for mappings.
/// @param _mappingType the type of the mapping
/// @param _keyType the type of the value provided
std::string mappingIndexAccessFunction(MappingType const& _mappingType, Type const& _keyType);
/// @returns a function that reads a value type from storage.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// @returns a function that extracts a value type from storage slot that has been
/// retrieved already.
/// Performs bit mask/sign extend cleanup and appropriate left / right shift, but not validation.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
/// Performs cleanup after reading from a potentially compressed storage slot.
/// The function does not perform any validation, it just masks or sign-extends
/// higher order bytes or left-aligns (in case of bytesNN).
/// The storage cleanup expects the value to be right-aligned with potentially
/// dirty higher order bytes.
/// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable.
std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes);
/// @returns the name of a function that prepares a value of the given type
/// for being stored in storage. This usually includes cleanup and right-alignment
/// to fit the number of bytes in storage.
/// The resulting value might still have dirty higher order bits.
std::string prepareStoreFunction(Type const& _type);
/// @returns the name of a function that allocates memory.
/// Modifies the "free memory pointer"
/// Arguments: size
@ -115,13 +171,32 @@ public:
/// This is used for data decoded from external sources.
std::string validatorFunction(Type const& _type, bool _revertOnFailure = false);
std::string packedHashFunction(std::vector<Type const*> const& _givenTypes, std::vector<Type const*> const& _targetTypes);
/// @returns the name of a function that reverts and uses returndata (if available)
/// as reason string.
std::string forwardingRevertFunction();
/// @returns a string containing a comma-separated list of variable names consisting of @a _baseName suffixed
/// with increasing integers in the range [@a _startSuffix, @a _endSuffix), if @a _startSuffix < @a _endSuffix,
/// and with decreasing integers in the range [@a _endSuffix, @a _startSuffix), if @a _endSuffix < @a _startSuffix.
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
static std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
std::string incrementCheckedFunction(Type const& _type);
std::string decrementCheckedFunction(Type const& _type);
std::string negateNumberCheckedFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the
/// provided type
std::string zeroValueFunction(Type const& _type);
private:
/// Special case of conversionFunction - handles everything that does not
/// use exactly one variable to hold the value.
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
langutil::EVMVersion m_evmVersion;
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
};

View File

@ -39,7 +39,7 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl
return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl)
{
solAssert(
m_localVariables.count(&_varDecl),
@ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
return m_localVariables[&_varDecl];
}
void IRGenerationContext::addStateVariable(
VariableDeclaration const& _declaration,
u256 _storageOffset,
unsigned _byteOffset
)
{
m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset);
}
string IRGenerationContext::functionName(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
@ -85,18 +94,27 @@ string IRGenerationContext::newYulVariable()
string IRGenerationContext::variable(Expression const& _expression)
{
unsigned size = _expression.annotation().type->sizeOnStack();
solUnimplementedAssert(size == 1, "");
return "expr_" + to_string(_expression.id());
string var = "expr_" + to_string(_expression.id());
if (size == 1)
return var;
else
return YulUtilFunctions::suffixedVariableNameList(move(var) + "_", 1, 1 + size);
}
string IRGenerationContext::variablePart(Expression const& _expression, size_t _part)
{
size_t numVars = _expression.annotation().type->sizeOnStack();
solAssert(numVars > 1, "");
solAssert(1 <= _part && _part <= numVars, "");
return "expr_" + to_string(_expression.id()) + "_" + to_string(_part);
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
// TODO can we limit the generated functions to only those visited
// in the expression context? What about creation / runtime context?
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun <comma> <in>) -> <out> {
function <functionName>(fun <comma> <in>) <arrow> <out> {
switch fun
<#cases>
case <funID>
@ -111,6 +129,7 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_functions);
templ("in", utils.suffixedVariableNameList("in_", 0, _in));
templ("arrow", _out > 0 ? "->" : "");
templ("out", utils.suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy)
@ -120,11 +139,21 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
function->parameters().size() == _in &&
function->returnParameters().size() == _out
)
{
// 0 is reserved for uninitialized function pointers
solAssert(function->id() != 0, "Unexpected function ID: 0");
functions.emplace_back(map<string, string> {
{ "funID", to_string(function->id()) },
{ "name", functionName(*function)}
});
}
templ("cases", move(functions));
return templ.render();
});
}
YulUtilFunctions IRGenerationContext::utils()
{
return YulUtilFunctions(m_evmVersion, m_functions);
}

View File

@ -26,6 +26,8 @@
#include <liblangutil/EVMVersion.h>
#include <libdevcore/Common.h>
#include <string>
#include <memory>
#include <vector>
@ -39,6 +41,7 @@ class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
class YulUtilFunctions;
/**
* Class that contains contextual information during IR generation.
@ -62,7 +65,16 @@ public:
std::string addLocalVariable(VariableDeclaration const& _varDecl);
std::string variableName(VariableDeclaration const& _varDecl);
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
std::string localVariableName(VariableDeclaration const& _varDecl);
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const
{
return m_stateVariables.at(&_varDecl);
}
std::string functionName(FunctionDefinition const& _function);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
@ -71,14 +83,24 @@ public:
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
/// @returns the variable of a multi-variable expression. Variables are numbered
/// starting from 1.
std::string variablePart(Expression const& _expression, size_t _part);
std::string internalDispatch(size_t _in, size_t _out);
/// @returns a new copy of the utility function generator (but using the same function set).
YulUtilFunctions utils();
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
private:
langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables;
/// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
size_t m_varCounter = 0;
};

View File

@ -71,6 +71,8 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
string IRGenerator::generate(ContractDefinition const& _contract)
{
solUnimplementedAssert(!_contract.isLibrary(), "Libraries not yet implemented.");
Whiskers t(R"(
object "<CreationObject>" {
code {
@ -89,18 +91,27 @@ string IRGenerator::generate(ContractDefinition const& _contract)
}
)");
resetContext();
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
t("constructor", constructorCode(_contract));
t("deploy", deployCode(_contract));
// We generate code for all functions and rely on the optimizer to remove them again
// TODO it would probably be better to only generate functions when internalDispatch or
// virtualFunctionName is called - same below.
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("functions", m_context.functionCollector()->requestedFunctions());
resetContext();
resetContext(_contract);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
for (auto const* contract: _contract.annotation().linearizedBaseContracts)
for (auto const* fun: contract->definedFunctions())
generateFunction(*fun);
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
return t.render();
}
@ -116,7 +127,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n");
Whiskers t(R"(
function <functionName>(<params>) <returns> {
for { let return_flag := 1 } return_flag {} {
<body>
break
}
}
)");
t("functionName", functionName);
string params;
for (auto const& varDecl: _function.parameters())
@ -131,15 +149,21 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
});
}
string IRGenerator::constructorCode(FunctionDefinition const& _constructor)
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
string out;
if (!_constructor.isPayable())
out = callValueCheck();
// TODO initialize state variables in base to derived order.
// TODO base constructors
// TODO callValueCheck if there is no constructor.
if (FunctionDefinition const* constructor = _contract.constructor())
{
string out;
if (!constructor->isPayable())
out = callValueCheck();
solUnimplementedAssert(constructor->parameters().empty(), "");
return move(out) + m_context.functionName(*constructor) + "()\n";
}
solUnimplemented("Constructors are not yet implemented.");
return out;
return {};
}
string IRGenerator::deployCode(ContractDefinition const& _contract)
@ -242,7 +266,7 @@ string IRGenerator::memoryInit()
.render();
}
void IRGenerator::resetContext()
void IRGenerator::resetContext(ContractDefinition const& _contract)
{
solAssert(
m_context.functionCollector()->requestedFunctions().empty(),
@ -250,4 +274,8 @@ void IRGenerator::resetContext()
);
m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings);
m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector());
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
for (auto const& var: ContractType(_contract).stateVariables())
m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}

View File

@ -57,7 +57,7 @@ private:
/// Generates code for and returns the name of the function.
std::string generateFunction(FunctionDefinition const& _function);
std::string constructorCode(FunctionDefinition const& _constructor);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
@ -68,7 +68,7 @@ private:
std::string memoryInit();
void resetContext();
void resetContext(ContractDefinition const& _contract);
langutil::EVMVersion const m_evmVersion;
OptimiserSettings const m_optimiserSettings;

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@
#pragma once
#include <libsolidity/ast/ASTVisitor.h>
#include <libsolidity/codegen/ir/IRLValue.h>
namespace dev
{
@ -42,25 +43,70 @@ public:
m_utils(_utils)
{}
std::string code() const { return m_code.str(); }
std::string code() const;
bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Assignment const& _assignment) override;
bool visit(Return const& _return) override;
void endVisit(BinaryOperation const& _binOp) override;
bool visit(FunctionCall const& _funCall) override;
bool visit(TupleExpression const& _tuple) override;
bool visit(IfStatement const& _ifStatement) override;
bool visit(ForStatement const& _forStatement) override;
bool visit(WhileStatement const& _whileStatement) override;
bool visit(Continue const& _continueStatement) override;
bool visit(Break const& _breakStatement) override;
void endVisit(Return const& _return) override;
void endVisit(UnaryOperation const& _unaryOperation) override;
bool visit(BinaryOperation const& _binOp) override;
void endVisit(FunctionCall const& _funCall) override;
void endVisit(MemberAccess const& _memberAccess) override;
bool visit(InlineAssembly const& _inlineAsm) override;
bool visit(Identifier const& _identifier) override;
void endVisit(IndexAccess const& _indexAccess) override;
void endVisit(Identifier const& _identifier) override;
bool visit(Literal const& _literal) override;
private:
/// Appends code to call an external function with the given arguments.
/// All involved expressions have already been visited.
void appendExternalFunctionCall(
FunctionCall const& _functionCall,
std::vector<ASTPointer<Expression const>> const& _arguments
);
std::string fetchFreeMem() const;
/// @returns a Yul expression representing the current value of @a _expression,
/// converted to type @a _to if it does not yet have that type.
std::string expressionAsType(Expression const& _expression, Type const& _to);
std::ostream& defineExpression(Expression const& _expression);
/// Defines only one of many variables corresponding to an expression.
/// We start counting at 1 instead of 0.
std::ostream& defineExpressionPart(Expression const& _expression, size_t _part);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);
/// @returns code to perform the given binary operation in the given type on the two values.
std::string binaryOperation(
langutil::Token _op,
Type const& _type,
std::string const& _left,
std::string const& _right
);
void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue);
void generateLoop(
Statement const& _body,
Expression const* _conditionExpression,
Statement const* _initExpression = nullptr,
ExpressionStatement const* _loopExpression = nullptr,
bool _isDoWhile = false
);
static Type const& type(Expression const& _expression);
std::ostringstream m_code;
IRGenerationContext& m_context;
YulUtilFunctions& m_utils;
std::unique_ptr<IRLValue> m_currentLValue;
};
}

View File

@ -0,0 +1,122 @@
/*
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/>.
*/
/**
* Generator for code that handles LValues.
*/
#include <libsolidity/codegen/ir/IRLValue.h>
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libdevcore/Whiskers.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
IRLocalVariable::IRLocalVariable(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_context, _varDecl.annotation().type),
m_variableName(_context.localVariableName(_varDecl))
{
}
string IRLocalVariable::storeValue(string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Storing different types - not necessarily a problem.");
return m_variableName + " := " + _value + "\n";
}
string IRLocalVariable::setToZero() const
{
return storeValue(m_context.utils().zeroValueFunction(*m_type) + "()", *m_type);
}
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
):
IRLValue(_context, _varDecl.annotation().type)
{
u256 slot;
unsigned offset;
std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl);
m_slot = toCompactHexWithPrefix(slot);
m_offset = offset;
}
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
string _slot,
unsigned _offset,
Type const& _type
):
IRLValue(_context, &_type),
m_slot(move(_slot)),
m_offset(_offset)
{
}
string IRStorageItem::retrieveValue() const
{
if (!m_type->isValueType())
return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")";
}
string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{
if (m_type->isValueType())
{
solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() + m_offset <= 32, "");
solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
return Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n")
("slot", m_slot)
("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset))
("prepare", m_context.utils().prepareStoreFunction(*m_type))
("value", _value)
.render();
}
else
{
solAssert(
_sourceType.category() == m_type->category(),
"Wrong type conversation for assignment."
);
if (m_type->category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (m_type->category() == Type::Category::Struct)
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
}
string IRStorageItem::setToZero() const
{
solUnimplemented("Delete for storage location not yet implemented");
}

View File

@ -0,0 +1,100 @@
/*
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/>.
*/
/**
* Generator for code that handles LValues.
*/
#pragma once
#include <string>
#include <ostream>
namespace dev
{
namespace solidity
{
class VariableDeclaration;
class IRGenerationContext;
class Type;
/**
* Abstract class used to retrieve, delete and store data in LValues.
*/
class IRLValue
{
protected:
IRLValue(IRGenerationContext& _context, Type const* _type = nullptr):
m_context(_context),
m_type(_type)
{}
public:
virtual ~IRLValue() = default;
/// @returns an expression to retrieve the value of the lvalue.
virtual std::string retrieveValue() const = 0;
/// Returns code that stores the value of @a _value (should be an identifier)
/// of type @a _type in the lvalue. Might perform type conversion.
virtual std::string storeValue(std::string const& _value, Type const& _type) const = 0;
/// Returns code that will reset the stored value to zero
virtual std::string setToZero() const = 0;
protected:
IRGenerationContext& m_context;
Type const* m_type;
};
class IRLocalVariable: public IRLValue
{
public:
IRLocalVariable(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
std::string retrieveValue() const override { return m_variableName; }
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
std::string m_variableName;
};
class IRStorageItem: public IRLValue
{
public:
IRStorageItem(
IRGenerationContext& _context,
VariableDeclaration const& _varDecl
);
IRStorageItem(
IRGenerationContext& _context,
std::string _slot,
unsigned _offset,
Type const& _type
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
std::string m_slot;
unsigned m_offset;
};
}
}

View File

@ -21,42 +21,179 @@
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::smt;
EncodingContext::EncodingContext(SolverInterface& _solver):
m_solver(_solver),
m_thisAddress(make_unique<SymbolicAddressVariable>("this", m_solver))
{
auto sort = make_shared<smt::ArraySort>(
make_shared<smt::Sort>(smt::Kind::Int),
make_shared<smt::Sort>(smt::Kind::Int)
auto sort = make_shared<ArraySort>(
make_shared<Sort>(Kind::Int),
make_shared<Sort>(Kind::Int)
);
m_balances = make_unique<SymbolicVariable>(sort, "balances", m_solver);
}
void EncodingContext::reset()
{
resetAllVariables();
m_expressions.clear();
m_globalContext.clear();
m_thisAddress->increaseIndex();
m_balances->increaseIndex();
}
smt::Expression EncodingContext::thisAddress()
/// Variables.
shared_ptr<SymbolicVariable> EncodingContext::variable(solidity::VariableDeclaration const& _varDecl)
{
solAssert(knownVariable(_varDecl), "");
return m_variables[&_varDecl];
}
bool EncodingContext::createVariable(solidity::VariableDeclaration const& _varDecl)
{
solAssert(!knownVariable(_varDecl), "");
auto const& type = _varDecl.type();
auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), m_solver);
m_variables.emplace(&_varDecl, result.second);
return result.first;
}
bool EncodingContext::knownVariable(solidity::VariableDeclaration const& _varDecl)
{
return m_variables.count(&_varDecl);
}
void EncodingContext::resetVariable(solidity::VariableDeclaration const& _variable)
{
newValue(_variable);
setUnknownValue(_variable);
}
void EncodingContext::resetVariables(set<solidity::VariableDeclaration const*> const& _variables)
{
for (auto const* decl: _variables)
resetVariable(*decl);
}
void EncodingContext::resetVariables(function<bool(solidity::VariableDeclaration const&)> const& _filter)
{
for_each(begin(m_variables), end(m_variables), [&](auto _variable)
{
if (_filter(*_variable.first))
this->resetVariable(*_variable.first);
});
}
void EncodingContext::resetAllVariables()
{
resetVariables([&](solidity::VariableDeclaration const&) { return true; });
}
Expression EncodingContext::newValue(solidity::VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
return m_variables.at(&_decl)->increaseIndex();
}
void EncodingContext::setZeroValue(solidity::VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
setZeroValue(*m_variables.at(&_decl));
}
void EncodingContext::setZeroValue(SymbolicVariable& _variable)
{
setSymbolicZeroValue(_variable, m_solver);
}
void EncodingContext::setUnknownValue(solidity::VariableDeclaration const& _decl)
{
solAssert(knownVariable(_decl), "");
setUnknownValue(*m_variables.at(&_decl));
}
void EncodingContext::setUnknownValue(SymbolicVariable& _variable)
{
setSymbolicUnknownValue(_variable, m_solver);
}
/// Expressions
shared_ptr<SymbolicVariable> EncodingContext::expression(solidity::Expression const& _e)
{
if (!knownExpression(_e))
createExpression(_e);
return m_expressions.at(&_e);
}
bool EncodingContext::createExpression(solidity::Expression const& _e, shared_ptr<SymbolicVariable> _symbVar)
{
solAssert(_e.annotation().type, "");
if (knownExpression(_e))
{
expression(_e)->increaseIndex();
return false;
}
else if (_symbVar)
{
m_expressions.emplace(&_e, _symbVar);
return false;
}
else
{
auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), m_solver);
m_expressions.emplace(&_e, result.second);
return result.first;
}
}
bool EncodingContext::knownExpression(solidity::Expression const& _e) const
{
return m_expressions.count(&_e);
}
/// Global variables and functions.
shared_ptr<SymbolicVariable> EncodingContext::globalSymbol(string const& _name)
{
solAssert(knownGlobalSymbol(_name), "");
return m_globalContext.at(_name);
}
bool EncodingContext::createGlobalSymbol(string const& _name, solidity::Expression const& _expr)
{
solAssert(!knownGlobalSymbol(_name), "");
auto result = newSymbolicVariable(*_expr.annotation().type, _name, m_solver);
m_globalContext.emplace(_name, result.second);
setUnknownValue(*result.second);
return result.first;
}
bool EncodingContext::knownGlobalSymbol(string const& _var) const
{
return m_globalContext.count(_var);
}
// Blockchain
Expression EncodingContext::thisAddress()
{
return m_thisAddress->currentValue();
}
smt::Expression EncodingContext::balance()
Expression EncodingContext::balance()
{
return balance(m_thisAddress->currentValue());
}
smt::Expression EncodingContext::balance(smt::Expression _address)
Expression EncodingContext::balance(Expression _address)
{
return smt::Expression::select(m_balances->currentValue(), move(_address));
return Expression::select(m_balances->currentValue(), move(_address));
}
void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value)
void EncodingContext::transfer(Expression _from, Expression _to, Expression _value)
{
unsigned indexBefore = m_balances->index();
addBalance(_from, 0 - _value);
@ -65,7 +202,7 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::
solAssert(indexAfter > indexBefore, "");
m_balances->increaseIndex();
/// Do not apply the transfer operation if _from == _to.
auto newBalances = smt::Expression::ite(
auto newBalances = Expression::ite(
move(_from) == move(_to),
m_balances->valueAtIndex(indexBefore),
m_balances->valueAtIndex(indexAfter)
@ -73,9 +210,9 @@ void EncodingContext::transfer(smt::Expression _from, smt::Expression _to, smt::
m_solver.addAssertion(m_balances->currentValue() == newBalances);
}
void EncodingContext::addBalance(smt::Expression _address, smt::Expression _value)
void EncodingContext::addBalance(Expression _address, Expression _value)
{
auto newBalances = smt::Expression::store(
auto newBalances = Expression::store(
m_balances->currentValue(),
_address,
balance(_address) + move(_value)

View File

@ -20,6 +20,9 @@
#include <libsolidity/formal/SolverInterface.h>
#include <libsolidity/formal/SymbolicVariables.h>
#include <unordered_map>
#include <set>
namespace dev
{
namespace solidity
@ -38,22 +41,94 @@ public:
/// Resets the entire context.
void reset();
/// Value of `this` address.
smt::Expression thisAddress();
/// Methods related to variables.
//@{
/// @returns the symbolic representation of a program variable.
std::shared_ptr<SymbolicVariable> variable(solidity::VariableDeclaration const& _varDecl);
/// @returns all symbolic variables.
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> const& variables() const { return m_variables; }
/// Creates a symbolic variable and
/// @returns true if a variable's type is not supported and is therefore abstract.
bool createVariable(solidity::VariableDeclaration const& _varDecl);
/// @returns true if variable was created.
bool knownVariable(solidity::VariableDeclaration const& _varDecl);
/// Resets a specific variable.
void resetVariable(solidity::VariableDeclaration const& _variable);
/// Resets a set of variables.
void resetVariables(std::set<solidity::VariableDeclaration const*> const& _variables);
/// Resets variables according to a predicate.
void resetVariables(std::function<bool(solidity::VariableDeclaration const&)> const& _filter);
///Resets all variables.
void resetAllVariables();
/// Allocates a new index for the declaration, updates the current
/// index to this value and returns the expression.
Expression newValue(solidity::VariableDeclaration const& _decl);
/// Sets the value of the declaration to zero.
void setZeroValue(solidity::VariableDeclaration const& _decl);
void setZeroValue(SymbolicVariable& _variable);
/// Resets the variable to an unknown value (in its range).
void setUnknownValue(solidity::VariableDeclaration const& decl);
void setUnknownValue(SymbolicVariable& _variable);
//@}
/// Methods related to expressions.
////@{
/// @returns the symbolic representation of an AST node expression.
std::shared_ptr<SymbolicVariable> expression(solidity::Expression const& _e);
/// @returns all symbolic expressions.
std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> const& expressions() const { return m_expressions; }
/// Creates the expression (value can be arbitrary).
/// @returns true if type is not supported.
bool createExpression(solidity::Expression const& _e, std::shared_ptr<SymbolicVariable> _symbExpr = nullptr);
/// Checks if expression was created.
bool knownExpression(solidity::Expression const& _e) const;
//@}
/// Methods related to global variables and functions.
//@{
/// Global variables and functions.
std::shared_ptr<SymbolicVariable> globalSymbol(std::string const& _name);
/// @returns all symbolic variables.
std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> const& globalSymbols() const { return m_globalContext; }
/// Defines a new global variable or function
/// and @returns true if type was abstracted.
bool createGlobalSymbol(std::string const& _name, solidity::Expression const& _expr);
/// Checks if special variable or function was seen.
bool knownGlobalSymbol(std::string const& _var) const;
//@}
/// Blockchain related methods.
//@{
/// Value of `this` address.
Expression thisAddress();
/// @returns the symbolic balance of address `this`.
smt::Expression balance();
Expression balance();
/// @returns the symbolic balance of an address.
smt::Expression balance(smt::Expression _address);
Expression balance(Expression _address);
/// Transfer _value from _from to _to.
void transfer(smt::Expression _from, smt::Expression _to, smt::Expression _value);
void transfer(Expression _from, Expression _to, Expression _value);
//@}
private:
/// Adds _value to _account's balance.
void addBalance(smt::Expression _account, smt::Expression _value);
void addBalance(Expression _account, Expression _value);
SolverInterface& m_solver;
/// Symbolic variables.
std::unordered_map<solidity::VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
/// Symbolic expressions.
std::unordered_map<solidity::Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions;
/// Symbolic representation of global symbols including
/// variables and functions.
std::unordered_map<std::string, std::shared_ptr<smt::SymbolicVariable>> m_globalContext;
/// Symbolic `this` address.
std::unique_ptr<SymbolicAddressVariable> m_thisAddress;

File diff suppressed because it is too large Load Diff

View File

@ -55,9 +55,11 @@ public:
/// the constructor.
std::vector<std::string> unhandledQueries() { return m_interface->unhandledQueries(); }
/// @return the FunctionDefinition of a called function if possible and should inline,
/// @returns the FunctionDefinition of a called function if possible and should inline,
/// otherwise nullptr.
static FunctionDefinition const* inlinedFunctionCallToDefinition(FunctionCall const& _funCall);
/// @returns the leftmost identifier in a multi-d IndexAccess.
static Expression const* leftmostBase(IndexAccess const& _indexAccess);
private:
// TODO: Check that we do not have concurrent reads and writes to a variable,
@ -115,14 +117,18 @@ private:
void inlineFunctionCall(FunctionCall const& _funCall);
/// Creates an uninterpreted function call.
void abstractFunctionCall(FunctionCall const& _funCall);
/// Inlines if the function call is internal or external to `this`.
/// Erases knowledge about state variables if external.
void internalOrExternalFunctionCall(FunctionCall const& _funCall);
void visitFunctionIdentifier(Identifier const& _identifier);
/// Encodes a modifier or function body according to the modifier
/// visit depth.
void visitFunctionOrModifier();
/// Defines a new global variable or function.
void defineGlobalVariable(std::string const& _name, Expression const& _expr, bool _increaseIndex = false);
void defineGlobalFunction(std::string const& _name, Expression const& _expr);
/// Handles the side effects of assignment
/// to variable of some SMT array type
/// while aliasing is not supported.
@ -135,7 +141,18 @@ private:
smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
void assignment(VariableDeclaration const& _variable, Expression const& _value, langutil::SourceLocation const& _location);
/// Handles assignments to variables of different types.
void assignment(VariableDeclaration const& _variable, smt::Expression const& _value, langutil::SourceLocation const& _location);
/// Handles assignments between generic expressions.
/// Will also be used for assignments of tuple components.
void assignment(
Expression const& _left,
std::vector<smt::Expression> const& _right,
TypePointer const& _type,
langutil::SourceLocation const& _location
);
/// Computes the right hand side of a compound assignment.
smt::Expression compoundAssignment(Assignment const& _assignment);
/// Maps a variable to an SSA index.
using VariableIndices = std::unordered_map<VariableDeclaration const*, int>;
@ -162,6 +179,8 @@ private:
std::string const& _description
);
using CallStackEntry = std::pair<CallableDeclaration const*, ASTNode const*>;
struct OverflowTarget
{
enum class Type { Underflow, Overflow, All } type;
@ -169,9 +188,9 @@ private:
smt::Expression value;
smt::Expression path;
langutil::SourceLocation const& location;
std::vector<ASTNode const*> callStack;
std::vector<CallStackEntry> callStack;
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<ASTNode const*> _callStack):
OverflowTarget(Type _type, TypePointer _intType, smt::Expression _value, smt::Expression _path, langutil::SourceLocation const& _location, std::vector<CallStackEntry> _callStack):
type(_type),
intType(_intType),
value(_value),
@ -198,11 +217,8 @@ private:
void initializeLocalVariables(FunctionDefinition const& _function);
void initializeFunctionCallParameters(CallableDeclaration const& _function, std::vector<smt::Expression> const& _callArgs);
void resetVariable(VariableDeclaration const& _variable);
void resetStateVariables();
void resetStorageReferences();
void resetVariables(std::set<VariableDeclaration const*> const& _variables);
void resetVariables(std::function<bool(VariableDeclaration const&)> const& _filter);
/// @returns the type without storage pointer information if it has it.
TypePointer typeWithoutPointer(TypePointer const& _type);
@ -211,41 +227,21 @@ private:
/// using the branch condition as guard.
void mergeVariables(std::set<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse);
/// Tries to create an uninitialized variable and returns true on success.
/// This fails if the type is not supported.
bool createVariable(VariableDeclaration const& _varDecl);
/// @returns true if _delc is a variable that is known at the current point, i.e.
/// has a valid index
bool knownVariable(VariableDeclaration const& _decl);
/// @returns an expression denoting the value of the variable declared in @a _decl
/// at the current point.
smt::Expression currentValue(VariableDeclaration const& _decl);
/// @returns an expression denoting the value of the variable declared in @a _decl
/// at the given index. Does not ensure that this index exists.
smt::Expression valueAtIndex(VariableDeclaration const& _decl, int _index);
/// Allocates a new index for the declaration, updates the current
/// index to this value and returns the expression.
smt::Expression newValue(VariableDeclaration const& _decl);
/// Sets the value of the declaration to zero.
void setZeroValue(VariableDeclaration const& _decl);
void setZeroValue(SymbolicVariable& _variable);
/// Resets the variable to an unknown value (in its range).
void setUnknownValue(VariableDeclaration const& decl);
void setUnknownValue(SymbolicVariable& _variable);
/// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e);
/// Creates the expression (value can be arbitrary)
void createExpr(Expression const& _e);
/// Checks if expression was created
bool knownExpr(Expression const& _e) const;
/// Creates the expression and sets its value.
void defineExpr(Expression const& _e, smt::Expression _value);
/// Checks if special variable or function was seen.
bool knownGlobalSymbol(std::string const& _var) const;
/// Adds a new path condition
void pushPathCondition(smt::Expression const& _e);
/// Remove the last path condition
@ -255,17 +251,14 @@ private:
/// Returns the current callstack. Used for models.
langutil::SecondarySourceLocation currentCallStack();
/// Copies and pops the last called node.
ASTNode const* popCallStack();
/// Adds @param _node to the callstack.
void pushCallStack(ASTNode const* _node);
CallStackEntry popCallStack();
/// Adds (_definition, _node) to the callstack.
void pushCallStack(CallStackEntry _entry);
/// Conjoin the current path conditions with the given parameter and add to the solver
void addPathConjoinedExpression(smt::Expression const& _e);
/// Add to the solver: the given expression implied by the current path conditions
void addPathImpliedExpression(smt::Expression const& _e);
/// Removes local variables from the context.
void removeLocalVariables();
/// Copy the SSA indices of m_variables.
VariableIndices copyVariableIndices();
/// Resets the variable indices.
@ -274,18 +267,16 @@ private:
/// @returns variables that are touched in _node's subtree.
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node);
std::shared_ptr<smt::SolverInterface> m_interface;
VariableUsage m_variableUsage;
/// @returns the VariableDeclaration referenced by an Identifier or nullptr.
VariableDeclaration const* identifierToVariable(Expression const& _expr);
std::unique_ptr<smt::SolverInterface> m_interface;
smt::VariableUsage m_variableUsage;
bool m_loopExecutionHappened = false;
bool m_arrayAssignmentHappened = false;
bool m_externalFunctionCallHappened = false;
// True if the "No SMT solver available" warning was already created.
bool m_noSolverWarning = false;
/// An Expression may have multiple smt::Expression due to
/// repeated calls to the same function.
std::unordered_map<Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions;
std::unordered_map<VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables;
std::unordered_map<std::string, std::shared_ptr<SymbolicVariable>> m_globalContext;
/// Stores the instances of an Uninterpreted Function applied to arguments.
/// These may be direct application of UFs or Array index access.
@ -301,10 +292,8 @@ private:
langutil::ErrorList m_smtErrors;
std::shared_ptr<langutil::Scanner> m_scanner;
/// Stores the current path of function calls.
std::vector<FunctionDefinition const*> m_functionPath;
/// Stores the current call/invocation path.
std::vector<ASTNode const*> m_callStack;
/// Stores the current function/modifier call/invocation path.
std::vector<CallStackEntry> m_callStack;
/// Returns true if the current function was not visited by
/// a function call.
bool isRootFunction();

View File

@ -32,42 +32,42 @@ using namespace dev::solidity::smt;
SMTPortfolio::SMTPortfolio(map<h256, string> const& _smtlib2Responses)
{
m_solvers.emplace_back(make_shared<smt::SMTLib2Interface>(_smtlib2Responses));
m_solvers.emplace_back(make_unique<smt::SMTLib2Interface>(_smtlib2Responses));
#ifdef HAVE_Z3
m_solvers.emplace_back(make_shared<smt::Z3Interface>());
m_solvers.emplace_back(make_unique<smt::Z3Interface>());
#endif
#ifdef HAVE_CVC4
m_solvers.emplace_back(make_shared<smt::CVC4Interface>());
m_solvers.emplace_back(make_unique<smt::CVC4Interface>());
#endif
}
void SMTPortfolio::reset()
{
for (auto s : m_solvers)
for (auto const& s: m_solvers)
s->reset();
}
void SMTPortfolio::push()
{
for (auto s : m_solvers)
for (auto const& s: m_solvers)
s->push();
}
void SMTPortfolio::pop()
{
for (auto s : m_solvers)
for (auto const& s: m_solvers)
s->pop();
}
void SMTPortfolio::declareVariable(string const& _name, Sort const& _sort)
{
for (auto s : m_solvers)
for (auto const& s: m_solvers)
s->declareVariable(_name, _sort);
}
void SMTPortfolio::addAssertion(Expression const& _expr)
{
for (auto s : m_solvers)
for (auto const& s: m_solvers)
s->addAssertion(_expr);
}
@ -105,7 +105,7 @@ pair<CheckResult, vector<string>> SMTPortfolio::check(vector<Expression> const&
{
CheckResult lastResult = CheckResult::ERROR;
vector<string> finalValues;
for (auto s : m_solvers)
for (auto const& s: m_solvers)
{
CheckResult result;
vector<string> values;
@ -134,8 +134,8 @@ vector<string> SMTPortfolio::unhandledQueries()
// This code assumes that the constructor guarantees that
// SmtLib2Interface is in position 0.
solAssert(!m_solvers.empty(), "");
solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.at(0).get()), "");
return m_solvers.at(0)->unhandledQueries();
solAssert(dynamic_cast<smt::SMTLib2Interface*>(m_solvers.front().get()), "");
return m_solvers.front()->unhandledQueries();
}
bool SMTPortfolio::solverAnswered(CheckResult result)

View File

@ -59,7 +59,7 @@ public:
private:
static bool solverAnswered(CheckResult result);
std::vector<std::shared_ptr<smt::SolverInterface>> m_solvers;
std::vector<std::unique_ptr<smt::SolverInterface>> m_solvers;
};
}

View File

@ -18,7 +18,7 @@
#include <libsolidity/formal/SSAVariable.h>
using namespace std;
using namespace dev::solidity;
using namespace dev::solidity::smt;
SSAVariable::SSAVariable()
{
@ -28,6 +28,5 @@ SSAVariable::SSAVariable()
void SSAVariable::resetIndex()
{
m_currentIndex = 0;
m_nextFreeIndex.reset (new unsigned);
*m_nextFreeIndex = 1;
m_nextFreeIndex = make_unique<unsigned>(1);
}

View File

@ -23,6 +23,8 @@ namespace dev
{
namespace solidity
{
namespace smt
{
/**
* This class represents the SSA representation of a program variable.
@ -44,10 +46,9 @@ public:
private:
unsigned m_currentIndex;
/// The next free index is a shared pointer because we want
/// the copy and the copied to share it.
std::shared_ptr<unsigned> m_nextFreeIndex;
std::unique_ptr<unsigned> m_nextFreeIndex;
};
}
}
}

View File

@ -22,105 +22,112 @@
#include <memory>
using namespace std;
using namespace dev::solidity;
smt::SortPointer dev::solidity::smtSort(Type const& _type)
namespace dev
{
namespace solidity
{
namespace smt
{
SortPointer smtSort(solidity::Type const& _type)
{
switch (smtKind(_type.category()))
{
case smt::Kind::Int:
return make_shared<smt::Sort>(smt::Kind::Int);
case smt::Kind::Bool:
return make_shared<smt::Sort>(smt::Kind::Bool);
case smt::Kind::Function:
case Kind::Int:
return make_shared<Sort>(Kind::Int);
case Kind::Bool:
return make_shared<Sort>(Kind::Bool);
case Kind::Function:
{
auto fType = dynamic_cast<FunctionType const*>(&_type);
auto fType = dynamic_cast<solidity::FunctionType const*>(&_type);
solAssert(fType, "");
vector<smt::SortPointer> parameterSorts = smtSort(fType->parameterTypes());
vector<SortPointer> parameterSorts = smtSort(fType->parameterTypes());
auto returnTypes = fType->returnParameterTypes();
smt::SortPointer returnSort;
SortPointer returnSort;
// TODO change this when we support tuples.
if (returnTypes.size() == 0)
// We cannot declare functions without a return sort, so we use the smallest.
returnSort = make_shared<smt::Sort>(smt::Kind::Bool);
returnSort = make_shared<Sort>(Kind::Bool);
else if (returnTypes.size() > 1)
// Abstract sort.
returnSort = make_shared<smt::Sort>(smt::Kind::Int);
returnSort = make_shared<Sort>(Kind::Int);
else
returnSort = smtSort(*returnTypes.at(0));
return make_shared<smt::FunctionSort>(parameterSorts, returnSort);
returnSort = smtSort(*returnTypes.front());
return make_shared<FunctionSort>(parameterSorts, returnSort);
}
case smt::Kind::Array:
case Kind::Array:
{
if (isMapping(_type.category()))
{
auto mapType = dynamic_cast<MappingType const*>(&_type);
auto mapType = dynamic_cast<solidity::MappingType const*>(&_type);
solAssert(mapType, "");
return make_shared<smt::ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
return make_shared<ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
}
else
{
solAssert(isArray(_type.category()), "");
auto arrayType = dynamic_cast<ArrayType const*>(&_type);
auto arrayType = dynamic_cast<solidity::ArrayType const*>(&_type);
solAssert(arrayType, "");
return make_shared<smt::ArraySort>(make_shared<smt::Sort>(smt::Kind::Int), smtSort(*arrayType->baseType()));
return make_shared<ArraySort>(make_shared<Sort>(Kind::Int), smtSort(*arrayType->baseType()));
}
}
default:
// Abstract case.
return make_shared<smt::Sort>(smt::Kind::Int);
return make_shared<Sort>(Kind::Int);
}
}
vector<smt::SortPointer> dev::solidity::smtSort(vector<TypePointer> const& _types)
vector<SortPointer> smtSort(vector<solidity::TypePointer> const& _types)
{
vector<smt::SortPointer> sorts;
vector<SortPointer> sorts;
for (auto const& type: _types)
sorts.push_back(smtSort(*type));
return sorts;
}
smt::Kind dev::solidity::smtKind(Type::Category _category)
Kind smtKind(solidity::Type::Category _category)
{
if (isNumber(_category))
return smt::Kind::Int;
return Kind::Int;
else if (isBool(_category))
return smt::Kind::Bool;
return Kind::Bool;
else if (isFunction(_category))
return smt::Kind::Function;
return Kind::Function;
else if (isMapping(_category) || isArray(_category))
return smt::Kind::Array;
return Kind::Array;
// Abstract case.
return smt::Kind::Int;
return Kind::Int;
}
bool dev::solidity::isSupportedType(Type::Category _category)
bool isSupportedType(solidity::Type::Category _category)
{
return isNumber(_category) ||
isBool(_category) ||
isMapping(_category) ||
isArray(_category);
isArray(_category) ||
isTuple(_category);
}
bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category)
bool isSupportedTypeDeclaration(solidity::Type::Category _category)
{
return isSupportedType(_category) ||
isFunction(_category);
}
pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
Type const& _type,
pair<bool, shared_ptr<SymbolicVariable>> newSymbolicVariable(
solidity::Type const& _type,
std::string const& _uniqueName,
smt::SolverInterface& _solver
SolverInterface& _solver
)
{
bool abstract = false;
shared_ptr<SymbolicVariable> var;
TypePointer type = &_type;
solidity::TypePointer type = &_type;
if (!isSupportedTypeDeclaration(_type))
{
abstract = true;
var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver);
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
}
else if (isBool(_type.category()))
var = make_shared<SymbolicBoolVariable>(type, _uniqueName, _solver);
@ -130,7 +137,7 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
else if (isFixedBytes(_type.category()))
{
auto fixedBytesType = dynamic_cast<FixedBytesType const*>(type);
auto fixedBytesType = dynamic_cast<solidity::FixedBytesType const*>(type);
solAssert(fixedBytesType, "");
var = make_shared<SymbolicFixedBytesVariable>(fixedBytesType->numBytes(), _uniqueName, _solver);
}
@ -140,10 +147,10 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _solver);
else if (isRational(_type.category()))
{
auto rational = dynamic_cast<RationalNumberType const*>(&_type);
auto rational = dynamic_cast<solidity::RationalNumberType const*>(&_type);
solAssert(rational, "");
if (rational->isFractional())
var = make_shared<SymbolicIntVariable>(TypeProvider::uint256(), _uniqueName, _solver);
var = make_shared<SymbolicIntVariable>(solidity::TypeProvider::uint256(), _uniqueName, _solver);
else
var = make_shared<SymbolicIntVariable>(type, _uniqueName, _solver);
}
@ -151,52 +158,54 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver);
else if (isArray(_type.category()))
var = make_shared<SymbolicArrayVariable>(type, _uniqueName, _solver);
else if (isTuple(_type.category()))
var = make_shared<SymbolicTupleVariable>(type, _uniqueName, _solver);
else
solAssert(false, "");
return make_pair(abstract, var);
}
bool dev::solidity::isSupportedType(Type const& _type)
bool isSupportedType(solidity::Type const& _type)
{
return isSupportedType(_type.category());
}
bool dev::solidity::isSupportedTypeDeclaration(Type const& _type)
bool isSupportedTypeDeclaration(solidity::Type const& _type)
{
return isSupportedTypeDeclaration(_type.category());
}
bool dev::solidity::isInteger(Type::Category _category)
bool isInteger(solidity::Type::Category _category)
{
return _category == Type::Category::Integer;
return _category == solidity::Type::Category::Integer;
}
bool dev::solidity::isRational(Type::Category _category)
bool isRational(solidity::Type::Category _category)
{
return _category == Type::Category::RationalNumber;
return _category == solidity::Type::Category::RationalNumber;
}
bool dev::solidity::isFixedBytes(Type::Category _category)
bool isFixedBytes(solidity::Type::Category _category)
{
return _category == Type::Category::FixedBytes;
return _category == solidity::Type::Category::FixedBytes;
}
bool dev::solidity::isAddress(Type::Category _category)
bool isAddress(solidity::Type::Category _category)
{
return _category == Type::Category::Address;
return _category == solidity::Type::Category::Address;
}
bool dev::solidity::isContract(Type::Category _category)
bool isContract(solidity::Type::Category _category)
{
return _category == Type::Category::Contract;
return _category == solidity::Type::Category::Contract;
}
bool dev::solidity::isEnum(Type::Category _category)
bool isEnum(solidity::Type::Category _category)
{
return _category == Type::Category::Enum;
return _category == solidity::Type::Category::Enum;
}
bool dev::solidity::isNumber(Type::Category _category)
bool isNumber(solidity::Type::Category _category)
{
return isInteger(_category) ||
isRational(_category) ||
@ -206,70 +215,79 @@ bool dev::solidity::isNumber(Type::Category _category)
isEnum(_category);
}
bool dev::solidity::isBool(Type::Category _category)
bool isBool(solidity::Type::Category _category)
{
return _category == Type::Category::Bool;
return _category == solidity::Type::Category::Bool;
}
bool dev::solidity::isFunction(Type::Category _category)
bool isFunction(solidity::Type::Category _category)
{
return _category == Type::Category::Function;
return _category == solidity::Type::Category::Function;
}
bool dev::solidity::isMapping(Type::Category _category)
bool isMapping(solidity::Type::Category _category)
{
return _category == Type::Category::Mapping;
return _category == solidity::Type::Category::Mapping;
}
bool dev::solidity::isArray(Type::Category _category)
bool isArray(solidity::Type::Category _category)
{
return _category == Type::Category::Array;
return _category == solidity::Type::Category::Array;
}
smt::Expression dev::solidity::minValue(IntegerType const& _type)
bool isTuple(solidity::Type::Category _category)
{
return smt::Expression(_type.minValue());
return _category == solidity::Type::Category::Tuple;
}
smt::Expression dev::solidity::maxValue(IntegerType const& _type)
Expression minValue(solidity::IntegerType const& _type)
{
return smt::Expression(_type.maxValue());
return Expression(_type.minValue());
}
void dev::solidity::smt::setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface)
Expression maxValue(solidity::IntegerType const& _type)
{
return Expression(_type.maxValue());
}
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface)
{
setSymbolicZeroValue(_variable.currentValue(), _variable.type(), _interface);
}
void dev::solidity::smt::setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface)
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
{
solAssert(_type, "");
if (isInteger(_type->category()))
_interface.addAssertion(_expr == 0);
else if (isBool(_type->category()))
_interface.addAssertion(_expr == smt::Expression(false));
_interface.addAssertion(_expr == Expression(false));
}
void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface)
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface)
{
setSymbolicUnknownValue(_variable.currentValue(), _variable.type(), _interface);
}
void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface)
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface)
{
solAssert(_type, "");
if (isEnum(_type->category()))
{
auto enumType = dynamic_cast<EnumType const*>(_type);
auto enumType = dynamic_cast<solidity::EnumType const*>(_type);
solAssert(enumType, "");
_interface.addAssertion(_expr >= 0);
_interface.addAssertion(_expr < enumType->numberOfMembers());
}
else if (isInteger(_type->category()))
{
auto intType = dynamic_cast<IntegerType const*>(_type);
auto intType = dynamic_cast<solidity::IntegerType const*>(_type);
solAssert(intType, "");
_interface.addAssertion(_expr >= minValue(*intType));
_interface.addAssertion(_expr <= maxValue(*intType));
}
}
}
}
}

View File

@ -26,49 +26,48 @@ namespace dev
{
namespace solidity
{
namespace smt
{
/// Returns the SMT sort that models the Solidity type _type.
smt::SortPointer smtSort(Type const& _type);
std::vector<smt::SortPointer> smtSort(std::vector<TypePointer> const& _types);
SortPointer smtSort(solidity::Type const& _type);
std::vector<SortPointer> smtSort(std::vector<solidity::TypePointer> const& _types);
/// Returns the SMT kind that models the Solidity type type category _category.
smt::Kind smtKind(Type::Category _category);
Kind smtKind(solidity::Type::Category _category);
/// Returns true if type is fully supported (declaration and operations).
bool isSupportedType(Type::Category _category);
bool isSupportedType(Type const& _type);
bool isSupportedType(solidity::Type::Category _category);
bool isSupportedType(solidity::Type const& _type);
/// Returns true if type is partially supported (declaration).
bool isSupportedTypeDeclaration(Type::Category _category);
bool isSupportedTypeDeclaration(Type const& _type);
bool isSupportedTypeDeclaration(solidity::Type::Category _category);
bool isSupportedTypeDeclaration(solidity::Type const& _type);
bool isInteger(Type::Category _category);
bool isRational(Type::Category _category);
bool isFixedBytes(Type::Category _category);
bool isAddress(Type::Category _category);
bool isContract(Type::Category _category);
bool isEnum(Type::Category _category);
bool isNumber(Type::Category _category);
bool isBool(Type::Category _category);
bool isFunction(Type::Category _category);
bool isMapping(Type::Category _category);
bool isArray(Type::Category _category);
bool isInteger(solidity::Type::Category _category);
bool isRational(solidity::Type::Category _category);
bool isFixedBytes(solidity::Type::Category _category);
bool isAddress(solidity::Type::Category _category);
bool isContract(solidity::Type::Category _category);
bool isEnum(solidity::Type::Category _category);
bool isNumber(solidity::Type::Category _category);
bool isBool(solidity::Type::Category _category);
bool isFunction(solidity::Type::Category _category);
bool isMapping(solidity::Type::Category _category);
bool isArray(solidity::Type::Category _category);
bool isTuple(solidity::Type::Category _category);
/// Returns a new symbolic variable, according to _type.
/// Also returns whether the type is abstract or not,
/// which is true for unsupported types.
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver);
std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(solidity::Type const& _type, std::string const& _uniqueName, SolverInterface& _solver);
smt::Expression minValue(IntegerType const& _type);
smt::Expression maxValue(IntegerType const& _type);
Expression minValue(solidity::IntegerType const& _type);
Expression maxValue(solidity::IntegerType const& _type);
namespace smt
{
void setSymbolicZeroValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface);
void setSymbolicZeroValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface);
void setSymbolicUnknownValue(SymbolicVariable const& _variable, smt::SolverInterface& _interface);
void setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface);
}
void setSymbolicZeroValue(SymbolicVariable const& _variable, SolverInterface& _interface);
void setSymbolicZeroValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
void setSymbolicUnknownValue(SymbolicVariable const& _variable, SolverInterface& _interface);
void setSymbolicUnknownValue(Expression _expr, solidity::TypePointer const& _type, SolverInterface& _interface);
}
}
}

View File

@ -23,17 +23,17 @@
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::smt;
SymbolicVariable::SymbolicVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
m_type(move(_type)),
m_uniqueName(move(_uniqueName)),
m_interface(_interface),
m_ssa(make_shared<SSAVariable>())
m_ssa(make_unique<SSAVariable>())
{
solAssert(m_type, "");
m_sort = smtSort(*m_type);
@ -41,19 +41,19 @@ SymbolicVariable::SymbolicVariable(
}
SymbolicVariable::SymbolicVariable(
smt::SortPointer _sort,
SortPointer _sort,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
m_sort(move(_sort)),
m_uniqueName(move(_uniqueName)),
m_interface(_interface),
m_ssa(make_shared<SSAVariable>())
m_ssa(make_unique<SSAVariable>())
{
solAssert(m_sort, "");
}
smt::Expression SymbolicVariable::currentValue() const
Expression SymbolicVariable::currentValue() const
{
return valueAtIndex(m_ssa->index());
}
@ -63,7 +63,7 @@ string SymbolicVariable::currentName() const
return uniqueSymbol(m_ssa->index());
}
smt::Expression SymbolicVariable::valueAtIndex(int _index) const
Expression SymbolicVariable::valueAtIndex(int _index) const
{
return m_interface.newVariable(uniqueSymbol(_index), m_sort);
}
@ -73,26 +73,26 @@ string SymbolicVariable::uniqueSymbol(unsigned _index) const
return m_uniqueName + "_" + to_string(_index);
}
smt::Expression SymbolicVariable::increaseIndex()
Expression SymbolicVariable::increaseIndex()
{
++(*m_ssa);
return currentValue();
}
SymbolicBoolVariable::SymbolicBoolVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
solAssert(m_type->category() == Type::Category::Bool, "");
solAssert(m_type->category() == solidity::Type::Category::Bool, "");
}
SymbolicIntVariable::SymbolicIntVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
@ -101,7 +101,7 @@ SymbolicIntVariable::SymbolicIntVariable(
SymbolicAddressVariable::SymbolicAddressVariable(
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicIntVariable(TypeProvider::uint(160), move(_uniqueName), _interface)
{
@ -110,21 +110,21 @@ SymbolicAddressVariable::SymbolicAddressVariable(
SymbolicFixedBytesVariable::SymbolicFixedBytesVariable(
unsigned _numBytes,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicIntVariable(TypeProvider::uint(_numBytes * 8), move(_uniqueName), _interface)
{
}
SymbolicFunctionVariable::SymbolicFunctionVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface),
m_declaration(m_interface.newVariable(currentName(), m_sort))
{
solAssert(m_type->category() == Type::Category::Function, "");
solAssert(m_type->category() == solidity::Type::Category::Function, "");
}
void SymbolicFunctionVariable::resetDeclaration()
@ -132,22 +132,22 @@ void SymbolicFunctionVariable::resetDeclaration()
m_declaration = m_interface.newVariable(currentName(), m_sort);
}
smt::Expression SymbolicFunctionVariable::increaseIndex()
Expression SymbolicFunctionVariable::increaseIndex()
{
++(*m_ssa);
resetDeclaration();
return currentValue();
}
smt::Expression SymbolicFunctionVariable::operator()(vector<smt::Expression> _arguments) const
Expression SymbolicFunctionVariable::operator()(vector<Expression> _arguments) const
{
return m_declaration(_arguments);
}
SymbolicMappingVariable::SymbolicMappingVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
@ -155,9 +155,9 @@ SymbolicMappingVariable::SymbolicMappingVariable(
}
SymbolicArrayVariable::SymbolicArrayVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
@ -165,11 +165,29 @@ SymbolicArrayVariable::SymbolicArrayVariable(
}
SymbolicEnumVariable::SymbolicEnumVariable(
TypePointer _type,
solidity::TypePointer _type,
string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
solAssert(isEnum(m_type->category()), "");
}
SymbolicTupleVariable::SymbolicTupleVariable(
solidity::TypePointer _type,
string _uniqueName,
SolverInterface& _interface
):
SymbolicVariable(move(_type), move(_uniqueName), _interface)
{
solAssert(isTuple(m_type->category()), "");
}
void SymbolicTupleVariable::setComponents(vector<shared_ptr<SymbolicVariable>> _components)
{
solAssert(m_components.empty(), "");
auto const& tupleType = dynamic_cast<solidity::TupleType const*>(m_type);
solAssert(_components.size() == tupleType->components().size(), "");
m_components = move(_components);
}

View File

@ -26,6 +26,8 @@ namespace dev
{
namespace solidity
{
namespace smt
{
class Type;
@ -36,23 +38,23 @@ class SymbolicVariable
{
public:
SymbolicVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
SymbolicVariable(
smt::SortPointer _sort,
SortPointer _sort,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
virtual ~SymbolicVariable() = default;
smt::Expression currentValue() const;
Expression currentValue() const;
std::string currentName() const;
virtual smt::Expression valueAtIndex(int _index) const;
virtual smt::Expression increaseIndex();
virtual smt::Expression operator()(std::vector<smt::Expression> /*_arguments*/) const
virtual Expression valueAtIndex(int _index) const;
virtual Expression increaseIndex();
virtual Expression operator()(std::vector<Expression> /*_arguments*/) const
{
solAssert(false, "Function application to non-function.");
}
@ -60,18 +62,18 @@ public:
unsigned index() const { return m_ssa->index(); }
unsigned& index() { return m_ssa->index(); }
TypePointer const& type() const { return m_type; }
solidity::TypePointer const& type() const { return m_type; }
protected:
std::string uniqueSymbol(unsigned _index) const;
/// SMT sort.
smt::SortPointer m_sort;
SortPointer m_sort;
/// Solidity type, used for size and range in number types.
TypePointer m_type;
solidity::TypePointer m_type;
std::string m_uniqueName;
smt::SolverInterface& m_interface;
std::shared_ptr<SSAVariable> m_ssa;
SolverInterface& m_interface;
std::unique_ptr<SSAVariable> m_ssa;
};
/**
@ -81,9 +83,9 @@ class SymbolicBoolVariable: public SymbolicVariable
{
public:
SymbolicBoolVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -94,9 +96,9 @@ class SymbolicIntVariable: public SymbolicVariable
{
public:
SymbolicIntVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -108,7 +110,7 @@ class SymbolicAddressVariable: public SymbolicIntVariable
public:
SymbolicAddressVariable(
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -121,7 +123,7 @@ public:
SymbolicFixedBytesVariable(
unsigned _numBytes,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -132,20 +134,20 @@ class SymbolicFunctionVariable: public SymbolicVariable
{
public:
SymbolicFunctionVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
smt::Expression increaseIndex();
smt::Expression operator()(std::vector<smt::Expression> _arguments) const;
Expression increaseIndex();
Expression operator()(std::vector<Expression> _arguments) const;
private:
/// Creates a new function declaration.
void resetDeclaration();
/// Stores the current function declaration.
smt::Expression m_declaration;
Expression m_declaration;
};
/**
@ -155,9 +157,9 @@ class SymbolicMappingVariable: public SymbolicVariable
{
public:
SymbolicMappingVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -168,9 +170,9 @@ class SymbolicArrayVariable: public SymbolicVariable
{
public:
SymbolicArrayVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
@ -181,11 +183,35 @@ class SymbolicEnumVariable: public SymbolicVariable
{
public:
SymbolicEnumVariable(
TypePointer _type,
solidity::TypePointer _type,
std::string _uniqueName,
smt::SolverInterface& _interface
SolverInterface& _interface
);
};
/**
* Specialization of SymbolicVariable for Tuple
*/
class SymbolicTupleVariable: public SymbolicVariable
{
public:
SymbolicTupleVariable(
solidity::TypePointer _type,
std::string _uniqueName,
SolverInterface& _interface
);
std::vector<std::shared_ptr<SymbolicVariable>> const& components()
{
return m_components;
}
void setComponents(std::vector<std::shared_ptr<SymbolicVariable>> _components);
private:
std::vector<std::shared_ptr<SymbolicVariable>> m_components;
};
}
}
}

View File

@ -24,33 +24,53 @@
using namespace std;
using namespace dev;
using namespace dev::solidity;
using namespace dev::solidity::smt;
set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<CallableDeclaration const*> const& _outerCallstack)
{
m_touchedVariables.clear();
m_callStack.clear();
m_callStack += _outerCallstack;
m_lastCall = m_callStack.back();
_node.accept(*this);
return m_touchedVariables;
}
void VariableUsage::endVisit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
solAssert(declaration, "");
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
if (_identifier.annotation().lValueRequested)
m_touchedVariables.insert(varDecl);
if (_identifier.annotation().lValueRequested)
checkIdentifier(_identifier);
}
void VariableUsage::endVisit(IndexAccess const& _indexAccess)
{
if (_indexAccess.annotation().lValueRequested)
{
/// identifier.annotation().lValueRequested == false, that's why we
/// need to check that before.
auto identifier = dynamic_cast<Identifier const*>(SMTChecker::leftmostBase(_indexAccess));
if (identifier)
checkIdentifier(*identifier);
}
}
void VariableUsage::endVisit(FunctionCall const& _funCall)
{
if (auto const& funDef = SMTChecker::inlinedFunctionCallToDefinition(_funCall))
if (find(m_functionPath.begin(), m_functionPath.end(), funDef) == m_functionPath.end())
if (find(m_callStack.begin(), m_callStack.end(), funDef) == m_callStack.end())
funDef->accept(*this);
}
bool VariableUsage::visit(FunctionDefinition const& _function)
{
m_functionPath.push_back(&_function);
m_callStack.push_back(&_function);
return true;
}
void VariableUsage::endVisit(FunctionDefinition const&)
{
solAssert(!m_functionPath.empty(), "");
m_functionPath.pop_back();
solAssert(!m_callStack.empty(), "");
m_callStack.pop_back();
}
void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
@ -62,18 +82,23 @@ void VariableUsage::endVisit(ModifierInvocation const& _modifierInv)
void VariableUsage::endVisit(PlaceholderStatement const&)
{
solAssert(!m_functionPath.empty(), "");
FunctionDefinition const* function = m_functionPath.back();
solAssert(function, "");
if (function->isImplemented())
function->body().accept(*this);
solAssert(!m_callStack.empty(), "");
FunctionDefinition const* funDef = nullptr;
for (auto it = m_callStack.rbegin(); it != m_callStack.rend() && !funDef; ++it)
funDef = dynamic_cast<FunctionDefinition const*>(*it);
solAssert(funDef, "");
if (funDef->isImplemented())
funDef->body().accept(*this);
}
set<VariableDeclaration const*> VariableUsage::touchedVariables(ASTNode const& _node, vector<FunctionDefinition const*> const& _outerCallstack)
void VariableUsage::checkIdentifier(Identifier const& _identifier)
{
m_touchedVariables.clear();
m_functionPath.clear();
m_functionPath += _outerCallstack;
_node.accept(*this);
return m_touchedVariables;
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
solAssert(declaration, "");
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
{
solAssert(m_lastCall, "");
if (!varDecl->isLocalVariable() || varDecl->functionOrModifierDefinition() == m_lastCall)
m_touchedVariables.insert(varDecl);
}
}

View File

@ -26,6 +26,8 @@ namespace dev
{
namespace solidity
{
namespace smt
{
/**
* This class computes information about which variables are modified in a certain subtree.
@ -34,19 +36,25 @@ class VariableUsage: private ASTConstVisitor
{
public:
/// @param _outerCallstack the current callstack in the callers context.
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<FunctionDefinition const*> const& _outerCallstack);
std::set<VariableDeclaration const*> touchedVariables(ASTNode const& _node, std::vector<CallableDeclaration const*> const& _outerCallstack);
private:
void endVisit(Identifier const& _node) override;
void endVisit(IndexAccess const& _node) override;
void endVisit(FunctionCall const& _node) override;
bool visit(FunctionDefinition const& _node) override;
void endVisit(FunctionDefinition const& _node) override;
void endVisit(ModifierInvocation const& _node) override;
void endVisit(PlaceholderStatement const& _node) override;
/// Checks whether an identifier should be added to touchedVariables.
void checkIdentifier(Identifier const& _identifier);
std::set<VariableDeclaration const*> m_touchedVariables;
std::vector<FunctionDefinition const*> m_functionPath;
std::vector<CallableDeclaration const*> m_callStack;
CallableDeclaration const* m_lastCall = nullptr;
};
}
}
}

View File

@ -56,6 +56,7 @@
#include <libevmasm/Exceptions.h>
#include <libdevcore/SwarmHash.h>
#include <libdevcore/IpfsHash.h>
#include <libdevcore/JSON.h>
#include <json/json.h>
@ -216,7 +217,7 @@ bool CompilerStack::parse()
string const& path = sourcesToParse[i];
Source& source = m_sources[path];
source.scanner->reset();
source.ast = Parser(m_errorReporter).parse(source.scanner);
source.ast = Parser(m_errorReporter, m_evmVersion, m_parserErrorRecovery).parse(source.scanner);
if (!source.ast)
solAssert(!Error::containsOnlyWarnings(m_errorReporter.errors()), "Parser returned null but did not report error.");
else
@ -249,7 +250,7 @@ bool CompilerStack::analyze()
bool noErrors = true;
try {
SyntaxChecker syntaxChecker(m_errorReporter);
SyntaxChecker syntaxChecker(m_errorReporter, m_optimiserSettings.runYulOptimiser);
for (Source const* source: m_sourceOrder)
if (!syntaxChecker.checkSyntax(*source->ast))
noErrors = false;
@ -260,7 +261,7 @@ bool CompilerStack::analyze()
noErrors = false;
m_globalContext = make_shared<GlobalContext>();
NameAndTypeResolver resolver(m_globalContext->declarations(), m_scopes, m_errorReporter);
NameAndTypeResolver resolver(*m_globalContext, m_scopes, m_errorReporter);
for (Source const* source: m_sourceOrder)
if (!resolver.registerDeclarations(*source->ast))
return false;
@ -278,11 +279,8 @@ bool CompilerStack::analyze()
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 (!resolver.resolveNamesAndTypes(*contract)) 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
@ -397,7 +395,8 @@ bool CompilerStack::isRequestedContract(ContractDefinition const& _contract) con
return
m_requestedContractNames.empty() ||
m_requestedContractNames.count(_contract.fullyQualifiedName()) ||
m_requestedContractNames.count(_contract.name());
m_requestedContractNames.count(_contract.name()) ||
m_requestedContractNames.count(":" + _contract.name());
}
bool CompilerStack::compile()
@ -771,6 +770,13 @@ h256 const& CompilerStack::Source::swarmHash() const
return swarmHashCached;
}
string const& CompilerStack::Source::ipfsUrl() const
{
if (ipfsUrlCached.empty())
if (scanner->source().size() < 1024 * 256)
ipfsUrlCached = "dweb:/ipfs/" + dev::ipfsHashBase58(scanner->source());
return ipfsUrlCached;
}
StringMap CompilerStack::loadMissingSources(SourceUnit const& _ast, std::string const& _sourcePath)
{
@ -1032,6 +1038,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const
{
meta["sources"][s.first]["urls"] = Json::arrayValue;
meta["sources"][s.first]["urls"].append("bzzr://" + toHex(s.second.swarmHash().asBytes()));
meta["sources"][s.first]["urls"].append(s.second.ipfsUrl());
}
}
@ -1180,6 +1187,10 @@ bytes CompilerStack::createCBORMetadata(string const& _metadata, bool _experimen
encoder.pushBytes("bzzr0", dev::swarmHash(_metadata).asBytes());
if (_experimentalMode)
encoder.pushBool("experimental", true);
if (m_release)
encoder.pushBytes("solc", VersionCompactBytes);
else
encoder.pushString("solc", VersionStringStrict);
return encoder.serialise();
}

View File

@ -25,6 +25,7 @@
#include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/Version.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
@ -131,6 +132,14 @@ public:
/// Must be set before parsing.
void setOptimiserSettings(OptimiserSettings _settings);
/// Set whether or not parser error is desired.
/// When called without an argument it will revert to the default.
/// Must be set before parsing.
void setParserErrorRecovery(bool _wantErrorRecovery = false)
{
m_parserErrorRecovery = _wantErrorRecovery;
}
/// Set the EVM version used before running compile.
/// When called without an argument it will revert to the default version.
/// Must be set before parsing.
@ -261,6 +270,8 @@ public:
/// @returns a JSON representing the estimated gas usage for contract creation, internal and external functions
Json::Value gasEstimates(std::string const& _contractName) const;
/// Overwrites the release/prerelease flag. Should only be used for testing.
void overwriteReleaseFlag(bool release) { m_release = release; }
private:
/// The state per source unit. Filled gradually during parsing.
struct Source
@ -269,9 +280,11 @@ private:
std::shared_ptr<SourceUnit> ast;
h256 mutable keccak256HashCached;
h256 mutable swarmHashCached;
std::string mutable ipfsUrlCached;
void reset() { *this = Source(); }
h256 const& keccak256() const;
h256 const& swarmHash() const;
std::string const& ipfsUrl() const;
};
/// The state per contract. Filled gradually during compilation.
@ -333,7 +346,7 @@ private:
std::string createMetadata(Contract const& _contract) const;
/// @returns the metadata CBOR for the given serialised metadata JSON.
static bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
bytes createCBORMetadata(std::string const& _metadata, bool _experimentalMode);
/// @returns the computer source mapping string.
std::string computeSourceMapping(eth::AssemblyItems const& _items) const;
@ -381,7 +394,9 @@ private:
langutil::ErrorList m_errorList;
langutil::ErrorReporter m_errorReporter;
bool m_metadataLiteralSources = false;
bool m_parserErrorRecovery = false;
State m_stackState = Empty;
bool m_release = VersionIsRelease;
};
}

View File

@ -437,7 +437,7 @@ boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Valu
return *error;
}
}
return std::move(settings);
return { std::move(settings) };
}
}
@ -663,7 +663,7 @@ boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompile
ret.outputSelection = std::move(outputSelection);
return std::move(ret);
return { std::move(ret) };
}
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings)
@ -965,6 +965,8 @@ Json::Value StandardCompiler::compileYul(InputsAndSettings _inputsAndSettings)
Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
{
YulStringRepository::reset();
try
{
auto parsed = parseInput(_input);

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